diff --git a/16/umbraco-cms/.gitbook.yaml b/16/umbraco-cms/.gitbook.yaml new file mode 100644 index 00000000000..a6cb5d706d4 --- /dev/null +++ b/16/umbraco-cms/.gitbook.yaml @@ -0,0 +1,8 @@ +root: ./ + +​structure: + readme: README.md + summary: SUMMARY.md + +redirects: + \ No newline at end of file diff --git a/16/umbraco-cms/.gitbook/assets/Additional_Info (1).jpg b/16/umbraco-cms/.gitbook/assets/Additional_Info (1).jpg new file mode 100644 index 00000000000..72a2929b890 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/Additional_Info (1).jpg differ diff --git a/16/umbraco-cms/.gitbook/assets/Additional_Info (2).jpg b/16/umbraco-cms/.gitbook/assets/Additional_Info (2).jpg new file mode 100644 index 00000000000..72a2929b890 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/Additional_Info (2).jpg differ diff --git a/16/umbraco-cms/.gitbook/assets/Additional_Info.jpg b/16/umbraco-cms/.gitbook/assets/Additional_Info.jpg new file mode 100644 index 00000000000..72a2929b890 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/Additional_Info.jpg differ diff --git a/16/umbraco-cms/.gitbook/assets/Canvas_tab (1).png b/16/umbraco-cms/.gitbook/assets/Canvas_tab (1).png new file mode 100644 index 00000000000..1e6b0c0730a Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/Canvas_tab (1).png differ diff --git a/16/umbraco-cms/.gitbook/assets/Canvas_tab (2).png b/16/umbraco-cms/.gitbook/assets/Canvas_tab (2).png new file mode 100644 index 00000000000..1e6b0c0730a Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/Canvas_tab (2).png differ diff --git a/16/umbraco-cms/.gitbook/assets/Canvas_tab.png b/16/umbraco-cms/.gitbook/assets/Canvas_tab.png new file mode 100644 index 00000000000..1e6b0c0730a Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/Canvas_tab.png differ diff --git a/16/umbraco-cms/.gitbook/assets/ContentPicker-data-type-definition.png b/16/umbraco-cms/.gitbook/assets/ContentPicker-data-type-definition.png new file mode 100644 index 00000000000..59b364cbe99 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/ContentPicker-data-type-definition.png differ diff --git a/16/umbraco-cms/.gitbook/assets/Create_LaunchJson_file.jpg b/16/umbraco-cms/.gitbook/assets/Create_LaunchJson_file.jpg new file mode 100644 index 00000000000..0ce06b712d2 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/Create_LaunchJson_file.jpg differ diff --git a/16/umbraco-cms/.gitbook/assets/Create_dashboard_functionality (1).png b/16/umbraco-cms/.gitbook/assets/Create_dashboard_functionality (1).png new file mode 100644 index 00000000000..2ffb4829d22 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/Create_dashboard_functionality (1).png differ diff --git a/16/umbraco-cms/.gitbook/assets/Create_dashboard_functionality.png b/16/umbraco-cms/.gitbook/assets/Create_dashboard_functionality.png new file mode 100644 index 00000000000..2ffb4829d22 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/Create_dashboard_functionality.png differ diff --git a/16/umbraco-cms/.gitbook/assets/Create_dashboard_functionality_gettting_data (1).png b/16/umbraco-cms/.gitbook/assets/Create_dashboard_functionality_gettting_data (1).png new file mode 100644 index 00000000000..ebc88d8d4fb Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/Create_dashboard_functionality_gettting_data (1).png differ diff --git a/16/umbraco-cms/.gitbook/assets/Create_dashboard_functionality_gettting_data.png b/16/umbraco-cms/.gitbook/assets/Create_dashboard_functionality_gettting_data.png new file mode 100644 index 00000000000..ebc88d8d4fb Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/Create_dashboard_functionality_gettting_data.png differ diff --git a/16/umbraco-cms/.gitbook/assets/Create_dashboard_functionality_users_list (1).png b/16/umbraco-cms/.gitbook/assets/Create_dashboard_functionality_users_list (1).png new file mode 100644 index 00000000000..2393d1f4590 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/Create_dashboard_functionality_users_list (1).png differ diff --git a/16/umbraco-cms/.gitbook/assets/Create_dashboard_functionality_users_list.png b/16/umbraco-cms/.gitbook/assets/Create_dashboard_functionality_users_list.png new file mode 100644 index 00000000000..2393d1f4590 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/Create_dashboard_functionality_users_list.png differ diff --git a/16/umbraco-cms/.gitbook/assets/Create_dashboard_functionality_users_list_ui_styled (1).png b/16/umbraco-cms/.gitbook/assets/Create_dashboard_functionality_users_list_ui_styled (1).png new file mode 100644 index 00000000000..41774b290b1 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/Create_dashboard_functionality_users_list_ui_styled (1).png differ diff --git a/16/umbraco-cms/.gitbook/assets/Create_dashboard_functionality_users_list_ui_styled.png b/16/umbraco-cms/.gitbook/assets/Create_dashboard_functionality_users_list_ui_styled.png new file mode 100644 index 00000000000..41774b290b1 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/Create_dashboard_functionality_users_list_ui_styled.png differ diff --git a/16/umbraco-cms/.gitbook/assets/Create_dashboard_functionality_users_list_ui_styled_table (1).png b/16/umbraco-cms/.gitbook/assets/Create_dashboard_functionality_users_list_ui_styled_table (1).png new file mode 100644 index 00000000000..71dc772189f Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/Create_dashboard_functionality_users_list_ui_styled_table (1).png differ diff --git a/16/umbraco-cms/.gitbook/assets/Create_dashboard_functionality_users_list_ui_styled_table.png b/16/umbraco-cms/.gitbook/assets/Create_dashboard_functionality_users_list_ui_styled_table.png new file mode 100644 index 00000000000..71dc772189f Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/Create_dashboard_functionality_users_list_ui_styled_table.png differ diff --git a/16/umbraco-cms/.gitbook/assets/Create_first_extension_Typescript (1).png b/16/umbraco-cms/.gitbook/assets/Create_first_extension_Typescript (1).png new file mode 100644 index 00000000000..298fc34e257 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/Create_first_extension_Typescript (1).png differ diff --git a/16/umbraco-cms/.gitbook/assets/Create_first_extension_Typescript.png b/16/umbraco-cms/.gitbook/assets/Create_first_extension_Typescript.png new file mode 100644 index 00000000000..298fc34e257 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/Create_first_extension_Typescript.png differ diff --git a/16/umbraco-cms/.gitbook/assets/Create_first_extension_Vanilla (1).png b/16/umbraco-cms/.gitbook/assets/Create_first_extension_Vanilla (1).png new file mode 100644 index 00000000000..d132d1b4d23 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/Create_first_extension_Vanilla (1).png differ diff --git a/16/umbraco-cms/.gitbook/assets/Create_first_extension_Vanilla.png b/16/umbraco-cms/.gitbook/assets/Create_first_extension_Vanilla.png new file mode 100644 index 00000000000..d132d1b4d23 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/Create_first_extension_Vanilla.png differ diff --git a/16/umbraco-cms/.gitbook/assets/Docs_tab (1).png b/16/umbraco-cms/.gitbook/assets/Docs_tab (1).png new file mode 100644 index 00000000000..ac21325f172 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/Docs_tab (1).png differ diff --git a/16/umbraco-cms/.gitbook/assets/Docs_tab (2).png b/16/umbraco-cms/.gitbook/assets/Docs_tab (2).png new file mode 100644 index 00000000000..ac21325f172 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/Docs_tab (2).png differ diff --git a/16/umbraco-cms/.gitbook/assets/Docs_tab.png b/16/umbraco-cms/.gitbook/assets/Docs_tab.png new file mode 100644 index 00000000000..ac21325f172 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/Docs_tab.png differ diff --git a/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Extending_Dashboards.png b/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Extending_Dashboards.png new file mode 100644 index 00000000000..3521daba379 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Extending_Dashboards.png differ diff --git a/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Extending_Health_Checks.png b/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Extending_Health_Checks.png new file mode 100644 index 00000000000..5524b73c767 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Extending_Health_Checks.png differ diff --git a/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Extending_Packages.png b/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Extending_Packages.png new file mode 100644 index 00000000000..126a7bb65a1 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Extending_Packages.png differ diff --git a/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Extending_Property_Editors.png b/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Extending_Property_Editors.png new file mode 100644 index 00000000000..555d76dfbee Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Extending_Property_Editors.png differ diff --git a/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Extending_Sections_and_Trees.png b/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Extending_Sections_and_Trees.png new file mode 100644 index 00000000000..c313d797d13 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Extending_Sections_and_Trees.png differ diff --git a/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Fundamentals.png b/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Fundamentals.png new file mode 100644 index 00000000000..b9010a07979 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Fundamentals.png differ diff --git a/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Fundamentals_Backoffice (1) (1).png b/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Fundamentals_Backoffice (1) (1).png new file mode 100644 index 00000000000..07e0d5c35eb Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Fundamentals_Backoffice (1) (1).png differ diff --git a/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Fundamentals_Backoffice (1) (2).png b/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Fundamentals_Backoffice (1) (2).png new file mode 100644 index 00000000000..07e0d5c35eb Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Fundamentals_Backoffice (1) (2).png differ diff --git a/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Fundamentals_Backoffice (1).png b/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Fundamentals_Backoffice (1).png new file mode 100644 index 00000000000..07e0d5c35eb Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Fundamentals_Backoffice (1).png differ diff --git a/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Fundamentals_Backoffice.png b/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Fundamentals_Backoffice.png new file mode 100644 index 00000000000..07e0d5c35eb Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Fundamentals_Backoffice.png differ diff --git a/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Fundamentals_Code.png b/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Fundamentals_Code.png new file mode 100644 index 00000000000..6258bb83e76 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Fundamentals_Code.png differ diff --git a/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Fundamentals_Data.png b/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Fundamentals_Data.png new file mode 100644 index 00000000000..635ea063b60 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Fundamentals_Data.png differ diff --git a/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Fundamentals_Design.png b/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Fundamentals_Design.png new file mode 100644 index 00000000000..b3ed5209357 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Fundamentals_Design.png differ diff --git a/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Fundamentals_Setup (1).png b/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Fundamentals_Setup (1).png new file mode 100644 index 00000000000..7ec0cf1d7b5 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Fundamentals_Setup (1).png differ diff --git a/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Fundamentals_Setup.png b/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Fundamentals_Setup.png new file mode 100644 index 00000000000..7ec0cf1d7b5 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Fundamentals_Setup.png differ diff --git a/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Implementation_Composing.png b/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Implementation_Composing.png new file mode 100644 index 00000000000..b10aa4d8651 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Implementation_Composing.png differ diff --git a/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Implementation_Controllers.png b/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Implementation_Controllers.png new file mode 100644 index 00000000000..56e75d65963 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Implementation_Controllers.png differ diff --git a/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Implementation_Custom_Routing.png b/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Implementation_Custom_Routing.png new file mode 100644 index 00000000000..4ac0bbf95a0 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Implementation_Custom_Routing.png differ diff --git a/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Implementation_Data_Persistence.png b/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Implementation_Data_Persistence.png new file mode 100644 index 00000000000..ac956eafbbc Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Implementation_Data_Persistence.png differ diff --git a/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Implementation_Routing.png b/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Implementation_Routing.png new file mode 100644 index 00000000000..8ea13956765 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Implementation_Routing.png differ diff --git a/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Implementation_Services_and_Helpers.png b/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Implementation_Services_and_Helpers.png new file mode 100644 index 00000000000..724c96c54d3 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Implementation_Services_and_Helpers.png differ diff --git a/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Install (1).png b/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Install (1).png new file mode 100644 index 00000000000..01beb93f1e6 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Install (1).png differ diff --git a/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Install.png b/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Install.png new file mode 100644 index 00000000000..01beb93f1e6 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Install.png differ diff --git a/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Reference_API_Documentation.png b/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Reference_API_Documentation.png new file mode 100644 index 00000000000..7164d6542f9 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Reference_API_Documentation.png differ diff --git a/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Reference_Caching.png b/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Reference_Caching.png new file mode 100644 index 00000000000..41b02fe3c4a Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Reference_Caching.png differ diff --git a/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Reference_Configuration.png b/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Reference_Configuration.png new file mode 100644 index 00000000000..35d43482ffd Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Reference_Configuration.png differ diff --git a/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Reference_Notifications.png b/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Reference_Notifications.png new file mode 100644 index 00000000000..5790d353ba3 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Reference_Notifications.png differ diff --git a/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Reference_Querying_and_Models.png b/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Reference_Querying_and_Models.png new file mode 100644 index 00000000000..61b11658050 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Reference_Querying_and_Models.png differ diff --git a/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Reference_Routing_and_Controllers.png b/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Reference_Routing_and_Controllers.png new file mode 100644 index 00000000000..8529aae789f Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Reference_Routing_and_Controllers.png differ diff --git a/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Reference_Searching (1).png b/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Reference_Searching (1).png new file mode 100644 index 00000000000..36feaf25719 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Reference_Searching (1).png differ diff --git a/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Reference_Searching.png b/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Reference_Searching.png new file mode 100644 index 00000000000..36feaf25719 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Reference_Searching.png differ diff --git a/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Reference_Security.png b/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Reference_Security.png new file mode 100644 index 00000000000..a9bbebbbea0 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Reference_Security.png differ diff --git a/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Reference_Templating.png b/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Reference_Templating.png new file mode 100644 index 00000000000..94df11bb19a Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Reference_Templating.png differ diff --git a/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Tutorials.png b/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Tutorials.png new file mode 100644 index 00000000000..f96fffc1c43 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Tutorials.png differ diff --git a/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Tutorials_Basic_Website.png b/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Tutorials_Basic_Website.png new file mode 100644 index 00000000000..45f98513f48 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Tutorials_Basic_Website.png differ diff --git a/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Tutorials_Custom_Dashboard.png b/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Tutorials_Custom_Dashboard.png new file mode 100644 index 00000000000..49a429c95da Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Tutorials_Custom_Dashboard.png differ diff --git a/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Tutorials_Custom_Error_Page.png b/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Tutorials_Custom_Error_Page.png new file mode 100644 index 00000000000..f95896c57ef Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Tutorials_Custom_Error_Page.png differ diff --git a/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Tutorials_Custom_Property_Editor.png b/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Tutorials_Custom_Property_Editor.png new file mode 100644 index 00000000000..fb540e90782 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Tutorials_Custom_Property_Editor.png differ diff --git a/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Tutorials_Custom_Views_Block_List.png b/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Tutorials_Custom_Views_Block_List.png new file mode 100644 index 00000000000..8b0c842229f Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Tutorials_Custom_Views_Block_List.png differ diff --git a/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Tutorials_Google_Authentification.png b/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Tutorials_Google_Authentification.png new file mode 100644 index 00000000000..3f678967f20 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Tutorials_Google_Authentification.png differ diff --git a/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Tutorials_Member_Reg_and_login.png b/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Tutorials_Member_Reg_and_login.png new file mode 100644 index 00000000000..66dc300073f Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Tutorials_Member_Reg_and_login.png differ diff --git a/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Tutorials_MultiSite_Setup (1).png b/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Tutorials_MultiSite_Setup (1).png new file mode 100644 index 00000000000..a6e56cde534 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Tutorials_MultiSite_Setup (1).png differ diff --git a/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Tutorials_MultiSite_Setup.png b/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Tutorials_MultiSite_Setup.png new file mode 100644 index 00000000000..a6e56cde534 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Tutorials_MultiSite_Setup.png differ diff --git a/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Tutorials_Multilingual_Website.png b/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Tutorials_Multilingual_Website.png new file mode 100644 index 00000000000..92cc76d753f Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Tutorials_Multilingual_Website.png differ diff --git a/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Tutorials_Umbraco_Forms_and_Zapier.png b/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Tutorials_Umbraco_Forms_and_Zapier.png new file mode 100644 index 00000000000..ccffbc3c872 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Tutorials_Umbraco_Forms_and_Zapier.png differ diff --git a/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Tutorials_the_Starter_Kit (1).png b/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Tutorials_the_Starter_Kit (1).png new file mode 100644 index 00000000000..aed96f1fd6a Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Tutorials_the_Starter_Kit (1).png differ diff --git a/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Tutorials_the_Starter_Kit.png b/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Tutorials_the_Starter_Kit.png new file mode 100644 index 00000000000..aed96f1fd6a Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_CMS_Tutorials_the_Starter_Kit.png differ diff --git a/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_Cloud_Setup.png b/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_Cloud_Setup.png new file mode 100644 index 00000000000..0dad8ed536e Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_Cloud_Setup.png differ diff --git a/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_Commerce_How_to_Guide.png b/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_Commerce_How_to_Guide.png new file mode 100644 index 00000000000..bdcd0b3f83c Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_Commerce_How_to_Guide.png differ diff --git a/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_Forms_Install (1).png b/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_Forms_Install (1).png new file mode 100644 index 00000000000..e4aa739cb56 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_Forms_Install (1).png differ diff --git a/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_Forms_Install.png b/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_Forms_Install.png new file mode 100644 index 00000000000..e4aa739cb56 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/Documentations Icons_Umbraco_Forms_Install.png differ diff --git a/16/umbraco-cms/.gitbook/assets/Dropdown_option.jpg b/16/umbraco-cms/.gitbook/assets/Dropdown_option.jpg new file mode 100644 index 00000000000..8c6c135c273 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/Dropdown_option.jpg differ diff --git a/16/umbraco-cms/.gitbook/assets/Install_Umbraco.jpg b/16/umbraco-cms/.gitbook/assets/Install_Umbraco.jpg new file mode 100644 index 00000000000..88c43ab285e Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/Install_Umbraco.jpg differ diff --git a/16/umbraco-cms/.gitbook/assets/Latest_nightly_build_version.jpg b/16/umbraco-cms/.gitbook/assets/Latest_nightly_build_version.jpg new file mode 100644 index 00000000000..341da76534c Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/Latest_nightly_build_version.jpg differ diff --git a/16/umbraco-cms/.gitbook/assets/Manage_NuGet_Pkgs (1).jpg b/16/umbraco-cms/.gitbook/assets/Manage_NuGet_Pkgs (1).jpg new file mode 100644 index 00000000000..68138f7e4b6 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/Manage_NuGet_Pkgs (1).jpg differ diff --git a/16/umbraco-cms/.gitbook/assets/Manage_NuGet_Pkgs.jpg b/16/umbraco-cms/.gitbook/assets/Manage_NuGet_Pkgs.jpg new file mode 100644 index 00000000000..68138f7e4b6 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/Manage_NuGet_Pkgs.jpg differ diff --git a/16/umbraco-cms/.gitbook/assets/Manage_Packages.jpg b/16/umbraco-cms/.gitbook/assets/Manage_Packages.jpg new file mode 100644 index 00000000000..9ff2c8538dc Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/Manage_Packages.jpg differ diff --git a/16/umbraco-cms/.gitbook/assets/Marketplace.jpg b/16/umbraco-cms/.gitbook/assets/Marketplace.jpg new file mode 100644 index 00000000000..e9d31ab959e Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/Marketplace.jpg differ diff --git a/16/umbraco-cms/.gitbook/assets/ModelsBuilderDisabledOnProduction.png b/16/umbraco-cms/.gitbook/assets/ModelsBuilderDisabledOnProduction.png new file mode 100644 index 00000000000..14492e61ab3 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/ModelsBuilderDisabledOnProduction.png differ diff --git a/16/umbraco-cms/.gitbook/assets/NewFeed_Details.jpg b/16/umbraco-cms/.gitbook/assets/NewFeed_Details.jpg new file mode 100644 index 00000000000..5ce96af75da Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/NewFeed_Details.jpg differ diff --git a/16/umbraco-cms/.gitbook/assets/NewPropertyEditor (1).png b/16/umbraco-cms/.gitbook/assets/NewPropertyEditor (1).png new file mode 100644 index 00000000000..b7803bdf34b Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/NewPropertyEditor (1).png differ diff --git a/16/umbraco-cms/.gitbook/assets/NewPropertyEditor.png b/16/umbraco-cms/.gitbook/assets/NewPropertyEditor.png new file mode 100644 index 00000000000..b7803bdf34b Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/NewPropertyEditor.png differ diff --git a/16/umbraco-cms/.gitbook/assets/NewPropertyEditorButtons (1).png b/16/umbraco-cms/.gitbook/assets/NewPropertyEditorButtons (1).png new file mode 100644 index 00000000000..1da56df1786 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/NewPropertyEditorButtons (1).png differ diff --git a/16/umbraco-cms/.gitbook/assets/NewPropertyEditorButtons.png b/16/umbraco-cms/.gitbook/assets/NewPropertyEditorButtons.png new file mode 100644 index 00000000000..1da56df1786 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/NewPropertyEditorButtons.png differ diff --git a/16/umbraco-cms/.gitbook/assets/NewPropertyEditorSuggestions (1).png b/16/umbraco-cms/.gitbook/assets/NewPropertyEditorSuggestions (1).png new file mode 100644 index 00000000000..fa8291c3905 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/NewPropertyEditorSuggestions (1).png differ diff --git a/16/umbraco-cms/.gitbook/assets/NewPropertyEditorSuggestions.png b/16/umbraco-cms/.gitbook/assets/NewPropertyEditorSuggestions.png new file mode 100644 index 00000000000..fa8291c3905 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/NewPropertyEditorSuggestions.png differ diff --git a/16/umbraco-cms/.gitbook/assets/New_Project.jpg b/16/umbraco-cms/.gitbook/assets/New_Project.jpg new file mode 100644 index 00000000000..0666959592a Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/New_Project.jpg differ diff --git a/16/umbraco-cms/.gitbook/assets/NuGet_NewFeed.jpg b/16/umbraco-cms/.gitbook/assets/NuGet_NewFeed.jpg new file mode 100644 index 00000000000..e17dee522b9 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/NuGet_NewFeed.jpg differ diff --git a/16/umbraco-cms/.gitbook/assets/Package-Manager-Settings (1) (1).jpg b/16/umbraco-cms/.gitbook/assets/Package-Manager-Settings (1) (1).jpg new file mode 100644 index 00000000000..748293bccb7 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/Package-Manager-Settings (1) (1).jpg differ diff --git a/16/umbraco-cms/.gitbook/assets/Package-Manager-Settings (1) (2).jpg b/16/umbraco-cms/.gitbook/assets/Package-Manager-Settings (1) (2).jpg new file mode 100644 index 00000000000..748293bccb7 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/Package-Manager-Settings (1) (2).jpg differ diff --git a/16/umbraco-cms/.gitbook/assets/Package-Manager-Settings (1).jpg b/16/umbraco-cms/.gitbook/assets/Package-Manager-Settings (1).jpg new file mode 100644 index 00000000000..748293bccb7 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/Package-Manager-Settings (1).jpg differ diff --git a/16/umbraco-cms/.gitbook/assets/Package-Manager-Settings.jpg b/16/umbraco-cms/.gitbook/assets/Package-Manager-Settings.jpg new file mode 100644 index 00000000000..748293bccb7 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/Package-Manager-Settings.jpg differ diff --git a/16/umbraco-cms/.gitbook/assets/Pasted image 20230525192217 (1).png b/16/umbraco-cms/.gitbook/assets/Pasted image 20230525192217 (1).png new file mode 100644 index 00000000000..9e2ea36a5f8 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/Pasted image 20230525192217 (1).png differ diff --git a/16/umbraco-cms/.gitbook/assets/Pasted image 20230525192217.png b/16/umbraco-cms/.gitbook/assets/Pasted image 20230525192217.png new file mode 100644 index 00000000000..9e2ea36a5f8 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/Pasted image 20230525192217.png differ diff --git a/16/umbraco-cms/.gitbook/assets/Prompt_Menu.jpg b/16/umbraco-cms/.gitbook/assets/Prompt_Menu.jpg new file mode 100644 index 00000000000..e700d4609b9 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/Prompt_Menu.jpg differ diff --git a/16/umbraco-cms/.gitbook/assets/ReferencedDataTypes.PNG b/16/umbraco-cms/.gitbook/assets/ReferencedDataTypes.PNG new file mode 100644 index 00000000000..a0d6f1abbb9 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/ReferencedDataTypes.PNG differ diff --git a/16/umbraco-cms/.gitbook/assets/Register_Nightly_Feed.jpg b/16/umbraco-cms/.gitbook/assets/Register_Nightly_Feed.jpg new file mode 100644 index 00000000000..bd549030ecc Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/Register_Nightly_Feed.jpg differ diff --git a/16/umbraco-cms/.gitbook/assets/Rider_Nightly_Feed.jpg b/16/umbraco-cms/.gitbook/assets/Rider_Nightly_Feed.jpg new file mode 100644 index 00000000000..68701876301 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/Rider_Nightly_Feed.jpg differ diff --git a/16/umbraco-cms/.gitbook/assets/Rider_Nightly_Feed_version.jpg b/16/umbraco-cms/.gitbook/assets/Rider_Nightly_Feed_version.jpg new file mode 100644 index 00000000000..9b60bb59f21 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/Rider_Nightly_Feed_version.jpg differ diff --git a/16/umbraco-cms/.gitbook/assets/Solution_Explorer.png b/16/umbraco-cms/.gitbook/assets/Solution_Explorer.png new file mode 100644 index 00000000000..254eb8d89be Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/Solution_Explorer.png differ diff --git a/16/umbraco-cms/.gitbook/assets/TemplatedCannotBeEditedWhenRuntimeIsProduction.png b/16/umbraco-cms/.gitbook/assets/TemplatedCannotBeEditedWhenRuntimeIsProduction.png new file mode 100644 index 00000000000..f6379dc05f6 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/TemplatedCannotBeEditedWhenRuntimeIsProduction.png differ diff --git a/16/umbraco-cms/.gitbook/assets/Top_hero_for_com_1500x500px2.png b/16/umbraco-cms/.gitbook/assets/Top_hero_for_com_1500x500px2.png new file mode 100644 index 00000000000..10ef4a25841 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/Top_hero_for_com_1500x500px2.png differ diff --git a/16/umbraco-cms/.gitbook/assets/UUI.jpg b/16/umbraco-cms/.gitbook/assets/UUI.jpg new file mode 100644 index 00000000000..28a397b8e2f Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/UUI.jpg differ diff --git a/16/umbraco-cms/.gitbook/assets/Umbraco 15 - Release - Hero_1665x438px.jpg b/16/umbraco-cms/.gitbook/assets/Umbraco 15 - Release - Hero_1665x438px.jpg new file mode 100644 index 00000000000..00e1e766fd8 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/Umbraco 15 - Release - Hero_1665x438px.jpg differ diff --git a/16/umbraco-cms/.gitbook/assets/Umbraco 15 - Release - SoMe_1200x628px.jpg b/16/umbraco-cms/.gitbook/assets/Umbraco 15 - Release - SoMe_1200x628px.jpg new file mode 100644 index 00000000000..222ddb45785 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/Umbraco 15 - Release - SoMe_1200x628px.jpg differ diff --git a/16/umbraco-cms/.gitbook/assets/Umbraco 15 - Release - Thumbnail_898x598px.jpg b/16/umbraco-cms/.gitbook/assets/Umbraco 15 - Release - Thumbnail_898x598px.jpg new file mode 100644 index 00000000000..2037767fcf5 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/Umbraco 15 - Release - Thumbnail_898x598px.jpg differ diff --git a/16/umbraco-cms/.gitbook/assets/Using_Dictionary_Value.jpg b/16/umbraco-cms/.gitbook/assets/Using_Dictionary_Value.jpg new file mode 100644 index 00000000000..71aafdbe493 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/Using_Dictionary_Value.jpg differ diff --git a/16/umbraco-cms/.gitbook/assets/VS-Package-Sources (1).jpg b/16/umbraco-cms/.gitbook/assets/VS-Package-Sources (1).jpg new file mode 100644 index 00000000000..d29f5cc865b Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/VS-Package-Sources (1).jpg differ diff --git a/16/umbraco-cms/.gitbook/assets/VS-Package-Sources.jpg b/16/umbraco-cms/.gitbook/assets/VS-Package-Sources.jpg new file mode 100644 index 00000000000..d29f5cc865b Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/VS-Package-Sources.jpg differ diff --git a/16/umbraco-cms/.gitbook/assets/VS_Code_Explorer.png b/16/umbraco-cms/.gitbook/assets/VS_Code_Explorer.png new file mode 100644 index 00000000000..8a2fb9da115 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/VS_Code_Explorer.png differ diff --git a/16/umbraco-cms/.gitbook/assets/Vite_Package_Setup_Dashboard (1).png b/16/umbraco-cms/.gitbook/assets/Vite_Package_Setup_Dashboard (1).png new file mode 100644 index 00000000000..1a623c467ba Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/Vite_Package_Setup_Dashboard (1).png differ diff --git a/16/umbraco-cms/.gitbook/assets/Vite_Package_Setup_Dashboard.png b/16/umbraco-cms/.gitbook/assets/Vite_Package_Setup_Dashboard.png new file mode 100644 index 00000000000..1a623c467ba Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/Vite_Package_Setup_Dashboard.png differ diff --git a/16/umbraco-cms/.gitbook/assets/WeKnowWhatYouEditedLastWeek-v10 (1).png b/16/umbraco-cms/.gitbook/assets/WeKnowWhatYouEditedLastWeek-v10 (1).png new file mode 100644 index 00000000000..fa519c365bc Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/WeKnowWhatYouEditedLastWeek-v10 (1).png differ diff --git a/16/umbraco-cms/.gitbook/assets/WeKnowWhatYouEditedLastWeek-v10 (2).png b/16/umbraco-cms/.gitbook/assets/WeKnowWhatYouEditedLastWeek-v10 (2).png new file mode 100644 index 00000000000..fa519c365bc Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/WeKnowWhatYouEditedLastWeek-v10 (2).png differ diff --git a/16/umbraco-cms/.gitbook/assets/WeKnowWhatYouEditedLastWeek-v10.png b/16/umbraco-cms/.gitbook/assets/WeKnowWhatYouEditedLastWeek-v10.png new file mode 100644 index 00000000000..fa519c365bc Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/WeKnowWhatYouEditedLastWeek-v10.png differ diff --git a/16/umbraco-cms/.gitbook/assets/add-custom-section-v8 (1).png b/16/umbraco-cms/.gitbook/assets/add-custom-section-v8 (1).png new file mode 100644 index 00000000000..d5465050e54 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/add-custom-section-v8 (1).png differ diff --git a/16/umbraco-cms/.gitbook/assets/add-custom-section-v8.png b/16/umbraco-cms/.gitbook/assets/add-custom-section-v8.png new file mode 100644 index 00000000000..d5465050e54 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/add-custom-section-v8.png differ diff --git a/16/umbraco-cms/.gitbook/assets/all-users-first-look.png b/16/umbraco-cms/.gitbook/assets/all-users-first-look.png new file mode 100644 index 00000000000..c1f369af340 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/all-users-first-look.png differ diff --git a/16/umbraco-cms/.gitbook/assets/all-users-first-look2.png b/16/umbraco-cms/.gitbook/assets/all-users-first-look2.png new file mode 100644 index 00000000000..1a2a34217db Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/all-users-first-look2.png differ diff --git a/16/umbraco-cms/.gitbook/assets/api-docs-image.png b/16/umbraco-cms/.gitbook/assets/api-docs-image.png new file mode 100644 index 00000000000..215f4191aff Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/api-docs-image.png differ diff --git a/16/umbraco-cms/.gitbook/assets/app-pligins-contents.png b/16/umbraco-cms/.gitbook/assets/app-pligins-contents.png new file mode 100644 index 00000000000..c0350372ad7 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/app-pligins-contents.png differ diff --git a/16/umbraco-cms/.gitbook/assets/append-step-to-query.png b/16/umbraco-cms/.gitbook/assets/append-step-to-query.png new file mode 100644 index 00000000000..e180bb29a74 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/append-step-to-query.png differ diff --git a/16/umbraco-cms/.gitbook/assets/are-you-hungry.png b/16/umbraco-cms/.gitbook/assets/are-you-hungry.png new file mode 100644 index 00000000000..d3367721f56 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/are-you-hungry.png differ diff --git a/16/umbraco-cms/.gitbook/assets/backoffice-overview-customizations.png b/16/umbraco-cms/.gitbook/assets/backoffice-overview-customizations.png new file mode 100644 index 00000000000..144ff1379a1 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/backoffice-overview-customizations.png differ diff --git a/16/umbraco-cms/.gitbook/assets/backoffice-search-v8 (1).png b/16/umbraco-cms/.gitbook/assets/backoffice-search-v8 (1).png new file mode 100644 index 00000000000..1ab9820f3bc Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/backoffice-search-v8 (1).png differ diff --git a/16/umbraco-cms/.gitbook/assets/backoffice-search-v8.png b/16/umbraco-cms/.gitbook/assets/backoffice-search-v8.png new file mode 100644 index 00000000000..1ab9820f3bc Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/backoffice-search-v8.png differ diff --git a/16/umbraco-cms/.gitbook/assets/backoffice-ui-images (1).png b/16/umbraco-cms/.gitbook/assets/backoffice-ui-images (1).png new file mode 100644 index 00000000000..52949da0a85 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/backoffice-ui-images (1).png differ diff --git a/16/umbraco-cms/.gitbook/assets/backoffice-ui-images.png b/16/umbraco-cms/.gitbook/assets/backoffice-ui-images.png new file mode 100644 index 00000000000..52949da0a85 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/backoffice-ui-images.png differ diff --git a/16/umbraco-cms/.gitbook/assets/backoffice.png b/16/umbraco-cms/.gitbook/assets/backoffice.png new file mode 100644 index 00000000000..fb407f3e003 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/backoffice.png differ diff --git a/16/umbraco-cms/.gitbook/assets/block-list-image.png b/16/umbraco-cms/.gitbook/assets/block-list-image.png new file mode 100644 index 00000000000..7e2462aff88 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/block-list-image.png differ diff --git a/16/umbraco-cms/.gitbook/assets/c-routing-image.png b/16/umbraco-cms/.gitbook/assets/c-routing-image.png new file mode 100644 index 00000000000..6ef562a2139 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/c-routing-image.png differ diff --git a/16/umbraco-cms/.gitbook/assets/caching-image.png b/16/umbraco-cms/.gitbook/assets/caching-image.png new file mode 100644 index 00000000000..16c31b7d1fd Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/caching-image.png differ diff --git a/16/umbraco-cms/.gitbook/assets/changing-languages-v10 (1).png b/16/umbraco-cms/.gitbook/assets/changing-languages-v10 (1).png new file mode 100644 index 00000000000..51b59df68af Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/changing-languages-v10 (1).png differ diff --git a/16/umbraco-cms/.gitbook/assets/changing-languages-v10 (2).png b/16/umbraco-cms/.gitbook/assets/changing-languages-v10 (2).png new file mode 100644 index 00000000000..51b59df68af Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/changing-languages-v10 (2).png differ diff --git a/16/umbraco-cms/.gitbook/assets/changing-languages-v10.png b/16/umbraco-cms/.gitbook/assets/changing-languages-v10.png new file mode 100644 index 00000000000..51b59df68af Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/changing-languages-v10.png differ diff --git a/16/umbraco-cms/.gitbook/assets/code-image.png b/16/umbraco-cms/.gitbook/assets/code-image.png new file mode 100644 index 00000000000..37060e9deb1 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/code-image.png differ diff --git a/16/umbraco-cms/.gitbook/assets/collection-icon.png b/16/umbraco-cms/.gitbook/assets/collection-icon.png new file mode 100644 index 00000000000..41b58466ad7 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/collection-icon.png differ diff --git a/16/umbraco-cms/.gitbook/assets/collection-settings-example-15-1.png b/16/umbraco-cms/.gitbook/assets/collection-settings-example-15-1.png new file mode 100644 index 00000000000..9b989d60176 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/collection-settings-example-15-1.png differ diff --git a/16/umbraco-cms/.gitbook/assets/composing-image.png b/16/umbraco-cms/.gitbook/assets/composing-image.png new file mode 100644 index 00000000000..84114e14334 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/composing-image.png differ diff --git a/16/umbraco-cms/.gitbook/assets/consolelog.png b/16/umbraco-cms/.gitbook/assets/consolelog.png new file mode 100644 index 00000000000..ac07082a2e4 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/consolelog.png differ diff --git a/16/umbraco-cms/.gitbook/assets/content-picker-query-steps.png b/16/umbraco-cms/.gitbook/assets/content-picker-query-steps.png new file mode 100644 index 00000000000..0204da0e2a6 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/content-picker-query-steps.png differ diff --git a/16/umbraco-cms/.gitbook/assets/controllers-image (1).png b/16/umbraco-cms/.gitbook/assets/controllers-image (1).png new file mode 100644 index 00000000000..18d83567ff4 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/controllers-image (1).png differ diff --git a/16/umbraco-cms/.gitbook/assets/controllers-image.png b/16/umbraco-cms/.gitbook/assets/controllers-image.png new file mode 100644 index 00000000000..18d83567ff4 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/controllers-image.png differ diff --git a/16/umbraco-cms/.gitbook/assets/create-website-image.png b/16/umbraco-cms/.gitbook/assets/create-website-image.png new file mode 100644 index 00000000000..950d99bb992 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/create-website-image.png differ diff --git a/16/umbraco-cms/.gitbook/assets/creating-a-property-editor-trim.png b/16/umbraco-cms/.gitbook/assets/creating-a-property-editor-trim.png new file mode 100644 index 00000000000..5a964fd5215 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/creating-a-property-editor-trim.png differ diff --git a/16/umbraco-cms/.gitbook/assets/creating-package-menu-v9.png b/16/umbraco-cms/.gitbook/assets/creating-package-menu-v9.png new file mode 100644 index 00000000000..da73453c0a6 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/creating-package-menu-v9.png differ diff --git a/16/umbraco-cms/.gitbook/assets/custom-section-alias-v8 (1).png b/16/umbraco-cms/.gitbook/assets/custom-section-alias-v8 (1).png new file mode 100644 index 00000000000..6a8e9633af9 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/custom-section-alias-v8 (1).png differ diff --git a/16/umbraco-cms/.gitbook/assets/custom-section-alias-v8.png b/16/umbraco-cms/.gitbook/assets/custom-section-alias-v8.png new file mode 100644 index 00000000000..6a8e9633af9 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/custom-section-alias-v8.png differ diff --git a/16/umbraco-cms/.gitbook/assets/dashboard-logviewer.png b/16/umbraco-cms/.gitbook/assets/dashboard-logviewer.png new file mode 100644 index 00000000000..47296f6475e Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/dashboard-logviewer.png differ diff --git a/16/umbraco-cms/.gitbook/assets/dashboard-translated-v10 (1).png b/16/umbraco-cms/.gitbook/assets/dashboard-translated-v10 (1).png new file mode 100644 index 00000000000..3f81a1b8dd0 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/dashboard-translated-v10 (1).png differ diff --git a/16/umbraco-cms/.gitbook/assets/dashboard-translated-v10 (2).png b/16/umbraco-cms/.gitbook/assets/dashboard-translated-v10 (2).png new file mode 100644 index 00000000000..3f81a1b8dd0 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/dashboard-translated-v10 (2).png differ diff --git a/16/umbraco-cms/.gitbook/assets/dashboard-translated-v10.png b/16/umbraco-cms/.gitbook/assets/dashboard-translated-v10.png new file mode 100644 index 00000000000..3f81a1b8dd0 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/dashboard-translated-v10.png differ diff --git a/16/umbraco-cms/.gitbook/assets/dashboard-untranslated-v10 (1).png b/16/umbraco-cms/.gitbook/assets/dashboard-untranslated-v10 (1).png new file mode 100644 index 00000000000..c5410b1f223 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/dashboard-untranslated-v10 (1).png differ diff --git a/16/umbraco-cms/.gitbook/assets/dashboard-untranslated-v10 (2).png b/16/umbraco-cms/.gitbook/assets/dashboard-untranslated-v10 (2).png new file mode 100644 index 00000000000..c5410b1f223 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/dashboard-untranslated-v10 (2).png differ diff --git a/16/umbraco-cms/.gitbook/assets/dashboard-untranslated-v10.png b/16/umbraco-cms/.gitbook/assets/dashboard-untranslated-v10.png new file mode 100644 index 00000000000..c5410b1f223 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/dashboard-untranslated-v10.png differ diff --git a/16/umbraco-cms/.gitbook/assets/dashboards-image.png b/16/umbraco-cms/.gitbook/assets/dashboards-image.png new file mode 100644 index 00000000000..ae820772b31 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/dashboards-image.png differ diff --git a/16/umbraco-cms/.gitbook/assets/data-flow.svg b/16/umbraco-cms/.gitbook/assets/data-flow.svg new file mode 100644 index 00000000000..e7a8481c17b --- /dev/null +++ b/16/umbraco-cms/.gitbook/assets/data-flow.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/16/umbraco-cms/.gitbook/assets/data-types-references-v10.png b/16/umbraco-cms/.gitbook/assets/data-types-references-v10.png new file mode 100644 index 00000000000..bc1cddf4a1c Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/data-types-references-v10.png differ diff --git a/16/umbraco-cms/.gitbook/assets/data.png b/16/umbraco-cms/.gitbook/assets/data.png new file mode 100644 index 00000000000..00b9bd36c20 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/data.png differ diff --git a/16/umbraco-cms/.gitbook/assets/datap-image.png b/16/umbraco-cms/.gitbook/assets/datap-image.png new file mode 100644 index 00000000000..33c7e6e117c Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/datap-image.png differ diff --git a/16/umbraco-cms/.gitbook/assets/delete-raindrops-on-roses-v8 (1).png b/16/umbraco-cms/.gitbook/assets/delete-raindrops-on-roses-v8 (1).png new file mode 100644 index 00000000000..ac562758a4b Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/delete-raindrops-on-roses-v8 (1).png differ diff --git a/16/umbraco-cms/.gitbook/assets/delete-raindrops-on-roses-v8.png b/16/umbraco-cms/.gitbook/assets/delete-raindrops-on-roses-v8.png new file mode 100644 index 00000000000..ac562758a4b Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/delete-raindrops-on-roses-v8.png differ diff --git a/16/umbraco-cms/.gitbook/assets/design.png b/16/umbraco-cms/.gitbook/assets/design.png new file mode 100644 index 00000000000..f35e9ca42fd Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/design.png differ diff --git a/16/umbraco-cms/.gitbook/assets/docs-style-hero.png b/16/umbraco-cms/.gitbook/assets/docs-style-hero.png new file mode 100644 index 00000000000..a63f4894788 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/docs-style-hero.png differ diff --git a/16/umbraco-cms/.gitbook/assets/document-references-v9.png b/16/umbraco-cms/.gitbook/assets/document-references-v9.png new file mode 100644 index 00000000000..ff428234d8e Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/document-references-v9.png differ diff --git a/16/umbraco-cms/.gitbook/assets/dynamic-templates.zip b/16/umbraco-cms/.gitbook/assets/dynamic-templates.zip new file mode 100644 index 00000000000..305af158943 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/dynamic-templates.zip differ diff --git a/16/umbraco-cms/.gitbook/assets/editorBar-v9.jpg b/16/umbraco-cms/.gitbook/assets/editorBar-v9.jpg new file mode 100644 index 00000000000..3b13ec937c3 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/editorBar-v9.jpg differ diff --git a/16/umbraco-cms/.gitbook/assets/embeded-resource-props.png b/16/umbraco-cms/.gitbook/assets/embeded-resource-props.png new file mode 100644 index 00000000000..ee8e0b6a49e Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/embeded-resource-props.png differ diff --git a/16/umbraco-cms/.gitbook/assets/embeded-resource.png b/16/umbraco-cms/.gitbook/assets/embeded-resource.png new file mode 100644 index 00000000000..f3ac2e59a1a Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/embeded-resource.png differ diff --git a/16/umbraco-cms/.gitbook/assets/embeded-zip-resource.png b/16/umbraco-cms/.gitbook/assets/embeded-zip-resource.png new file mode 100644 index 00000000000..4b85628f1a0 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/embeded-zip-resource.png differ diff --git a/16/umbraco-cms/.gitbook/assets/empty-package-from-template.png b/16/umbraco-cms/.gitbook/assets/empty-package-from-template.png new file mode 100644 index 00000000000..0a692a5c54e Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/empty-package-from-template.png differ diff --git a/16/umbraco-cms/.gitbook/assets/entity-action-collection-menu.svg b/16/umbraco-cms/.gitbook/assets/entity-action-collection-menu.svg new file mode 100644 index 00000000000..d9918b302aa --- /dev/null +++ b/16/umbraco-cms/.gitbook/assets/entity-action-collection-menu.svg @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/16/umbraco-cms/.gitbook/assets/entity-action-example-content-section.png b/16/umbraco-cms/.gitbook/assets/entity-action-example-content-section.png new file mode 100644 index 00000000000..d94ccac8158 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/entity-action-example-content-section.png differ diff --git a/16/umbraco-cms/.gitbook/assets/entity-action-picker-context-menu.svg b/16/umbraco-cms/.gitbook/assets/entity-action-picker-context-menu.svg new file mode 100644 index 00000000000..6a3daa0be37 --- /dev/null +++ b/16/umbraco-cms/.gitbook/assets/entity-action-picker-context-menu.svg @@ -0,0 +1,96 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/16/umbraco-cms/.gitbook/assets/entity-action-sidebar-context.svg b/16/umbraco-cms/.gitbook/assets/entity-action-sidebar-context.svg new file mode 100644 index 00000000000..5e0e9b44c2d --- /dev/null +++ b/16/umbraco-cms/.gitbook/assets/entity-action-sidebar-context.svg @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/16/umbraco-cms/.gitbook/assets/entity-action-workspace-menu.svg b/16/umbraco-cms/.gitbook/assets/entity-action-workspace-menu.svg new file mode 100644 index 00000000000..483aededf30 --- /dev/null +++ b/16/umbraco-cms/.gitbook/assets/entity-action-workspace-menu.svg @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/16/umbraco-cms/.gitbook/assets/entity-bulk-action-collection-menu.svg b/16/umbraco-cms/.gitbook/assets/entity-bulk-action-collection-menu.svg new file mode 100644 index 00000000000..71b4e5eb5c2 --- /dev/null +++ b/16/umbraco-cms/.gitbook/assets/entity-bulk-action-collection-menu.svg @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/16/umbraco-cms/.gitbook/assets/entity-user-permissions-ui.png b/16/umbraco-cms/.gitbook/assets/entity-user-permissions-ui.png new file mode 100644 index 00000000000..245a3d3a5b2 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/entity-user-permissions-ui.png differ diff --git a/16/umbraco-cms/.gitbook/assets/error-pages-image.png b/16/umbraco-cms/.gitbook/assets/error-pages-image.png new file mode 100644 index 00000000000..a54961025b6 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/error-pages-image.png differ diff --git a/16/umbraco-cms/.gitbook/assets/example-of-property-actions (1).jpg b/16/umbraco-cms/.gitbook/assets/example-of-property-actions (1).jpg new file mode 100644 index 00000000000..2e94865f559 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/example-of-property-actions (1).jpg differ diff --git a/16/umbraco-cms/.gitbook/assets/example-of-property-actions.jpg b/16/umbraco-cms/.gitbook/assets/example-of-property-actions.jpg new file mode 100644 index 00000000000..2e94865f559 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/example-of-property-actions.jpg differ diff --git a/16/umbraco-cms/.gitbook/assets/extendedWithUiLibrary (1).png b/16/umbraco-cms/.gitbook/assets/extendedWithUiLibrary (1).png new file mode 100644 index 00000000000..041139ba80f Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/extendedWithUiLibrary (1).png differ diff --git a/16/umbraco-cms/.gitbook/assets/extendedWithUiLibrary.png b/16/umbraco-cms/.gitbook/assets/extendedWithUiLibrary.png new file mode 100644 index 00000000000..041139ba80f Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/extendedWithUiLibrary.png differ diff --git a/16/umbraco-cms/.gitbook/assets/favourite-thing-custom-single-node-tree (1).png b/16/umbraco-cms/.gitbook/assets/favourite-thing-custom-single-node-tree (1).png new file mode 100644 index 00000000000..c40ceb25a75 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/favourite-thing-custom-single-node-tree (1).png differ diff --git a/16/umbraco-cms/.gitbook/assets/favourite-thing-custom-single-node-tree.png b/16/umbraco-cms/.gitbook/assets/favourite-thing-custom-single-node-tree.png new file mode 100644 index 00000000000..c40ceb25a75 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/favourite-thing-custom-single-node-tree.png differ diff --git a/16/umbraco-cms/.gitbook/assets/favourite-things-custom-tree-v8 (1).png b/16/umbraco-cms/.gitbook/assets/favourite-things-custom-tree-v8 (1).png new file mode 100644 index 00000000000..c6e99e95dc7 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/favourite-things-custom-tree-v8 (1).png differ diff --git a/16/umbraco-cms/.gitbook/assets/favourite-things-custom-tree-v8.png b/16/umbraco-cms/.gitbook/assets/favourite-things-custom-tree-v8.png new file mode 100644 index 00000000000..c6e99e95dc7 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/favourite-things-custom-tree-v8.png differ diff --git a/16/umbraco-cms/.gitbook/assets/favouritethings-search-v8 (1).png b/16/umbraco-cms/.gitbook/assets/favouritethings-search-v8 (1).png new file mode 100644 index 00000000000..c1010a8dda1 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/favouritethings-search-v8 (1).png differ diff --git a/16/umbraco-cms/.gitbook/assets/favouritethings-search-v8.png b/16/umbraco-cms/.gitbook/assets/favouritethings-search-v8.png new file mode 100644 index 00000000000..c1010a8dda1 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/favouritethings-search-v8.png differ diff --git a/16/umbraco-cms/.gitbook/assets/google-auth-image.png b/16/umbraco-cms/.gitbook/assets/google-auth-image.png new file mode 100644 index 00000000000..e3aa84cc8a4 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/google-auth-image.png differ diff --git a/16/umbraco-cms/.gitbook/assets/granular-user-permissions-ui.png b/16/umbraco-cms/.gitbook/assets/granular-user-permissions-ui.png new file mode 100644 index 00000000000..2b1a223e744 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/granular-user-permissions-ui.png differ diff --git a/16/umbraco-cms/.gitbook/assets/happy-anniversary.png b/16/umbraco-cms/.gitbook/assets/happy-anniversary.png new file mode 100644 index 00000000000..5fea181f52c Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/happy-anniversary.png differ diff --git a/16/umbraco-cms/.gitbook/assets/header-app-example-ts (1).png b/16/umbraco-cms/.gitbook/assets/header-app-example-ts (1).png new file mode 100644 index 00000000000..8153d236233 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/header-app-example-ts (1).png differ diff --git a/16/umbraco-cms/.gitbook/assets/header-app-example-ts.png b/16/umbraco-cms/.gitbook/assets/header-app-example-ts.png new file mode 100644 index 00000000000..65d2b1a206c Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/header-app-example-ts.png differ diff --git a/16/umbraco-cms/.gitbook/assets/header-app-example.png b/16/umbraco-cms/.gitbook/assets/header-app-example.png new file mode 100644 index 00000000000..1ea617940f0 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/header-app-example.png differ diff --git a/16/umbraco-cms/.gitbook/assets/header-apps.svg b/16/umbraco-cms/.gitbook/assets/header-apps.svg new file mode 100644 index 00000000000..c9054b02e1b --- /dev/null +++ b/16/umbraco-cms/.gitbook/assets/header-apps.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/16/umbraco-cms/.gitbook/assets/healthchecks-image (1).png b/16/umbraco-cms/.gitbook/assets/healthchecks-image (1).png new file mode 100644 index 00000000000..954ba2acfb7 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/healthchecks-image (1).png differ diff --git a/16/umbraco-cms/.gitbook/assets/healthchecks-image.png b/16/umbraco-cms/.gitbook/assets/healthchecks-image.png new file mode 100644 index 00000000000..954ba2acfb7 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/healthchecks-image.png differ diff --git a/16/umbraco-cms/.gitbook/assets/help-menu.svg b/16/umbraco-cms/.gitbook/assets/help-menu.svg new file mode 100644 index 00000000000..d5fc6683779 --- /dev/null +++ b/16/umbraco-cms/.gitbook/assets/help-menu.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/16/umbraco-cms/.gitbook/assets/html5up-story.zip b/16/umbraco-cms/.gitbook/assets/html5up-story.zip new file mode 100644 index 00000000000..7effddfb275 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/html5up-story.zip differ diff --git a/16/umbraco-cms/.gitbook/assets/image (1) (1).png b/16/umbraco-cms/.gitbook/assets/image (1) (1).png new file mode 100644 index 00000000000..f7933c19d15 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/image (1) (1).png differ diff --git a/16/umbraco-cms/.gitbook/assets/image (1).png b/16/umbraco-cms/.gitbook/assets/image (1).png new file mode 100644 index 00000000000..b65b978bf84 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/image (1).png differ diff --git a/16/umbraco-cms/.gitbook/assets/image (10).png b/16/umbraco-cms/.gitbook/assets/image (10).png new file mode 100644 index 00000000000..481423e8db6 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/image (10).png differ diff --git a/16/umbraco-cms/.gitbook/assets/image (11).png b/16/umbraco-cms/.gitbook/assets/image (11).png new file mode 100644 index 00000000000..eb4330ec76a Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/image (11).png differ diff --git a/16/umbraco-cms/.gitbook/assets/image (12).png b/16/umbraco-cms/.gitbook/assets/image (12).png new file mode 100644 index 00000000000..b8e8bf6135c Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/image (12).png differ diff --git a/16/umbraco-cms/.gitbook/assets/image (13).png b/16/umbraco-cms/.gitbook/assets/image (13).png new file mode 100644 index 00000000000..7fec9b4d8d0 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/image (13).png differ diff --git a/16/umbraco-cms/.gitbook/assets/image (14).png b/16/umbraco-cms/.gitbook/assets/image (14).png new file mode 100644 index 00000000000..41aa7327446 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/image (14).png differ diff --git a/16/umbraco-cms/.gitbook/assets/image (15).png b/16/umbraco-cms/.gitbook/assets/image (15).png new file mode 100644 index 00000000000..4c846b75695 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/image (15).png differ diff --git a/16/umbraco-cms/.gitbook/assets/image (16).png b/16/umbraco-cms/.gitbook/assets/image (16).png new file mode 100644 index 00000000000..78e18ac9371 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/image (16).png differ diff --git a/16/umbraco-cms/.gitbook/assets/image (17).png b/16/umbraco-cms/.gitbook/assets/image (17).png new file mode 100644 index 00000000000..57a6ac8ccd1 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/image (17).png differ diff --git a/16/umbraco-cms/.gitbook/assets/image (18).png b/16/umbraco-cms/.gitbook/assets/image (18).png new file mode 100644 index 00000000000..eaec414510d Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/image (18).png differ diff --git a/16/umbraco-cms/.gitbook/assets/image (19).png b/16/umbraco-cms/.gitbook/assets/image (19).png new file mode 100644 index 00000000000..0c35835d207 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/image (19).png differ diff --git a/16/umbraco-cms/.gitbook/assets/image (2).png b/16/umbraco-cms/.gitbook/assets/image (2).png new file mode 100644 index 00000000000..1b3afc7f2cd Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/image (2).png differ diff --git a/16/umbraco-cms/.gitbook/assets/image (20).png b/16/umbraco-cms/.gitbook/assets/image (20).png new file mode 100644 index 00000000000..7d9e34527fc Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/image (20).png differ diff --git a/16/umbraco-cms/.gitbook/assets/image (21).png b/16/umbraco-cms/.gitbook/assets/image (21).png new file mode 100644 index 00000000000..7d9e34527fc Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/image (21).png differ diff --git a/16/umbraco-cms/.gitbook/assets/image (22).png b/16/umbraco-cms/.gitbook/assets/image (22).png new file mode 100644 index 00000000000..12d9deb094f Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/image (22).png differ diff --git a/16/umbraco-cms/.gitbook/assets/image (23).png b/16/umbraco-cms/.gitbook/assets/image (23).png new file mode 100644 index 00000000000..9e9a5151407 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/image (23).png differ diff --git a/16/umbraco-cms/.gitbook/assets/image (24).png b/16/umbraco-cms/.gitbook/assets/image (24).png new file mode 100644 index 00000000000..3a8efa8e1ab Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/image (24).png differ diff --git a/16/umbraco-cms/.gitbook/assets/image (25).png b/16/umbraco-cms/.gitbook/assets/image (25).png new file mode 100644 index 00000000000..f7e83984290 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/image (25).png differ diff --git a/16/umbraco-cms/.gitbook/assets/image (26).png b/16/umbraco-cms/.gitbook/assets/image (26).png new file mode 100644 index 00000000000..8861f880c08 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/image (26).png differ diff --git a/16/umbraco-cms/.gitbook/assets/image (27).png b/16/umbraco-cms/.gitbook/assets/image (27).png new file mode 100644 index 00000000000..8861f880c08 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/image (27).png differ diff --git a/16/umbraco-cms/.gitbook/assets/image (28).png b/16/umbraco-cms/.gitbook/assets/image (28).png new file mode 100644 index 00000000000..da97b93df85 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/image (28).png differ diff --git a/16/umbraco-cms/.gitbook/assets/image (29).png b/16/umbraco-cms/.gitbook/assets/image (29).png new file mode 100644 index 00000000000..ede33c60a3f Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/image (29).png differ diff --git a/16/umbraco-cms/.gitbook/assets/image (3).png b/16/umbraco-cms/.gitbook/assets/image (3).png new file mode 100644 index 00000000000..68d64b6ae00 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/image (3).png differ diff --git a/16/umbraco-cms/.gitbook/assets/image (30).png b/16/umbraco-cms/.gitbook/assets/image (30).png new file mode 100644 index 00000000000..ea822a4d4d4 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/image (30).png differ diff --git a/16/umbraco-cms/.gitbook/assets/image (31).png b/16/umbraco-cms/.gitbook/assets/image (31).png new file mode 100644 index 00000000000..03716719629 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/image (31).png differ diff --git a/16/umbraco-cms/.gitbook/assets/image (32).png b/16/umbraco-cms/.gitbook/assets/image (32).png new file mode 100644 index 00000000000..03716719629 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/image (32).png differ diff --git a/16/umbraco-cms/.gitbook/assets/image (33).png b/16/umbraco-cms/.gitbook/assets/image (33).png new file mode 100644 index 00000000000..b9df7fb6648 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/image (33).png differ diff --git a/16/umbraco-cms/.gitbook/assets/image (4).png b/16/umbraco-cms/.gitbook/assets/image (4).png new file mode 100644 index 00000000000..67a9e94e1a6 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/image (4).png differ diff --git a/16/umbraco-cms/.gitbook/assets/image (5).png b/16/umbraco-cms/.gitbook/assets/image (5).png new file mode 100644 index 00000000000..aa1a9a634cc Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/image (5).png differ diff --git a/16/umbraco-cms/.gitbook/assets/image (6).png b/16/umbraco-cms/.gitbook/assets/image (6).png new file mode 100644 index 00000000000..51f0f235ed6 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/image (6).png differ diff --git a/16/umbraco-cms/.gitbook/assets/image (7).png b/16/umbraco-cms/.gitbook/assets/image (7).png new file mode 100644 index 00000000000..0ac6cdf8bc9 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/image (7).png differ diff --git a/16/umbraco-cms/.gitbook/assets/image (8).png b/16/umbraco-cms/.gitbook/assets/image (8).png new file mode 100644 index 00000000000..eb4330ec76a Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/image (8).png differ diff --git a/16/umbraco-cms/.gitbook/assets/image (9).png b/16/umbraco-cms/.gitbook/assets/image (9).png new file mode 100644 index 00000000000..802e64acf80 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/image (9).png differ diff --git a/16/umbraco-cms/.gitbook/assets/image(001).png b/16/umbraco-cms/.gitbook/assets/image(001).png new file mode 100644 index 00000000000..013578fcea0 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/image(001).png differ diff --git a/16/umbraco-cms/.gitbook/assets/image.png b/16/umbraco-cms/.gitbook/assets/image.png new file mode 100644 index 00000000000..0b1c999d566 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/image.png differ diff --git a/16/umbraco-cms/.gitbook/assets/installed-package.png b/16/umbraco-cms/.gitbook/assets/installed-package.png new file mode 100644 index 00000000000..715cf1f9bcb Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/installed-package.png differ diff --git a/16/umbraco-cms/.gitbook/assets/launchJson.jpg b/16/umbraco-cms/.gitbook/assets/launchJson.jpg new file mode 100644 index 00000000000..a9a8171036e Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/launchJson.jpg differ diff --git a/16/umbraco-cms/.gitbook/assets/media-references-v9.png b/16/umbraco-cms/.gitbook/assets/media-references-v9.png new file mode 100644 index 00000000000..8cd405efa6b Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/media-references-v9.png differ diff --git a/16/umbraco-cms/.gitbook/assets/member-image.png b/16/umbraco-cms/.gitbook/assets/member-image.png new file mode 100644 index 00000000000..7f90741a30b Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/member-image.png differ diff --git a/16/umbraco-cms/.gitbook/assets/menu-item.png b/16/umbraco-cms/.gitbook/assets/menu-item.png new file mode 100644 index 00000000000..f1f63f6aa15 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/menu-item.png differ diff --git a/16/umbraco-cms/.gitbook/assets/menu.png b/16/umbraco-cms/.gitbook/assets/menu.png new file mode 100644 index 00000000000..da3d9b2d237 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/menu.png differ diff --git a/16/umbraco-cms/.gitbook/assets/multilingual-site.png b/16/umbraco-cms/.gitbook/assets/multilingual-site.png new file mode 100644 index 00000000000..6a0121bf766 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/multilingual-site.png differ diff --git a/16/umbraco-cms/.gitbook/assets/multisite-image.png b/16/umbraco-cms/.gitbook/assets/multisite-image.png new file mode 100644 index 00000000000..78e415c519f Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/multisite-image.png differ diff --git a/16/umbraco-cms/.gitbook/assets/nothing-to-trim (1) (1).png b/16/umbraco-cms/.gitbook/assets/nothing-to-trim (1) (1).png new file mode 100644 index 00000000000..728e7e9d1a0 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/nothing-to-trim (1) (1).png differ diff --git a/16/umbraco-cms/.gitbook/assets/nothing-to-trim (1).png b/16/umbraco-cms/.gitbook/assets/nothing-to-trim (1).png new file mode 100644 index 00000000000..728e7e9d1a0 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/nothing-to-trim (1).png differ diff --git a/16/umbraco-cms/.gitbook/assets/nothing-to-trim.png b/16/umbraco-cms/.gitbook/assets/nothing-to-trim.png new file mode 100644 index 00000000000..728e7e9d1a0 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/nothing-to-trim.png differ diff --git a/16/umbraco-cms/.gitbook/assets/notifications-image.png b/16/umbraco-cms/.gitbook/assets/notifications-image.png new file mode 100644 index 00000000000..136257d2190 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/notifications-image.png differ diff --git a/16/umbraco-cms/.gitbook/assets/package-properties.png b/16/umbraco-cms/.gitbook/assets/package-properties.png new file mode 100644 index 00000000000..b7fb1b0d7f5 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/package-properties.png differ diff --git a/16/umbraco-cms/.gitbook/assets/packages-image (1).png b/16/umbraco-cms/.gitbook/assets/packages-image (1).png new file mode 100644 index 00000000000..796fe6fee73 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/packages-image (1).png differ diff --git a/16/umbraco-cms/.gitbook/assets/packages-image.png b/16/umbraco-cms/.gitbook/assets/packages-image.png new file mode 100644 index 00000000000..796fe6fee73 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/packages-image.png differ diff --git a/16/umbraco-cms/.gitbook/assets/pick-origin-root-node.png b/16/umbraco-cms/.gitbook/assets/pick-origin-root-node.png new file mode 100644 index 00000000000..611bc69c1ad Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/pick-origin-root-node.png differ diff --git a/16/umbraco-cms/.gitbook/assets/property-editor-config (1).png b/16/umbraco-cms/.gitbook/assets/property-editor-config (1).png new file mode 100644 index 00000000000..50af5d9de0c Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/property-editor-config (1).png differ diff --git a/16/umbraco-cms/.gitbook/assets/property-editor-config-on (1).png b/16/umbraco-cms/.gitbook/assets/property-editor-config-on (1).png new file mode 100644 index 00000000000..ee2326d84ba Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/property-editor-config-on (1).png differ diff --git a/16/umbraco-cms/.gitbook/assets/property-editor-config-on.png b/16/umbraco-cms/.gitbook/assets/property-editor-config-on.png new file mode 100644 index 00000000000..ee2326d84ba Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/property-editor-config-on.png differ diff --git a/16/umbraco-cms/.gitbook/assets/property-editor-config.png b/16/umbraco-cms/.gitbook/assets/property-editor-config.png new file mode 100644 index 00000000000..50af5d9de0c Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/property-editor-config.png differ diff --git a/16/umbraco-cms/.gitbook/assets/property-editor-first-look.png b/16/umbraco-cms/.gitbook/assets/property-editor-first-look.png new file mode 100644 index 00000000000..f726712c340 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/property-editor-first-look.png differ diff --git a/16/umbraco-cms/.gitbook/assets/property-editor-first-spawn.png b/16/umbraco-cms/.gitbook/assets/property-editor-first-spawn.png new file mode 100644 index 00000000000..8ced358a695 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/property-editor-first-spawn.png differ diff --git a/16/umbraco-cms/.gitbook/assets/property-image.png b/16/umbraco-cms/.gitbook/assets/property-image.png new file mode 100644 index 00000000000..b36657bc7db Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/property-image.png differ diff --git a/16/umbraco-cms/.gitbook/assets/public-access-icon.png b/16/umbraco-cms/.gitbook/assets/public-access-icon.png new file mode 100644 index 00000000000..de45936b8ca Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/public-access-icon.png differ diff --git a/16/umbraco-cms/.gitbook/assets/requestcollection.png b/16/umbraco-cms/.gitbook/assets/requestcollection.png new file mode 100644 index 00000000000..8edad9f9d22 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/requestcollection.png differ diff --git a/16/umbraco-cms/.gitbook/assets/reversed-heart-hero.png b/16/umbraco-cms/.gitbook/assets/reversed-heart-hero.png new file mode 100644 index 00000000000..76011a1be13 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/reversed-heart-hero.png differ diff --git a/16/umbraco-cms/.gitbook/assets/rocket-hero.png b/16/umbraco-cms/.gitbook/assets/rocket-hero.png new file mode 100644 index 00000000000..8ec6e8bfcb8 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/rocket-hero.png differ diff --git a/16/umbraco-cms/.gitbook/assets/routing-image.png b/16/umbraco-cms/.gitbook/assets/routing-image.png new file mode 100644 index 00000000000..19eda19e188 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/routing-image.png differ diff --git a/16/umbraco-cms/.gitbook/assets/searching-image.png b/16/umbraco-cms/.gitbook/assets/searching-image.png new file mode 100644 index 00000000000..e5651e643a1 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/searching-image.png differ diff --git a/16/umbraco-cms/.gitbook/assets/section-empty.png b/16/umbraco-cms/.gitbook/assets/section-empty.png new file mode 100644 index 00000000000..62299525623 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/section-empty.png differ diff --git a/16/umbraco-cms/.gitbook/assets/section-menu-sidebar-app.svg b/16/umbraco-cms/.gitbook/assets/section-menu-sidebar-app.svg new file mode 100644 index 00000000000..393ab5ec2d8 --- /dev/null +++ b/16/umbraco-cms/.gitbook/assets/section-menu-sidebar-app.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/16/umbraco-cms/.gitbook/assets/section-sidebar-apps.svg b/16/umbraco-cms/.gitbook/assets/section-sidebar-apps.svg new file mode 100644 index 00000000000..e781bc99002 --- /dev/null +++ b/16/umbraco-cms/.gitbook/assets/section-sidebar-apps.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/16/umbraco-cms/.gitbook/assets/section-sidebar-composed-apps.svg b/16/umbraco-cms/.gitbook/assets/section-sidebar-composed-apps.svg new file mode 100644 index 00000000000..4081881518c --- /dev/null +++ b/16/umbraco-cms/.gitbook/assets/section-sidebar-composed-apps.svg @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/16/umbraco-cms/.gitbook/assets/section-sidebar.svg b/16/umbraco-cms/.gitbook/assets/section-sidebar.svg new file mode 100644 index 00000000000..f88666f33e9 --- /dev/null +++ b/16/umbraco-cms/.gitbook/assets/section-sidebar.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/16/umbraco-cms/.gitbook/assets/section-views.svg b/16/umbraco-cms/.gitbook/assets/section-views.svg new file mode 100644 index 00000000000..45c88317b91 --- /dev/null +++ b/16/umbraco-cms/.gitbook/assets/section-views.svg @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/16/umbraco-cms/.gitbook/assets/section.svg b/16/umbraco-cms/.gitbook/assets/section.svg new file mode 100644 index 00000000000..a525f169bf7 --- /dev/null +++ b/16/umbraco-cms/.gitbook/assets/section.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/16/umbraco-cms/.gitbook/assets/security-imag.png b/16/umbraco-cms/.gitbook/assets/security-imag.png new file mode 100644 index 00000000000..a47343805a9 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/security-imag.png differ diff --git a/16/umbraco-cms/.gitbook/assets/services-image.png b/16/umbraco-cms/.gitbook/assets/services-image.png new file mode 100644 index 00000000000..7a6268434f9 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/services-image.png differ diff --git a/16/umbraco-cms/.gitbook/assets/setup-image.png b/16/umbraco-cms/.gitbook/assets/setup-image.png new file mode 100644 index 00000000000..612f4b29011 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/setup-image.png differ diff --git a/16/umbraco-cms/.gitbook/assets/setup.png b/16/umbraco-cms/.gitbook/assets/setup.png new file mode 100644 index 00000000000..8b85307ba8e Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/setup.png differ diff --git a/16/umbraco-cms/.gitbook/assets/spaces_G1Byxw7XfiZAj8zDMCTD_uploads_PtBQkEyVcGmoVx3ysAOJ_welcome (1).webp b/16/umbraco-cms/.gitbook/assets/spaces_G1Byxw7XfiZAj8zDMCTD_uploads_PtBQkEyVcGmoVx3ysAOJ_welcome (1).webp new file mode 100644 index 00000000000..8e14d6fe0e6 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/spaces_G1Byxw7XfiZAj8zDMCTD_uploads_PtBQkEyVcGmoVx3ysAOJ_welcome (1).webp differ diff --git a/16/umbraco-cms/.gitbook/assets/spaces_G1Byxw7XfiZAj8zDMCTD_uploads_PtBQkEyVcGmoVx3ysAOJ_welcome.webp b/16/umbraco-cms/.gitbook/assets/spaces_G1Byxw7XfiZAj8zDMCTD_uploads_PtBQkEyVcGmoVx3ysAOJ_welcome.webp new file mode 100644 index 00000000000..8e14d6fe0e6 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/spaces_G1Byxw7XfiZAj8zDMCTD_uploads_PtBQkEyVcGmoVx3ysAOJ_welcome.webp differ diff --git a/16/umbraco-cms/.gitbook/assets/spaces_OdQETpqkO0Kcv8KMquKL_uploads_git-blob-c7d7e59228a3b5738a1464489ef7601b7f8d350d_suggestion-property-editor (1).webp b/16/umbraco-cms/.gitbook/assets/spaces_OdQETpqkO0Kcv8KMquKL_uploads_git-blob-c7d7e59228a3b5738a1464489ef7601b7f8d350d_suggestion-property-editor (1).webp new file mode 100644 index 00000000000..a5af5f28cfa Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/spaces_OdQETpqkO0Kcv8KMquKL_uploads_git-blob-c7d7e59228a3b5738a1464489ef7601b7f8d350d_suggestion-property-editor (1).webp differ diff --git a/16/umbraco-cms/.gitbook/assets/spaces_OdQETpqkO0Kcv8KMquKL_uploads_git-blob-c7d7e59228a3b5738a1464489ef7601b7f8d350d_suggestion-property-editor.webp b/16/umbraco-cms/.gitbook/assets/spaces_OdQETpqkO0Kcv8KMquKL_uploads_git-blob-c7d7e59228a3b5738a1464489ef7601b7f8d350d_suggestion-property-editor.webp new file mode 100644 index 00000000000..a5af5f28cfa Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/spaces_OdQETpqkO0Kcv8KMquKL_uploads_git-blob-c7d7e59228a3b5738a1464489ef7601b7f8d350d_suggestion-property-editor.webp differ diff --git a/16/umbraco-cms/.gitbook/assets/specify-root-node.png b/16/umbraco-cms/.gitbook/assets/specify-root-node.png new file mode 100644 index 00000000000..6fa69a0a132 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/specify-root-node.png differ diff --git a/16/umbraco-cms/.gitbook/assets/starter-kit-image.png b/16/umbraco-cms/.gitbook/assets/starter-kit-image.png new file mode 100644 index 00000000000..150bd541804 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/starter-kit-image.png differ diff --git a/16/umbraco-cms/.gitbook/assets/static-templates.zip b/16/umbraco-cms/.gitbook/assets/static-templates.zip new file mode 100644 index 00000000000..5e0ee84185e Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/static-templates.zip differ diff --git a/16/umbraco-cms/.gitbook/assets/status.png b/16/umbraco-cms/.gitbook/assets/status.png new file mode 100644 index 00000000000..2109276c6b0 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/status.png differ diff --git a/16/umbraco-cms/.gitbook/assets/suggestions-datatype.png b/16/umbraco-cms/.gitbook/assets/suggestions-datatype.png new file mode 100644 index 00000000000..89a27e7c12a Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/suggestions-datatype.png differ diff --git a/16/umbraco-cms/.gitbook/assets/toolkit-image-4-3.png b/16/umbraco-cms/.gitbook/assets/toolkit-image-4-3.png new file mode 100644 index 00000000000..f1435b18627 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/toolkit-image-4-3.png differ diff --git a/16/umbraco-cms/.gitbook/assets/trees.image.png b/16/umbraco-cms/.gitbook/assets/trees.image.png new file mode 100644 index 00000000000..4579d6b0849 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/trees.image.png differ diff --git a/16/umbraco-cms/.gitbook/assets/trim-confirm (1).png b/16/umbraco-cms/.gitbook/assets/trim-confirm (1).png new file mode 100644 index 00000000000..7c8a638ada0 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/trim-confirm (1).png differ diff --git a/16/umbraco-cms/.gitbook/assets/trim-confirm.png b/16/umbraco-cms/.gitbook/assets/trim-confirm.png new file mode 100644 index 00000000000..7c8a638ada0 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/trim-confirm.png differ diff --git a/16/umbraco-cms/.gitbook/assets/uiLibraryCard (1).png b/16/umbraco-cms/.gitbook/assets/uiLibraryCard (1).png new file mode 100644 index 00000000000..21b2b6e4088 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/uiLibraryCard (1).png differ diff --git a/16/umbraco-cms/.gitbook/assets/uiLibraryCard.png b/16/umbraco-cms/.gitbook/assets/uiLibraryCard.png new file mode 100644 index 00000000000..21b2b6e4088 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/uiLibraryCard.png differ diff --git a/16/umbraco-cms/.gitbook/assets/uui-table-and-tag.png b/16/umbraco-cms/.gitbook/assets/uui-table-and-tag.png new file mode 100644 index 00000000000..950955fd251 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/uui-table-and-tag.png differ diff --git a/16/umbraco-cms/.gitbook/assets/uuiibox.png b/16/umbraco-cms/.gitbook/assets/uuiibox.png new file mode 100644 index 00000000000..f7c476f18ed Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/uuiibox.png differ diff --git a/16/umbraco-cms/.gitbook/assets/welcome-da (1).png b/16/umbraco-cms/.gitbook/assets/welcome-da (1).png new file mode 100644 index 00000000000..58bd8bda153 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/welcome-da (1).png differ diff --git a/16/umbraco-cms/.gitbook/assets/welcome-da.png b/16/umbraco-cms/.gitbook/assets/welcome-da.png new file mode 100644 index 00000000000..58bd8bda153 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/welcome-da.png differ diff --git a/16/umbraco-cms/.gitbook/assets/welcome-eng (1).png b/16/umbraco-cms/.gitbook/assets/welcome-eng (1).png new file mode 100644 index 00000000000..d1b3548aea5 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/welcome-eng (1).png differ diff --git a/16/umbraco-cms/.gitbook/assets/welcome-eng.png b/16/umbraco-cms/.gitbook/assets/welcome-eng.png new file mode 100644 index 00000000000..d1b3548aea5 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/welcome-eng.png differ diff --git a/16/umbraco-cms/.gitbook/assets/welcome-hq.png b/16/umbraco-cms/.gitbook/assets/welcome-hq.png new file mode 100644 index 00000000000..728eb2f509a Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/welcome-hq.png differ diff --git a/16/umbraco-cms/.gitbook/assets/welcome-umb-user.png b/16/umbraco-cms/.gitbook/assets/welcome-umb-user.png new file mode 100644 index 00000000000..e57be41a2f2 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/welcome-umb-user.png differ diff --git a/16/umbraco-cms/.gitbook/assets/welcome-user-uuibox.png b/16/umbraco-cms/.gitbook/assets/welcome-user-uuibox.png new file mode 100644 index 00000000000..67197643bf4 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/welcome-user-uuibox.png differ diff --git a/16/umbraco-cms/.gitbook/assets/welcomehpver.png b/16/umbraco-cms/.gitbook/assets/welcomehpver.png new file mode 100644 index 00000000000..b6508b176f6 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/welcomehpver.png differ diff --git a/16/umbraco-cms/.gitbook/assets/welcomemessage-v8 (1).png b/16/umbraco-cms/.gitbook/assets/welcomemessage-v8 (1).png new file mode 100644 index 00000000000..d16deaf2b43 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/welcomemessage-v8 (1).png differ diff --git a/16/umbraco-cms/.gitbook/assets/welcomemessage-v8 (2).png b/16/umbraco-cms/.gitbook/assets/welcomemessage-v8 (2).png new file mode 100644 index 00000000000..d16deaf2b43 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/welcomemessage-v8 (2).png differ diff --git a/16/umbraco-cms/.gitbook/assets/welcomemessage-v8.png b/16/umbraco-cms/.gitbook/assets/welcomemessage-v8.png new file mode 100644 index 00000000000..d16deaf2b43 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/welcomemessage-v8.png differ diff --git a/16/umbraco-cms/.gitbook/assets/welcomemessagewithstyles-v10 (1).png b/16/umbraco-cms/.gitbook/assets/welcomemessagewithstyles-v10 (1).png new file mode 100644 index 00000000000..1664c32e550 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/welcomemessagewithstyles-v10 (1).png differ diff --git a/16/umbraco-cms/.gitbook/assets/welcomemessagewithstyles-v10 (2).png b/16/umbraco-cms/.gitbook/assets/welcomemessagewithstyles-v10 (2).png new file mode 100644 index 00000000000..1664c32e550 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/welcomemessagewithstyles-v10 (2).png differ diff --git a/16/umbraco-cms/.gitbook/assets/welcomemessagewithstyles-v10.png b/16/umbraco-cms/.gitbook/assets/welcomemessagewithstyles-v10.png new file mode 100644 index 00000000000..1664c32e550 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/welcomemessagewithstyles-v10.png differ diff --git a/16/umbraco-cms/.gitbook/assets/whatisadashboard-v10 (1) (1).jpg b/16/umbraco-cms/.gitbook/assets/whatisadashboard-v10 (1) (1).jpg new file mode 100644 index 00000000000..87e55037545 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/whatisadashboard-v10 (1) (1).jpg differ diff --git a/16/umbraco-cms/.gitbook/assets/whatisadashboard-v10 (1) (2).jpg b/16/umbraco-cms/.gitbook/assets/whatisadashboard-v10 (1) (2).jpg new file mode 100644 index 00000000000..87e55037545 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/whatisadashboard-v10 (1) (2).jpg differ diff --git a/16/umbraco-cms/.gitbook/assets/whatisadashboard-v10 (1) (3).jpg b/16/umbraco-cms/.gitbook/assets/whatisadashboard-v10 (1) (3).jpg new file mode 100644 index 00000000000..87e55037545 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/whatisadashboard-v10 (1) (3).jpg differ diff --git a/16/umbraco-cms/.gitbook/assets/whatisadashboard-v10 (1).jpg b/16/umbraco-cms/.gitbook/assets/whatisadashboard-v10 (1).jpg new file mode 100644 index 00000000000..87e55037545 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/whatisadashboard-v10 (1).jpg differ diff --git a/16/umbraco-cms/.gitbook/assets/workspace-actions.svg b/16/umbraco-cms/.gitbook/assets/workspace-actions.svg new file mode 100644 index 00000000000..7ddeb6d9f35 --- /dev/null +++ b/16/umbraco-cms/.gitbook/assets/workspace-actions.svg @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/16/umbraco-cms/.gitbook/assets/workspace-view-example.png b/16/umbraco-cms/.gitbook/assets/workspace-view-example.png new file mode 100644 index 00000000000..4190a1bc7d7 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/workspace-view-example.png differ diff --git a/16/umbraco-cms/.gitbook/assets/workspace-views.svg b/16/umbraco-cms/.gitbook/assets/workspace-views.svg new file mode 100644 index 00000000000..0c04fed189f --- /dev/null +++ b/16/umbraco-cms/.gitbook/assets/workspace-views.svg @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/16/umbraco-cms/.gitbook/assets/workspace.svg b/16/umbraco-cms/.gitbook/assets/workspace.svg new file mode 100644 index 00000000000..a897bd62814 --- /dev/null +++ b/16/umbraco-cms/.gitbook/assets/workspace.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/16/umbraco-cms/.gitbook/assets/zapier-image.png b/16/umbraco-cms/.gitbook/assets/zapier-image.png new file mode 100644 index 00000000000..0589efbfe34 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/zapier-image.png differ diff --git a/16/umbraco-cms/.gitbook/assets/zip-package-v9.png b/16/umbraco-cms/.gitbook/assets/zip-package-v9.png new file mode 100644 index 00000000000..169bbfb1591 Binary files /dev/null and b/16/umbraco-cms/.gitbook/assets/zip-package-v9.png differ diff --git a/16/umbraco-cms/.gitbook/includes/obsolete-warning-ipublishedsnapshotaccessor.md b/16/umbraco-cms/.gitbook/includes/obsolete-warning-ipublishedsnapshotaccessor.md new file mode 100644 index 00000000000..8c5aab07201 --- /dev/null +++ b/16/umbraco-cms/.gitbook/includes/obsolete-warning-ipublishedsnapshotaccessor.md @@ -0,0 +1,7 @@ +--- +title: 'Obsolete Warning: IPublishedSnapshotAccessor' +--- + +{% hint style="warning" %} +The following example uses `IPublishedSnapshotAccessor`, which is obsolete in Umbraco 15 and will be removed in a future version. For more information, see the [Version specific upgrades](../../fundamentals/setup/upgrading/version-specific/#umbraco-15) article. +{% endhint %} diff --git a/16/umbraco-cms/.gitbook/includes/obsolete-warning-publishedsnapshot.md b/16/umbraco-cms/.gitbook/includes/obsolete-warning-publishedsnapshot.md new file mode 100644 index 00000000000..2597cee8417 --- /dev/null +++ b/16/umbraco-cms/.gitbook/includes/obsolete-warning-publishedsnapshot.md @@ -0,0 +1,7 @@ +--- +title: 'Obsolete Warning: PublishedSnapshot' +--- + +{% hint style="warning" %} +The following example uses `PublishedSnapshot`, which is obsolete in Umbraco 15 and will be removed in a future version. For more information, see the [Version specific upgrades](../../fundamentals/setup/upgrading/version-specific/#umbraco-15) article. +{% endhint %} diff --git a/16/umbraco-cms/.gitbook/includes/obsolete-warning-snapshot-publishedcache.md b/16/umbraco-cms/.gitbook/includes/obsolete-warning-snapshot-publishedcache.md new file mode 100644 index 00000000000..92b5a44abca --- /dev/null +++ b/16/umbraco-cms/.gitbook/includes/obsolete-warning-snapshot-publishedcache.md @@ -0,0 +1,7 @@ +--- +title: 'Obsolete Warning: Snapshot and Published Cache' +--- + +{% hint style="warning" %} +The following example uses `Umbraco.Cms.Core.PublishedCache` and `IPublishedSnapshotAccessor` which are obsolete in Umbraco 15 and will be removed in a future version. For more information, see the [Version specific upgrades](../../fundamentals/setup/upgrading/version-specific/#umbraco-15) article. +{% endhint %} diff --git a/16/umbraco-cms/.gitbook/includes/obsolete-warning-snapshot.md b/16/umbraco-cms/.gitbook/includes/obsolete-warning-snapshot.md new file mode 100644 index 00000000000..0262b908421 --- /dev/null +++ b/16/umbraco-cms/.gitbook/includes/obsolete-warning-snapshot.md @@ -0,0 +1,7 @@ +--- +title: 'Obsolete Warning: Snapshot' +--- + +{% hint style="warning" %} +The following example uses `Umbraco.Cms.Core.PublishedCache`, `IPublishedSnapshotAccessor`, and `PropertyCacheLevel.Snapshot`, which are obsolete in Umbraco 15 and will be removed in a future version. For more information, see the [Version specific upgrades](../../fundamentals/setup/upgrading/version-specific/#umbraco-15) article. +{% endhint %} diff --git a/16/umbraco-cms/.gitbook/includes/umbraco-extending-the-backoffice-training-course.md b/16/umbraco-cms/.gitbook/includes/umbraco-extending-the-backoffice-training-course.md new file mode 100644 index 00000000000..270412ffdb8 --- /dev/null +++ b/16/umbraco-cms/.gitbook/includes/umbraco-extending-the-backoffice-training-course.md @@ -0,0 +1,7 @@ +--- +title: Umbraco Extending the Backoffice Training Course +--- + +Umbraco HQ offers a training course covering extending and customizing the Umbraco Backoffice to enhance the editing experience. The course targets frontend and backend developers familiar with Umbraco who want to extend the UI for editors. + +[Explore Extending the Backoffice Training Course](https://umbraco.com/training/course-details/extending-the-backoffice-details/) to learn more about the topics covered and how it can enhance your Umbraco development skills. diff --git a/16/umbraco-cms/.gitbook/includes/umbraco-fundamentals-training-course.md b/16/umbraco-cms/.gitbook/includes/umbraco-fundamentals-training-course.md new file mode 100644 index 00000000000..f5bab081116 --- /dev/null +++ b/16/umbraco-cms/.gitbook/includes/umbraco-fundamentals-training-course.md @@ -0,0 +1,7 @@ +--- +title: Umbraco Fundamentals Training Course +--- + +Umbraco HQ offers a training course covering the basic concepts and features needed for building an Umbraco CMS website. The course targets frontend and backend developers, designers, and technical users who want to build a website from scratch in Umbraco, + +[Explore the Fundamentals Training Course](https://umbraco.com/training/course-details/fundamentals-details/) to learn more about the topics covered and how they can enhance your Umbraco development skills. diff --git a/16/umbraco-cms/.gitbook/includes/umbraco-load-balancing-training-course.md b/16/umbraco-cms/.gitbook/includes/umbraco-load-balancing-training-course.md new file mode 100644 index 00000000000..3b68d80560d --- /dev/null +++ b/16/umbraco-cms/.gitbook/includes/umbraco-load-balancing-training-course.md @@ -0,0 +1,7 @@ +--- +title: Umbraco Load Balancing Training Course +--- + +Umbraco HQ offers a training course covering the basics of working with Umbraco CMS in a load-balanced setup. The course targets backend developers and operations engineers. + +[Explore the Load Balancing Training Course](https://umbraco.com/training/course-details/load-balancing-and-azure/) to learn more about the topics covered and get more details about the course. diff --git a/16/umbraco-cms/.gitbook/includes/umbraco-mvc-training-course.md b/16/umbraco-cms/.gitbook/includes/umbraco-mvc-training-course.md new file mode 100644 index 00000000000..4c49e206302 --- /dev/null +++ b/16/umbraco-cms/.gitbook/includes/umbraco-mvc-training-course.md @@ -0,0 +1,7 @@ +--- +title: Umbraco MVC Training Course +--- + +Umbraco HQ offers a training course covering everything you need to know about working with MVC in an Umbraco context. The course targets anyone who's interested in learning how to utilize Umbraco´s built-in features and basic functionality with standard MVC. + +[Explore the MVC course](https://umbraco.com/training/course-details/mvc-and-umbraco/) to learn more about the topics covered and how it can enhance your Umbraco development skills. diff --git a/16/umbraco-cms/.gitbook/includes/umbraco-searching-and-indexing-training-course.md b/16/umbraco-cms/.gitbook/includes/umbraco-searching-and-indexing-training-course.md new file mode 100644 index 00000000000..238eb1f60b3 --- /dev/null +++ b/16/umbraco-cms/.gitbook/includes/umbraco-searching-and-indexing-training-course.md @@ -0,0 +1,7 @@ +--- +title: Umbraco Searching and Indexing Training Course +--- + +Umbraco HQ offers a training course covering the inner workings of Examine and Lucene, query debugging, and knowledge of the search tool. The course targets backend ASP.NET MVC developers who need to build real-world search applications with Umbraco. + +[Explore the Searching and Indexing with Examine Training Course](https://umbraco.com/training/course-details/searching-and-indexing/) to learn more about the topics covered and how it can enhance your Umbraco development skills. diff --git a/16/umbraco-cms/README.md b/16/umbraco-cms/README.md new file mode 100644 index 00000000000..c67e5b57aac --- /dev/null +++ b/16/umbraco-cms/README.md @@ -0,0 +1,50 @@ +--- +description: Your main resource when building and managing an Umbraco CMS website. +cover: .gitbook/assets/Umbraco 15 - Release - Hero_1665x438px.jpg +coverY: 0 +layout: + cover: + visible: true + size: full + title: + visible: true + description: + visible: true + tableOfContents: + visible: true + outline: + visible: true + pagination: + visible: true +--- + +# Umbraco CMS Documentation + +Umbraco CMS is a flexible and editor-friendly Content Management System (CMS) that allows you to create beautiful and modern websites. Use the latest version of .NET, integrate with your favorite services, and help your customers launch a website tailored to their specific needs. + +Learn more about Umbraco CMS and get an overview of the top features on [Umbraco.com](https://umbraco.com/products/umbraco-cms/). + +
FundamentalsLearn the basics of working with Umbraco CMS. How to install and setup your first site is also included in this section.Documentations Icons_Umbraco_CMS_Fundamentals.pngget-to-know-umbraco.md
Install Umbraco CMSReady to get started with Umbraco? Head over to the Setup section to learn how to install Umbraco CMS.Documentations Icons_Umbraco_CMS_Install.pnginstall
TutorialsFind detailed step-by-step guides on everything from building a site from scratch to implementing a custom maintenance page.Documentations Icons_Umbraco_CMS_Tutorials.pngoverview.md
+ +The documentation for Umbraco CMS provides information for experienced Umbraco and .NET developers. It also offers guides and high-level articles for people starting out with the CMS. + +{% content-ref url="tutorials/creating-a-basic-website/" %} +[creating-a-basic-website](tutorials/creating-a-basic-website/) +{% endcontent-ref %} + +{% content-ref url="reference/configuration/" %} +[configuration](reference/configuration/) +{% endcontent-ref %} + +{% content-ref url="fundamentals/setup/requirements.md" %} +[requirements.md](fundamentals/setup/requirements.md) +{% endcontent-ref %} + +{% content-ref url="reference/notifications/" %} +[notifications](reference/notifications/) +{% endcontent-ref %} + +*** + +{% include ".gitbook/includes/umbraco-fundamentals-training-course.md" %} + diff --git a/16/umbraco-cms/SUMMARY.md b/16/umbraco-cms/SUMMARY.md new file mode 100644 index 00000000000..af4a95a927e --- /dev/null +++ b/16/umbraco-cms/SUMMARY.md @@ -0,0 +1,506 @@ +# Table of contents + +* [Umbraco CMS Documentation](README.md) +* [Release Candidate Guide](release-candidate-guide.md) +* [Legacy Documentation](legacy-documentation/README.md) + * [Our Umbraco](https://our.umbraco.com/documentation/) + * [GitHub](https://github.com/umbraco/UmbracoDocs/tree/umbraco-eol-versions) +* [Release Notes](https://our.umbraco.com/download/releases/) +* [Contribute](https://docs.umbraco.com/welcome/contribute/) +* [Sustainability Best Practices](https://docs.umbraco.com/sustainability-best-practices/) + +## Fundamentals + +* [Get to know Umbraco](fundamentals/get-to-know-umbraco.md) +* [Setup](fundamentals/setup/README.md) + * [Requirements](fundamentals/setup/requirements.md) + * [Installation](fundamentals/setup/install/README.md) + * [Install using .NET CLI](fundamentals/setup/install/install-umbraco-with-templates.md) + * [Running Umbraco in Docker using Docker Compose](fundamentals/setup/install/running-umbraco-on-docker-locally.md) + * [Install using Visual Studio](fundamentals/setup/install/visual-studio.md) + * [Local IIS With Umbraco](fundamentals/setup/install/iis.md) + * [Install using Visual Studio Code](fundamentals/setup/install/install-umbraco-with-vs-code.md) + * [Installing Nightly Builds](fundamentals/setup/install/installing-nightly-builds.md) + * [Running Umbraco on Linux/macOS](fundamentals/setup/install/running-umbraco-on-linux-macos.md) + * [Unattended Installs](fundamentals/setup/install/unattended-install.md) + * [Upgrade your project](fundamentals/setup/upgrading/README.md) + * [Version Specific Upgrades](fundamentals/setup/upgrading/version-specific/README.md) + * [Upgrade from Umbraco 8 to the latest version](fundamentals/setup/upgrading/version-specific/upgrade-from-8-to-latest.md) + * [Migrate content to Umbraco 15](fundamentals/setup/upgrading/version-specific/migrate-content-to-umbraco-15.md) + * [Migrate content to Umbraco 8](fundamentals/setup/upgrading/version-specific/migrate-content-to-umbraco-8.md) + * [Minor upgrades for Umbraco 8](fundamentals/setup/upgrading/version-specific/minor-upgrades-for-umbraco-8.md) + * [Upgrade to Umbraco 7](fundamentals/setup/upgrading/version-specific/upgrade-to-umbraco-7.md) + * [Minor upgrades for Umbraco 7](fundamentals/setup/upgrading/version-specific/minor-upgrades-for-umbraco-7.md) + * [Server setup](fundamentals/setup/server-setup/README.md) + * [Running Umbraco On Azure Web Apps](fundamentals/setup/server-setup/azure-web-apps.md) + * [Hosting Umbraco in IIS](fundamentals/setup/server-setup/iis.md) + * [File And Folder Permissions](fundamentals/setup/server-setup/permissions.md) + * [Runtime Modes](fundamentals/setup/server-setup/runtime-modes.md) + * [Umbraco in Load Balanced Environments](fundamentals/setup/server-setup/load-balancing/README.md) + * [Load Balancing Azure Web Apps](fundamentals/setup/server-setup/load-balancing/azure-web-apps.md) + * [Standalone File System](fundamentals/setup/server-setup/load-balancing/file-system-replication.md) + * [Advanced Techniques With Flexible Load Balancing](fundamentals/setup/server-setup/load-balancing/flexible-advanced.md) + * [Logging With Load Balancing](fundamentals/setup/server-setup/load-balancing/logging.md) +* [Backoffice](fundamentals/backoffice/README.md) + * [Sections](fundamentals/backoffice/sections.md) + * [Property Editors](fundamentals/backoffice/property-editors/README.md) + * [Built-in Property Editors](fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/README.md) + * [Checkbox List](fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/checkbox-list.md) + * [Collection](fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/collection.md) + * [Color Picker](fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/color-picker.md) + * [Content Picker](fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/content-picker.md) + * [Document Picker](fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/document-picker.md) + * [DateTime](fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/date-time.md) + * [Date](fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/date.md) + * [Decimal](fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/decimal.md) + * [Email Address](fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/email-address.md) + * [Eye Dropper Color Picker](fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/eye-dropper-color-picker.md) + * [File Upload](fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/file-upload.md) + * [Image Cropper](fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/image-cropper.md) + * [Label](fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/label.md) + * [Markdown Editor](fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/markdown-editor.md) + * [Media Picker](fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/media-picker-3.md) + * [Member Group Picker](fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/member-group-picker.md) + * [Member Picker](fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/member-picker.md) + * [Multi Url Picker](fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/multi-url-picker.md) + * [Repeatable Textstrings](fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/multiple-textbox.md) + * [Numeric](fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/numeric.md) + * [Radiobutton List](fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/radiobutton-list.md) + * [Slider](fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/slider.md) + * [Tags](fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/tags.md) + * [Textarea](fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/textarea.md) + * [Textbox](fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/textbox.md) + * [Toggle](fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/true-false.md) + * [User Picker](fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/user-picker.md) + * [Block Editors](fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/block-editor/README.md) + * [Block Grid](fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/block-editor/block-grid-editor.md) + * [Block List](fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/block-editor/block-list-editor.md) + * [Block Level Variance](fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/block-editor/block-level-variance.md) + * [Dropdown](fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/dropdown/README.md) + * [Rich Text Editor](fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor/README.md) + * [Configuration](fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor/configuration.md) + * [Extensions](fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor/extensions.md) + * [Blocks](fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor/blocks.md) + * [Change Rich Text Editor UI](fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor/change-rich-text-editor-ui.md) + * [Rich Text Editor TinyMce](fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor-tinymce/README.md) + * [Configuration](fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor-tinymce/configuration.md) + * [Styles](fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor-tinymce/styles.md) + * [Plugins](fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor-tinymce/plugins.md) + * [Blocks](fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor-tinymce/blocks.md) + * [Login](fundamentals/backoffice/login.md) + * [Document Blueprints](fundamentals/backoffice/document-blueprints.md) + * [Sidebar](fundamentals/backoffice/sidebar.md) + * [Log Viewer](fundamentals/backoffice/logviewer.md) + * [Language Variants](fundamentals/backoffice/variants.md) + * [Settings Dashboards](fundamentals/backoffice/settings-dashboards.md) +* [Data](fundamentals/data/README.md) + * [Defining Content](fundamentals/data/defining-content/README.md) + * [Default Document Types](fundamentals/data/defining-content/default-document-types.md) + * [Document Type Localization](fundamentals/data/defining-content/document-type-localization.md) + * [Creating Media](fundamentals/data/creating-media/README.md) + * [Default Data/Media Types](fundamentals/data/creating-media/default-media-types.md) + * [Members](fundamentals/data/members.md) + * [Data Types](fundamentals/data/data-types/README.md) + * [Default Data Types](fundamentals/data/data-types/default-data-types.md) + * [Scheduled Publishing](fundamentals/data/scheduled-publishing.md) + * [Using Tabs](fundamentals/data/adding-tabs.md) + * [Users](fundamentals/data/users/README.md) + * [API Users](fundamentals/data/users/api-users.md) + * [Relations](fundamentals/data/relations.md) + * [Dictionary Items](fundamentals/data/dictionary-items.md) + * [Content Version Cleanup](fundamentals/data/content-version-cleanup.md) +* [Design](fundamentals/design/README.md) + * [Templates](fundamentals/design/templates/README.md) + * [Basic Razor Syntax](fundamentals/design/templates/basic-razor-syntax.md) + * [Razor Cheatsheet](fundamentals/design/templates/razor-cheatsheet.md) + * [Rendering Content](fundamentals/design/rendering-content.md) + * [Rendering Media](fundamentals/design/rendering-media.md) + * [Partial Views](fundamentals/design/partial-views.md) + * [Stylesheets And JavaScript](fundamentals/design/stylesheets-javascript.md) +* [Code](fundamentals/code/README.md) + * [Service APIs](fundamentals/code/umbraco-services.md) + * [Subscribing To Notifications](fundamentals/code/subscribing-to-notifications.md) + * [Creating Forms](fundamentals/code/creating-forms.md) + * [Debugging](fundamentals/code/debugging/README.md) + * [Logging](fundamentals/code/debugging/logging.md) + * [Source Control](fundamentals/code/source-control.md) + +## Implementation + +* [Learn how Umbraco works](implementation/learn-how-umbraco-works.md) +* [Routing](implementation/default-routing/README.md) + * [Controller & Action Selection](implementation/default-routing/controller-selection.md) + * [Execute Request](implementation/default-routing/execute-request.md) + * [Request Pipeline](implementation/default-routing/inbound-pipeline.md) +* [Custom Routing](implementation/custom-routing/README.md) + * [Adding a hub with SignalR and Umbraco](implementation/custom-routing/signalR.md) +* [Controllers](implementation/controllers.md) +* [Data Persistence (CRUD)](implementation/data-persistence.md) +* [Composing](implementation/composing.md) +* [Integration Testing](implementation/integration-testing.md) +* [Nullable Reference Types](implementation/nullable-reference-types.md) +* [Services and Helpers](implementation/services/README.md) + * [Circular Dependencies](implementation/services/circular-dependencies.md) +* [Unit Testing](implementation/unit-testing.md) + +## Customizing + +* [Extend and customize the editing experience](customizing/overview.md) +* [Project Bellissima](customizing/project-bellissima.md) +* [Setup Your Development Environment](customizing/development-flow/README.md) + * [TypeScript setup](customizing/development-flow/typescript-setup.md) + * [Vite Package Setup](customizing/development-flow/vite-package-setup.md) +* [Extension Overview](customizing/extending-overview/README.md) + * [Extension Registry](customizing/extending-overview/extension-registry/README.md) + * [Extension Registration](customizing/extending-overview/extension-registry/extension-registry.md) + * [Extension Manifest](customizing/extending-overview/extension-registry/extension-manifest.md) + * [Replace, Exclude or Unregister](customizing/extending-overview/extension-registry/replace-exclude-or-unregister.md) + * [Extension Types](customizing/extending-overview/extension-types/README.md) + * [Sections](customizing/extending-overview/extension-types/sections/README.md) + * [Sections](customizing/extending-overview/extension-types/sections/section.md) + * [Section Sidebar](customizing/extending-overview/extension-types/sections/section-sidebar.md) + * [Section View](customizing/extending-overview/extension-types/sections/section-view.md) + * [Workspaces](customizing/extending-overview/extension-types/workspaces/README.md) + * [Workspace Actions](customizing/extending-overview/extension-types/workspaces/workspace-editor-actions.md) + * [Workspace Context](customizing/extending-overview/extension-types/workspaces/workspace-context.md) + * [Workspace Views](customizing/extending-overview/extension-types/workspaces/workspace-views.md) + * [Route Registration](customizing/extending-overview/extension-types/modals/route-registration.md) + * [Menu](customizing/extending-overview/extension-types/menu.md) + * [Header Apps](customizing/extending-overview/extension-types/header-apps.md) + * [Icons](customizing/extending-overview/extension-types/icons.md) + * [Block Custom View](customizing/extending-overview/extension-types/block-custom-view.md) + * [Bundle](customizing/extending-overview/extension-types/bundle.md) + * [Kind](customizing/extending-overview/extension-types/kind.md) + * [App Entry Point](customizing/extending-overview/extension-types/app-entry-point.md) + * [Backoffice Entry Point](customizing/extending-overview/extension-types/backoffice-entry-point.md) + * [Extension Conditions](customizing/extending-overview/extension-types/condition.md) + * [Dashboards](customizing/extending-overview/extension-types/dashboard.md) + * [Entity Actions](customizing/extending-overview/extension-types/entity-actions.md) + * [Entity Bulk Actions](customizing/extending-overview/extension-types/entity-bulk-actions.md) + * [Entity Create Option Action](customizing/extending-overview/extension-types/entity-create-option-action.md) + * [Property Value Preset](customizing/extending-overview/extension-types/property-value-preset.md) + * [Trees](customizing/extending-overview/extension-types/tree.md) + * [Global Context](customizing/extending-overview/extension-types/global-context.md) + * [Localization](customizing/extending-overview/extension-types/localization.md) + * [Modals](customizing/extending-overview/extension-types/modals/README.md) + * [Confirm Dialog](customizing/extending-overview/extension-types/modals/confirm-dialog.md) + * [Custom Modals](customizing/extending-overview/extension-types/modals/custom-modals.md) + * [Extension Kind](customizing/extending-overview/extension-kind.md) + * [Extension Conditions](customizing/extending-overview/extension-conditions.md) + * [Custom Extension types](customizing/extending-overview/custom-extension-type.md) +* [Foundation](customizing/foundation/README.md) + * [Working with Data](customizing/foundation/working-with-data/README.md) + * [Repositories](customizing/foundation/working-with-data/repositories.md) + * [Context API](customizing/foundation/working-with-data/context-api.md) + * [Store](customizing/foundation/working-with-data/store.md) + * [States](customizing/foundation/working-with-data/states.md) + * [Contexts](customizing/foundation/contexts/README.md) + * [Property Dataset Context](customizing/foundation/contexts/property-dataset-context.md) + * [Umbraco Element](customizing/foundation/umbraco-element/README.md) + * [Controllers](customizing/foundation/umbraco-element/controllers/README.md) + * [Write your own controller](customizing/foundation/umbraco-element/controllers/write-your-own-controller.md) + * [Sorting](customizing/foundation/sorting.md) + * [Routes](customizing/foundation/routes.md) + * [Icons](customizing/foundation/icons.md) + * [Backoffice Localization](customizing/foundation/localization.md) + * [Terminology](customizing/foundation/terminology.md) +* [Sections & Trees](customizing/section-trees.md) +* [Searchable Trees (ISearchableTree)](customizing/searchable-trees.md) +* [Property Editors](customizing/property-editors/README.md) + * [Property Editors Composition](customizing/property-editors/composition/README.md) + * [Property Editor Schema](customizing/property-editors/composition/property-editor-schema.md) + * [Property Editor UI](customizing/property-editors/composition/property-editor-ui.md) + * [Property Value Converters](customizing/property-editors/property-value-converters.md) + * [Property Actions](customizing/property-editors/property-actions.md) + * [Integrate Property Editors](customizing/property-editors/integrate-property-editors.md) + * [Tracking References](customizing/property-editors/tracking.md) + * [Content Picker Value Converter Example](customizing/property-editors/full-examples-value-converters.md) + * [Property Dataset](customizing/property-editors/property-dataset.md) + * [Integrate Validation](customizing/property-editors/integrate-validation.md) +* [Workspaces](customizing/workspaces.md) +* [Umbraco Package](customizing/umbraco-package.md) +* [UI Library](customizing/ui-library.md) + +## Extending + +* [Build on Umbraco functionality](extending/build-on-umbraco-functionality.md) +* [Health Check](extending/health-check/README.md) + * [Health Check Guides](extending/health-check/guides/README.md) + * [Click-Jacking Protection](extending/health-check/guides/clickjackingprotection.md) + * [Content Content Security Policy (CSP)](extending/health-check/guides/contentsecuritypolicy.md) + * [Content/MIME Sniffing Protection](extending/health-check/guides/contentsniffingprotection.md) + * [Cross-site scripting Protection (X-XSS-Protection header)](extending/health-check/guides/crosssitescriptingprotection.md) + * [Debug Compilation Mode](extending/health-check/guides/debugcompilationmode.md) + * [Excessive Headers](extending/health-check/guides/excessiveheaders.md) + * [Fixed Application Url](extending/health-check/guides/fixedapplicationurl.md) + * [Folder & File Permissions](extending/health-check/guides/folderandfilepermissions.md) + * [HTTPS Configuration](extending/health-check/guides/httpsconfiguration.md) + * [Notification Email Settings](extending/health-check/guides/notificationemail.md) + * [SMTP](extending/health-check/guides/smtp.md) + * [Strict-Transport-Security Header](extending/health-check/guides/stricttransportsecurityheader.md) +* [Language Files & Localization](extending/language-files/README.md) + * [.NET Localization](extending/language-files/net-localization.md) +* [Backoffice Search](extending/backoffice-search.md) +* [Creating a Custom Database Table](extending/database.md) +* [Creating a Custom Seed Key Provider](extending/creating-custom-seed-key-provider.md) +* [Embedded Media Providers](extending/embedded-media-providers.md) +* [Custom File Systems (IFileSystem)](extending/filesystemproviders/README.md) + * [Using Azure Blob Storage for Media and ImageSharp Cache](extending/filesystemproviders/azure-blob-storage.md) +* [Configuring Azure Key Vault](extending/key-vault.md) +* [Packages](extending/packages/README.md) + * [Creating a Package](extending/packages/creating-a-package.md) + * [Language file for packages](extending/packages/language-files-for-packages.md) + * [Listing a Package on the Umbraco Marketplace](extending/packages/listing-on-marketplace.md) + * [Good practice and defaults](extending/packages/good-practice-and-defaults.md) + * [Packages on Umbraco Cloud](extending/packages/packages-on-umbraco-cloud.md) + * [Installing and Uninstalling Packages](extending/packages/installing-and-uninstalling-packages.md) + * [Maintaining packages](extending/packages/maintaining-packages.md) + * [Create accessible Umbraco packages](extending/packages/accessibility.md) + * [Example Package Repository](extending/packages/example-package-repository.md) + +## Reference + +* [Dive into the code](reference/dive-into-the-code.md) +* [Configuration](reference/configuration/README.md) + * [Basic Authentication Settings](reference/configuration/basicauthsettings.md) + * [Connection strings settings](reference/configuration/connectionstringssettings.md) + * [Content Dashboard Settings](reference/configuration/contentdashboard.md) + * [Content Settings](reference/configuration/contentsettings.md) + * [Data Types Settings](reference/configuration/datatypes.md) + * [Debug settings](reference/configuration/debugsettings.md) + * [Examine settings](reference/configuration/examinesettings.md) + * [Exception filter settings](reference/configuration/exceptionfiltersettings.md) + * [FileSystemProviders Configuration](reference/configuration/filesystemproviders.md) + * [Global Settings](reference/configuration/globalsettings.md) + * [Health checks](reference/configuration/healthchecks.md) + * [Hosting settings](reference/configuration/hostingsettings.md) + * [Imaging settings](reference/configuration/imagingsettings.md) + * [Indexing settings](reference/configuration/indexingsettings.md) + * [Install Default Data Settings](reference/configuration/installdefaultdatasettings.md) + * [Logging settings](reference/configuration/loggingsettings.md) + * [Maximum Upload Size Settings](reference/configuration/maximumuploadsizesettings.md) + * [Models builder settings](reference/configuration/modelsbuildersettings.md) + * [Cache Settings](reference/configuration/cache-settings.md) + * [Package Migration](reference/configuration/packagemigrationsettings.md) + * [Plugins settings](reference/configuration/pluginssettings.md) + * [Request handler settings](reference/configuration/requesthandlersettings.md) + * [Runtime settings](reference/configuration/runtimesettings.md) + * [Security Settings](reference/configuration/securitysettings.md) + * [Serilog settings](reference/configuration/serilog.md) + * [Type finder settings](reference/configuration/typefindersettings.md) + * [Unattended](reference/configuration/unattendedsettings.md) + * [Web routing](reference/configuration/webroutingsettings.md) +* [Templating](reference/templating/README.md) + * [Models Builder](reference/templating/modelsbuilder/README.md) + * [Introduction](reference/templating/modelsbuilder/introduction.md) + * [Configuration](reference/templating/modelsbuilder/configuration.md) + * [Builder Modes](reference/templating/modelsbuilder/builder-modes.md) + * [Understand and Extend](reference/templating/modelsbuilder/understand-and-extend.md) + * [Using Interfaces](reference/templating/modelsbuilder/using-interfaces.md) + * [Tips and Tricks](reference/templating/modelsbuilder/coolthingswithmodels.md) + * [Working with MVC](reference/templating/mvc/README.md) + * [Working with MVC Views in Umbraco](reference/templating/mvc/views.md) + * [View/Razor Examples](reference/templating/mvc/examples.md) + * [Using MVC Partial Views in Umbraco](reference/templating/mvc/partial-views.md) + * [Using View Components in Umbraco](reference/templating/mvc/viewcomponents.md) + * [Querying & Traversal](reference/templating/mvc/querying.md) + * [Creating Forms](reference/templating/mvc/forms.md) + * [Macros](reference/templating/macros.md) +* [Querying & Models](reference/querying/README.md) + * [IMemberManager](reference/querying/imembermanager.md) + * [IPublishedContentQuery](reference/querying/ipublishedcontentquery.md) + * [ITagQuery](reference/querying/itagquery.md) + * [UDI Identifiers](reference/querying/udi-identifiers.md) + * [UmbracoContext helper](reference/querying/umbraco-context.md) + * [UmbracoHelper](reference/querying/umbracohelper.md) + * [IPublishedContent](reference/querying/ipublishedcontent/README.md) + * [IPublishedContent Collections](reference/querying/ipublishedcontent/collections.md) + * [IPublishedContent IsHelpers](reference/querying/ipublishedcontent/ishelpers.md) + * [IPublishedContent Property Access & Extension Methods](reference/querying/ipublishedcontent/properties.md) +* [Routing & Controllers](reference/routing/README.md) + * [Custom MVC controllers (Umbraco Route Hijacking)](reference/routing/custom-controllers.md) + * [Custom MVC Routes](reference/routing/custom-routes.md) + * [Custom Middleware](reference/routing/custom-middleware.md) + * [URL Rewrites in Umbraco](reference/routing/iisrewriterules.md) + * [Special Property Type aliases for routing](reference/routing/routing-properties.md) + * [URL Redirect Management](reference/routing/url-tracking.md) + * [Routing in Umbraco](reference/routing/request-pipeline/README.md) + * [FindPublishedContentAndTemplate()](reference/routing/request-pipeline/find-publishedcontent-and-template.md) + * [IContentFinder](reference/routing/request-pipeline/icontentfinder.md) + * [Inbound request pipeline](reference/routing/request-pipeline/inbound-pipeline.md) + * [Outbound request pipeline](reference/routing/request-pipeline/outbound-pipeline.md) + * [Published Content Request Preparation](reference/routing/request-pipeline/published-content-request-preparation.md) + * [Surface controllers](reference/routing/surface-controllers/README.md) + * [Surface controller actions](reference/routing/surface-controllers/surface-controllers-actions.md) + * [Umbraco API Controllers](reference/routing/umbraco-api-controllers/README.md) + * [Porting old Umbraco API Controllers](reference/routing/umbraco-api-controllers/porting-old-umbraco-apis.md) +* [Content Delivery API](reference/content-delivery-api/README.md) + * [Custom property editors support](reference/content-delivery-api/custom-property-editors-support.md) + * [Extension API for querying](reference/content-delivery-api/extension-api-for-querying.md) + * [Media Delivery API](reference/content-delivery-api/media-delivery-api.md) + * [Protected content in the Delivery API](reference/content-delivery-api/protected-content-in-the-delivery-api/README.md) + * [Server to server access](reference/content-delivery-api/protected-content-in-the-delivery-api/server-to-server-access.md) + * [Output caching](reference/content-delivery-api/output-caching.md) + * [Property expansion and limiting](reference/content-delivery-api/property-expansion-and-limiting.md) + * [Additional preview environments support](reference/content-delivery-api/additional-preview-environments-support.md) +* [Webhooks](reference/webhooks/README.md) + * [Expanding Webhook Events](reference/webhooks/expanding-webhook-events.md) +* [API versioning and OpenAPI](reference/api-versioning-and-openapi.md) +* [Searching](reference/searching/README.md) + * [Examine](reference/searching/examine/README.md) + * [Examine Management](reference/searching/examine/examine-management.md) + * [Examine Manager](reference/searching/examine/examine-manager.md) + * [Custom indexing](reference/searching/examine/indexing.md) + * [PDF indexes and multisearchers](reference/searching/examine/pdfindex-multisearcher.md) + * [Quick-start](reference/searching/examine/quick-start.md) + * [Corrupt Indexes](reference/searching/examine/corrupt-indexes.md) +* [Using Notifications](reference/notifications/README.md) + * [Notification Handler](reference/notifications/notification-handler.md) + * [CacheRefresher Notifications Example](reference/notifications/cacherefresher-notifications.md) + * [ContentService Notifications Example](reference/notifications/contentservice-notifications.md) + * [Creating And Publishing Notifications](reference/notifications/creating-and-publishing-notifications.md) + * [Determining if an entity is new](reference/notifications/determining-new-entity.md) + * [MediaService Notifications Example](reference/notifications/mediaservice-notifications.md) + * [MemberService Notifications Example](reference/notifications/memberservice-notifications.md) + * [Sending Allowed Children Notification](reference/notifications/sendingallowedchildrennotifications.md) + * [Umbraco Application Lifetime Notifications](reference/notifications/umbracoapplicationlifetime-notifications.md) + * [EditorModel Notifications](reference/notifications/editormodel-notifications/README.md) + * [Customizing the "Links" box](reference/notifications/editormodel-notifications/customizing-the-links-box.md) + * [Hot vs. cold restarts](reference/notifications/hot-vs-cold-restarts.md) +* [Inversion of Control / Dependency injection](reference/using-ioc.md) +* [Management](reference/management/README.md) + * [Using Umbraco services](reference/management/using-services/README.md) + * [Consent Service](reference/management/using-services/consentservice.md) + * [Media Service](reference/management/using-services/mediaservice.md) + * [Relation Service](reference/management/using-services/relationservice.md) + * [Content Service](reference/management/using-services/contentservice.md) + * [Content Type Service](reference/management/using-services/contenttypeservice.md) + * [Localization Service](reference/management/using-services/localizationservice.md) + * [User Service](reference/management/using-services/userservice.md) +* [Plugins](reference/plugins/README.md) + * [Creating Resolvers](reference/plugins/creating-resolvers.md) + * [Finding types](reference/plugins/finding-types.md) +* [Cache & Distributed Cache](reference/cache/README.md) + * [Cache Seeding](reference/cache/cache-seeding.md) + * [Accessing the cache](reference/cache/application-cache.md) + * [ICacheRefresher](reference/cache/icacherefresher.md) + * [IServerMessenger](reference/cache/iservermessenger.md) + * [Getting/Adding/Updating/Inserting Into Cache](reference/cache/updating-cache.md) + * [Examples](reference/cache/examples/README.md) + * [Working with caching](reference/cache/examples/tags.md) +* [Response Caching](reference/response-caching.md) +* [Security](reference/security/README.md) + * [API rate limiting](reference/security/api-rate-limiting.md) + * [BackOfficeUserManager and Events](reference/security/backofficeusermanager-and-notifications.md) + * [Cookies](reference/security/cookies.md) + * [Replacing the basic username/password check](reference/security/custom-password-check.md) + * [External login providers](reference/security/external-login-providers.md) + * [Locking of Users and password reset](reference/security/password-reset.md) + * [Reset admin password](reference/security/reset-admin-password.md) + * [Umbraco Security Hardening](reference/security/security-hardening.md) + * [Umbraco Security Settings](reference/security/security-settings.md) + * [Sensitive data](reference/security/sensitive-data-on-members.md) + * [Sanitizing the Rich Text Editor](reference/security/serverside-sanitizing.md) + * [Setup Umbraco for a FIPS Compliant Server](reference/security/setup-umbraco-for-a-fips-server.md) + * [HTTPS](reference/security/ssl-https.md) + * [Two-factor Authentication](reference/security/two-factor-authentication.md) + * [Server-side file validation](reference/security/serverside-file-validation.md) +* [Scheduling](reference/scheduling.md) +* [Common Pitfalls & Anti-Patterns](reference/common-pitfalls.md) +* [API Documentation](reference/api-documentation.md) +* [Debugging with SourceLink](reference/debugging.md) +* [Language Variation](reference/language-variation.md) +* [UmbracoMapper](reference/mapping.md) +* [Distributed Locks](reference/distributed-locks.md) +* [Management API](reference/management-api/README.md) + * [External Access](reference/management-api/external-access.md) + * [Setup OAuth using Postman](reference/management-api/postman-setup-swagger.md) +* [Custom Swagger API](reference/custom-swagger-api.md) +* [Umbraco Flavored Markdown](reference/umbraco-flavored-markdown.md) +* [Content Type Filters](reference/content-type-filters.md) + +## Tutorials + +* [Overview](tutorials/overview.md) +* [Creating a Basic Website](tutorials/creating-a-basic-website/README.md) + * [Getting Started](tutorials/creating-a-basic-website/getting-started.md) + * [Document Types](tutorials/creating-a-basic-website/document-types.md) + * [Creating Your First Template](tutorials/creating-a-basic-website/creating-your-first-template-and-content-node.md) + * [CSS and Images](tutorials/creating-a-basic-website/css-and-images.md) + * [Displaying the Document Type Properties](tutorials/creating-a-basic-website/displaying-the-document-type-properties.md) + * [Creating a Master Template](tutorials/creating-a-basic-website/creating-master-template-part-1.md) + * [Creating Pages and Using the Master Template](tutorials/creating-a-basic-website/creating-master-template-part-2.md) + * [Setting the Navigation Menu](tutorials/creating-a-basic-website/setting-the-navigation-menu.md) + * [Articles and Article Items](tutorials/creating-a-basic-website/article-parent-and-article-items.md) + * [Adding Language Variants](tutorials/creating-a-basic-website/adding-language-variants.md) + * [Conclusions](tutorials/creating-a-basic-website/conclusion.md) +* [Creating your First Extension](tutorials/creating-your-first-extension.md) +* [Creating a Custom Dashboard](tutorials/creating-a-custom-dashboard/README.md) + * [Adding localization to the dashboard](tutorials/creating-a-custom-dashboard/adding-localization-to-the-dashboard.md) + * [Adding functionality to the Dashboard](tutorials/creating-a-custom-dashboard/adding-functionality-to-the-dashboard.md) + * [Using Umbraco UI library in the Dashboard](tutorials/creating-a-custom-dashboard/extending-the-dashboard-using-umbraco-ui-library.md) +* [Creating a Property Editor](tutorials/creating-a-property-editor/README.md) + * [Adding configuration to a Property Editor](tutorials/creating-a-property-editor/adding-configuration-to-a-property-editor.md) + * [Integrating context with a Property Editor](tutorials/creating-a-property-editor/integrating-context-with-a-property-editor.md) + * [Custom value conversion for rendering](tutorials/creating-a-property-editor/custom-value-conversion-for-rendering.md) + * [Adding server-side validation](tutorials/creating-a-property-editor/adding-server-side-validation.md) + * [Default Property Editor Schema aliases](tutorials/creating-a-property-editor/default-property-editor-schema-aliases.md) +* [Creating a Multilingual Site](tutorials/multilanguage-setup.md) +* [Add Google Authentication (Users)](tutorials/add-google-authentication.md) +* [Add Microsoft Entra ID authentication (Members)](tutorials/add-microsoft-entra-id-authentication.md) +* [Creating Custom Database Tables with Entity Framework](tutorials/getting-started-with-entity-framework-core.md) +* [The Starter Kit](tutorials/starter-kit/README.md) + * [Lessons](tutorials/starter-kit/lessons/README.md) + * [Customize the Starter Kit](tutorials/starter-kit/lessons/1-customize-the-starter-kit.md) + * [Add a Blog Post Publication Date](tutorials/starter-kit/lessons/2-add-a-blog-post-publication-date/README.md) + * [Add a Blog Post Publication Date](tutorials/starter-kit/lessons/2-add-a-blog-post-publication-date/part-2.md) + * [Add a Blog Post Publication Date](tutorials/starter-kit/lessons/2-add-a-blog-post-publication-date/part-3.md) + * [Add Open Graph](tutorials/starter-kit/lessons/3-add-open-graph/README.md) + * [Add Open Graph - Step 1](tutorials/starter-kit/lessons/3-add-open-graph/step-1.md) + * [Add Open Graph - Step 2](tutorials/starter-kit/lessons/3-add-open-graph/step-2.md) + * [Add Open Graph - Step 3](tutorials/starter-kit/lessons/3-add-open-graph/step-3.md) + * [Add Open Graph - Step 4](tutorials/starter-kit/lessons/3-add-open-graph/step-4.md) + * [Add Open Graph - Summary](tutorials/starter-kit/lessons/3-add-open-graph/summary.md) + * [Ask For Help and Join the Community](tutorials/starter-kit/lessons/4-help-and-community.md) +* [Editor's Manual](tutorials/editors-manual/README.md) + * [Getting Started](tutorials/editors-manual/getting-started-with-umbraco/README.md) + * [Logging In and Out](tutorials/editors-manual/getting-started-with-umbraco/logging-in-and-out.md) + * [Umbraco Interface](tutorials/editors-manual/getting-started-with-umbraco/umbraco-interface.md) + * [Creating, Saving and Publishing Content Options](tutorials/editors-manual/getting-started-with-umbraco/creating-saving-and-publishing-content.md) + * [Finding Content](tutorials/editors-manual/getting-started-with-umbraco/finding-content.md) + * [Editing Existing Content](tutorials/editors-manual/getting-started-with-umbraco/editing-existing-content.md) + * [Sorting Pages](tutorials/editors-manual/getting-started-with-umbraco/ordering-pages.md) + * [Moving a Page](tutorials/editors-manual/getting-started-with-umbraco/moving-a-page.md) + * [Copying a Page](tutorials/editors-manual/getting-started-with-umbraco/copying-a-page.md) + * [Deleting and Restoring Pages](tutorials/editors-manual/getting-started-with-umbraco/deleting-and-restoring-pages.md) + * [Working with Rich Text Editor](tutorials/editors-manual/working-with-content/README.md) + * [Version Management](tutorials/editors-manual/version-management/README.md) + * [Comparing Versions](tutorials/editors-manual/version-management/comparing-versions.md) + * [Rollback to a Previous Version](tutorials/editors-manual/version-management/rollback-to-a-previous-version.md) + * [Media Management](tutorials/editors-manual/media-management/README.md) + * [Working with Folders](tutorials/editors-manual/media-management/working-with-folders.md) + * [Working with Media Types](tutorials/editors-manual/media-management/working-with-images-and-files.md) + * [Cropping Images](tutorials/editors-manual/media-management/cropping-images.md) + * [Tips & Tricks](tutorials/editors-manual/tips-and-tricks/README.md) + * [Refreshing the Tree View](tutorials/editors-manual/tips-and-tricks/working-with-folders.md) + * [Audit Trail](tutorials/editors-manual/tips-and-tricks/audit-trail.md) + * [Notifications](tutorials/editors-manual/tips-and-tricks/notifications.md) + * [Preview Pane Responsive View](tutorials/editors-manual/tips-and-tricks/preview-pane-responsive-view.md) + * [Session Timeout](tutorials/editors-manual/tips-and-tricks/session-timeout.md) +* [Multisite Setup](tutorials/multisite-setup.md) +* [Member Registration and Login](tutorials/members-registration-and-login.md) +* [Custom Views for Block List](tutorials/creating-custom-views-for-blocklist.md) +* [Connecting Umbraco Forms and Zapier](tutorials/connecting-umbraco-forms-and-zapier.md) +* [Creating an XML Sitemap](tutorials/creating-an-xml-site-map.md) +* [Implement Custom Error Pages](tutorials/custom-error-page.md) +* [Create a custom maintenance page](tutorials/create-a-custom-maintenance-page.md) +* [Creating a backoffice API](tutorials/creating-a-backoffice-api/README.md) + * [Documenting your controllers](tutorials/creating-a-backoffice-api/documenting-your-controllers.md) + * [Adding a custom Swagger document](tutorials/creating-a-backoffice-api/adding-a-custom-swagger-document.md) + * [Versioning your API](tutorials/creating-a-backoffice-api/versioning-your-api.md) + * [Polymorphic output in the Management API](tutorials/creating-a-backoffice-api/polymorphic-output-in-the-management-api.md) + * [Umbraco schema and operation IDs](tutorials/creating-a-backoffice-api/umbraco-schema-and-operation-ids.md) + * [Access policies](tutorials/creating-a-backoffice-api/access-policies.md) +* [Extending the Help Menu](tutorials/extending-the-help-menu.md) +* [Downloadable content](tutorials/downloadable-content/README.md) + * [YouTube: Create a simple Umbraco Website](tutorials/downloadable-content/youtube-create-a-simple-umbraco-website.md) diff --git a/16/umbraco-cms/customizing/development-flow/README.md b/16/umbraco-cms/customizing/development-flow/README.md new file mode 100644 index 00000000000..4a3df0dc1fe --- /dev/null +++ b/16/umbraco-cms/customizing/development-flow/README.md @@ -0,0 +1,105 @@ +--- +description: Learn about the recommended development environment to extend Umbraco +--- + +# Setup Your Development Environment + +This article will take you through setting up everything you need to start building extensions and packages for Umbraco. + +## Required Software + +Make sure you have followed the [requirements](../../fundamentals/setup/requirements.md) article, especially having installed the following on your machine: +* [Node.js version 20.15.0 (LTS)](https://nodejs.org/en) and higher + +{% hint style="info" %} +Use Node Version Manager (NVM) for [Windows](https://github.com/coreybutler/nvm-windows) or [Mac/Linux](https://github.com/nvm-sh/nvm) to manage the Node.js versions. +{% endhint %} + +## Package Setup + +### App\_Plugins + +Extensions such as JavaScript, CSS, and manifests, will go into a folder called `App_Plugins`. If you do not have this folder, you can create it at the root of your Umbraco project. + +### Source Code + +The source code for your extensions should ideally be placed in a different project. You can make great use of a [Razor Class Library (RCL) with static assets](https://learn.microsoft.com/en-us/aspnet/core/razor-pages/ui-class?view=aspnetcore-8.0\&tabs=visual-studio#create-an-rcl-with-static-assets) for this purpose. This will make it easier to maintain and test your code. You can create a new project in the root of your Umbraco project, or you can create a new project in a separate folder. + +### Bundling + +If you are using a bundler like Webpack or Vite, you can configure it to output its files to a folder that Umbraco can see. If you have put your files directly in Umbraco project, you need to copy the compiled files over to the `App_Plugins` folder. + +With a Razor Class Library (RCL) project, you should instead configure your bundler to copy the files over to the `wwwroot` folder. You can then map your RCL project back to the `App_Plugins` web path, so Umbraco can read your files. You can do this by setting the `StaticWebAssetBasePath` in your `csproj` file: + +{% code title="MyExtension.csproj" lineNumbers="true" %} +```xml + + + + App_Plugins/MyExtension + + + +``` +{% endcode %} + +### Dependencies + +You can use any package manager you like to install dependencies. We recommend using NPM or Yarn. You can install packages by running the command: + +```bash +npm install -D +``` + +This will install the package and save it to your `package.json` file. + +You need to setup a `package.json` file if you don't have one already. You can do this by running the command: + +```bash +npm init -y +``` + +{% hint style="warning" %} +Make sure that you do not install any NPM dependencies directly into the `App_Plugins` folder. This can cause issues with Build and Publish processes in MSBuild. Always install dependencies in a separate folder and use a bundler to copy the compiled files over to the `App_Plugins` folder. +{% endhint %} + +### Umbraco Backoffice + +Umbraco publishes an NPM package called `@umbraco-cms/backoffice` that holds typings and other niceties to build extensions. + +You can install this package by running the command: + +```bash +npm install -D @umbraco-cms/backoffice +``` + +This will add a package to your devDependencies containing the TypeScript definitions for the Umbraco Backoffice. + +It is important that this namespace is ignored in your bundler. If you are using Vite, you can add the following to your `vite.config.ts` file: + +```ts +import { defineConfig } from "vite"; + +export default defineConfig({ + // other config + // ... + // add this to your config + build: { + rollupOptions: { + external: [/^@umbraco/], + }, + } +}); +``` + +This ensures that the Umbraco Backoffice package is not bundled with your package. + +Read more about using Vite with Umbraco in the [Vite Package Setup](vite-package-setup.md) article. + +## Visual Studio Code + +If you're using Visual Studio Code we recommend the extension called [Lit-Plugin](https://marketplace.visualstudio.com/items?itemName=runem.lit-plugin) to get IntelliSense for Lit Elements and Umbraco UI Library Components. + +## What's Next? + +Now that you have your development environment set up, you can start building your Umbraco extensions. Read more about [our recommended setup with Vite](vite-package-setup.md) to get started. diff --git a/16/umbraco-cms/customizing/development-flow/typescript-setup.md b/16/umbraco-cms/customizing/development-flow/typescript-setup.md new file mode 100644 index 00000000000..3113b51d9bf --- /dev/null +++ b/16/umbraco-cms/customizing/development-flow/typescript-setup.md @@ -0,0 +1,16 @@ +# TypeScript setup + +Make sure to configure your TypeScript compiler so it includes the Global Types from the Backoffice. This enables you to utilize the declared Extension Types. If your project is using other Packages that provides their Extension Types then please list these as well. + +In your `tsconfig.json` file. Add the array `types` inside `compilerOptions`, with the entry of `@umbraco-cms/backoffice/extension-types`: + +```json +{ + "compilerOptions": { + ... + "types": [ + "@umbraco-cms/backoffice/extension-types" + ] + } +} +``` diff --git a/16/umbraco-cms/customizing/development-flow/vite-package-setup.md b/16/umbraco-cms/customizing/development-flow/vite-package-setup.md new file mode 100644 index 00000000000..8c4097981a3 --- /dev/null +++ b/16/umbraco-cms/customizing/development-flow/vite-package-setup.md @@ -0,0 +1,223 @@ +--- +description: Get started with a Vite Package, setup with TypeScript and Lit +--- + +# Vite Package Setup + +Umbraco recommends building extensions with a setup using TypeScript and a build tool such as Vite. Umbraco uses the library Lit for building web components which we will use throughout this guide. + +{% hint style="info" %} +This guide is based on our **general recommendations** for working with and building extensions for the Umbraco backoffice. + +You can use **any framework or library**, as you are not limited to the mentioned frameworks. +{% endhint %} + +## Getting Started With Vite + +Vite comes with a set of good presets to get you quickly up and running with libraries and languages. For example: Lit, Svelte, and Vanilla Web Components with both JavaScript and TypeScript. + +{% hint style="info" %} +Before following this guide, read the [Setup Your Development Environment](./) article. +{% endhint %} + +1. Open a terminal and navigate to the project folder where you want to create your new Vite Package. +2. Run the following command in the folder to create a new Vite Package: + +```bash +npm create vite@latest +``` + +This command will set up your new package and ask you to pick a framework and a compiler. + +3. Enter `Client` as both the **Project Name** and **Package name** when prompted. + +4. Choose **Lit** and **TypeScript** as the framework and language. + +{% hint style="info" %} +For this tutorial, it is recommended to use the names given above. However, feel free to choose other names if preferred. +{% endhint %} + +

Create vite command choices

+ +This creates a new folder called `Client`, sets up our new project, and creates a `package.json` file, which includes the necessary packages. This is where all your source files live. + +{% hint style="info" %} +Alternatively, you can skip the interactive prompts and use this command: + +```typescript +npm create vite@latest Client -- --template lit-ts +``` + +This will create a Vite Package with Lit and TypeScript in a folder called **Client**. +{% endhint %} + +5. Navigate to the **Client** project folder and install the required packages: + +```bash +npm install +``` + +Before proceeding, ensure that you install the version of the Backoffice package compatible with your Umbraco installation. You can find the appropriate version on the [@umbraco-cms/backoffice npm page](https://www.npmjs.com/package/@umbraco-cms/backoffice). + +6. Install the Backoffice package using the following command: + +```bash +npm install -D @umbraco-cms/backoffice +``` + +{% hint style="info" %} +To avoid installing Umbraco’s sub-dependencies such as TinyMCE and Monaco Editor, you can add the `--legacy-peer-deps` flag: +{% endhint %} + + ```bash +npm install --legacy-peer-deps -D @umbraco-cms/backoffice + ``` + +Using this flag will disable Intellisense for external references. + +7. Open the `tsconfig.json` file. +8. Add the array `types` inside `compilerOptions`, with the entry of `@umbraco-cms/backoffice/extension-types`: + +```json +{ + "compilerOptions": { + ... + "types": [ + "@umbraco-cms/backoffice/extension-types" + ] + } +} +``` + +9. Create a new file called `vite.config.ts` in the folder and insert the following code: + +{% code title="vite.config.ts" lineNumbers="true" %} +```ts +import { defineConfig } from "vite"; + +export default defineConfig({ + build: { + lib: { + entry: "src/my-element.ts", // your web component source file + formats: ["es"], + }, + outDir: "../App_Plugins/Client", // all compiled files will be placed here + emptyOutDir: true, + sourcemap: true, + rollupOptions: { + external: [/^@umbraco/], // ignore the Umbraco Backoffice package in the build + }, + }, + base: "/App_Plugins/Client/", // the base path of the app in the browser (used for assets) +}); +``` +{% endcode %} + +{% hint style="info" %} +The `outDir` parameter specifies where the compiled files are placed. In this example, they are stored in the `App_Plugins/Client` folder. If you are working with a different structure, such as a Razor Class Library (RCL) project, update this path to `wwwroot`. +{% endhint %} + +This alters the Vite default output into a **library mode**, where the output is a JavaScript file with the same name as the `name` attribute in `package.json`. The name is `client.js` if you followed this tutorial with no changes. + +The source code that is compiled lives in the `src` folder of your package folder and that is where you can see a `my-element.ts` file. You can confirm that this file is the one specified as our entry on the Vite config file that we recently created. + +{% hint style="info" %} +The `build:lib:entry` parameter can accept an array which will allow you to export multiple files during the build. You can read more about [Vite's build options here](https://vitejs.dev/config/build-options.html#build-lib). +{% endhint %} + +Build the `ts` file in the `Client` folder so we can use it in our package: + +```bash +npm run build +``` + +## Watch for changes and build + +If you like to continuously work on the package and have each change built, you can add a `watch`script in your `package.json` with `vite build --watch`. The example below indicates where in the structure this change should be implemented: + +{% code title="package.json" lineNumbers="true" %} +```json +{ + "name": "Client", + ... + "scripts": { + "watch": "vite build --watch" + ... + }, + ... +``` +{% endcode %} + +Then in the terminal, you can run `npm run watch`. + +## Umbraco Package declaration + +Declare your package to Umbraco via a file called `umbraco-package.json`. This should be added at the root of your package. In this guide, it is inside the `Client/public` folder so that Vite automatically copies it over every time it builds. + +This example declares a Dashboard as part of your Package, using the Vite example element. + +{% code title="Client/public/umbraco-package.json" lineNumbers="true" %} +```json +{ + "$schema": "../../umbraco-package-schema.json", + "name": "My Dashboard", + "version": "0.1.0", + "extensions": [ + { + "type": "dashboard", + "alias": "My.Dashboard.MyExtension", + "name": "My Dashboard", + "element": "/App_Plugins/Client/client.js", + "elementName": "my-element", + "meta": { + "label": "My Dashboard", + "pathname": "my-dashboard" + } + } + ] +} +``` +{% endcode %} + +{% hint style="info" %} +Umbraco needs the name of the element that will render as default when our dashboard loads. + +* This is specified in the **manifest** as the `elementName`. +* Another approach would be to define your default element in the TS code. To do this, in the `src/my-element.ts` add **`default`** to your `MyElement` class in the file like so: + +```ts +export default class MyElement extends LitElement { +``` +{% endhint %} + +Learn more about the abilities of the manifest file in the [Umbraco Package Manifest](../umbraco-package.md) article. + +#### Testing your package + +To be able to test your package, you will need to run your site. + +Before you do this, you need to make sure to run `npm run build` to compile your TypeScript files and copy them to the `App_Plugins/Client` folder. + +{% hint style="warning" %} +If you try to include some of these resources via Visual Studio (VS), then make sure not to include TypeScript files. Otherwise, VS will try to include a few lines on your `.csproj` file to compile the TypeScript code that exists in your project folder. When you run your website, VS will try to compile these files and fail. +{% endhint %} + +The final result looks like this: + +

My dashboard

+ +Back in the `src/my-element.ts` file, you can update the `styles` property to make any styling changes. You can change the `background-color` of the `button` to white so it is more visible: + +```css +button { + background-color: white; +} +``` + +## Summary + +With this, you have set up your Package and created an Extension for the Backoffice. + +In more advanced cases, you can add more elements to your package and create more complex extensions. In that case, you can benefit greatly from creating another project in your solution to hold the files. This way, you can keep your solution clean and organized. We recommend creating a [Razor Class Library (RCL)](https://learn.microsoft.com/en-us/aspnet/core/razor-pages/ui-class?view=aspnetcore-8.0\&tabs=visual-studio#create-an-rcl-with-static-assets) for this purpose. You can read more about this in the [Development Flow](./#source-code) article. + +This Dashboard appears in all sections and does not do much. To extend it to interact with the Umbraco Backoffice, follow the tutorial on [Creating Your First Extension](../../tutorials/creating-your-first-extension.md). diff --git a/16/umbraco-cms/customizing/extending-overview/README.md b/16/umbraco-cms/customizing/extending-overview/README.md new file mode 100644 index 00000000000..3182b6dcb57 --- /dev/null +++ b/16/umbraco-cms/customizing/extending-overview/README.md @@ -0,0 +1,19 @@ +--- +description: Getting started with backoffice setup and configurations +--- + +# Extension Overview + +The Backoffice architecture is based on Extensions, making different parts of the UI extendable. Enabling you to append, replace, or remove parts. + +
+ +In this section you can find the common terms, concepts and guides used to extend the Umbraco backoffice. + +## [Extension Registry](extension-registry/) + +How to registere extensions or manipulate others. + +## [Extension Types](extension-types/) + +An overview of the different ways to append funcationtlity. diff --git a/16/umbraco-cms/customizing/extending-overview/custom-extension-type.md b/16/umbraco-cms/customizing/extending-overview/custom-extension-type.md new file mode 100644 index 00000000000..48284d67b25 --- /dev/null +++ b/16/umbraco-cms/customizing/extending-overview/custom-extension-type.md @@ -0,0 +1,23 @@ +# Custom Extension Types + +The extension registry is an open system, which can hold any Extension Manifest Type. This article describes how you can declare your types. +Types can be declared for re-useability/maintainability or to open up for other package extensions. + +## Manifest Type Declaration + +A Manifest Type is declared via a TypeScript Interface, like shown below: + +```typescript +import type { ManifestBase } from '@umbraco-cms/backoffice/extension-api'; + +export interface ManifestPreviewAppProvider extends ManifestBase { + type: 'myPrefixedExtensionType'; +} + +// Declare the Manifest Type in the global UmbExtensionManifestMap interface: +declare global { + interface UmbExtensionManifestMap { + MyPrefixedExtensionManifest: MyExtensionManifestType; + } +} +``` diff --git a/16/umbraco-cms/customizing/extending-overview/extension-conditions.md b/16/umbraco-cms/customizing/extending-overview/extension-conditions.md new file mode 100644 index 00000000000..44037a0ad4c --- /dev/null +++ b/16/umbraco-cms/customizing/extending-overview/extension-conditions.md @@ -0,0 +1,70 @@ +--- +description: Learn how to use Extension Conditions when working with the Umbraco backoffice. +--- + +# Extension Conditions + +{% hint style="warning" %} +This page is a work in progress and may undergo further revisions, updates, or amendments. The information contained herein is subject to change without notice. +{% endhint %} + +Extension Manifest Conditions enable you to declare requirements for an Extension before it becomes available. + +## Utilizing Conditions in your Manifest + +Conditions are referenced via their alias. The Declaration of a Condition is shown in the following example: + +```typescript +const manifest = { + type: 'workspaceView', + ... + conditions: [ + { + alias: 'Umb.Condition.WorkspaceAlias', + match: 'Umb.Workspace.Document', + }, + ], +}; +``` + +By declaring a condition the extension will become available only once the condition is permitted. + +The example above requires the nearest Workspaces Alias to be equal to `'Umb.Workspace.Document'`. + +When declaring multiple conditions all of them must be permitted for the extension to be available. + +## Built-in conditions types + +The following conditions are available out of the box, for all extension types that support Conditions. + +* `Umb.Condition.Switch` - Toggles on and off based on the `frequency` set in seconds. +* `Umb.Condition.MultipleAppLanguages` - Requires the app to have more than one language, such as a multi-language site. +* `Umb.Condition.SectionAlias` - Requires the current Section Alias to match the one specified. +* `Umb.Condition.MenuAlias` - Requires the current Menu Alias to match the one specified. +* `Umb.Condition.WorkspaceAlias` - Requires the current Workspace Alias to match the one specified. +* `Umb.Condition.WorkspaceEntityType` - Requires the current workspace to work on the given Entity Type. Examples: 'document', 'block' or 'user'. +* `Umb.Condition.WorkspaceContentTypeAlias` - Requires the current workspace to be based on a Content Type which Alias matches the one specified. +* `Umb.Condition.Workspace.ContentHasProperties` - Requires the Content Type of the current Workspace to have properties. +* `Umb.Condition.WorkspaceHasCollection` - Requires the current Workspace to have a Collection. +* `Umb.Condition.WorkspaceEntityIsNew` - Requires the current Workspace data to be new, not yet persisted on the server. +* `Umb.Condition.EntityIsTrashed` - Requires the current entity to be trashed. +* `Umb.Condition.EntityIsNotTrashed` - Requires the current entity to not be trashed. +* `Umb.Condition.SectionUserPermission` - Requires the current user to have permissions to the given Section Alias. +* `Umb.Condition.UserPermission.Document` - Requires the current user to have specific Document permissions. Example: 'Umb.Document.Save'. +* `Umb.Condition.CurrentUser.GroupId` - Requires the current user to belong to a specific group by GUID. Accepts `match` (GUID), `oneOf` (array), `allOf` (array), and `noneOf` (array). Example: '8d2b3c4d-4f1f-4b1f-8e3d-4a6b7b8c4f1e'. +* `Umb.Condition.CurrentUser.IsAdmin` - Requires the current user to be an admin as defined by the backend, for example, that they belong to the Administrator group. + +## Condition Configuration + +The conditions are defined as an array of condition configurations. Each entry can contain the following properties: + +* `alias`- The alias of the condition to utilize. +* `...` - The rest of the properties of the object are specific to the condition configuration. + +## Learn more + +Learn about built-in conditions and how to create your own: + +{% content-ref url="extension-types/condition.md" %} +Condition Extension Type +{% endcontent-ref %} diff --git a/16/umbraco-cms/customizing/extending-overview/extension-kind.md b/16/umbraco-cms/customizing/extending-overview/extension-kind.md new file mode 100644 index 00000000000..7d6b711c1c9 --- /dev/null +++ b/16/umbraco-cms/customizing/extending-overview/extension-kind.md @@ -0,0 +1,56 @@ +--- +description: Learn how to use the Kind extension in your manifest files when extending the Umbraco CMS backoffice. +--- + +# Extension Kind + +{% hint style="warning" %} +This page is a work in progress and may undergo further revisions, updates, or amendments. The information contained herein is subject to change without notice. +{% endhint %} + +The **Extension Manifest Kind** is used to declare a preset configuration that other extensions can inherit. It ensures maintainability and consistency when creating extensions for the Umbraco CMS backoffice. By using a Kind, developers can reuse predefined settings, which reduces redundancy across extensions. + +When a Kind is applied, the extension's manifest inherits the fields defined in the Kind. This approach prevents the need to repeat configuration details across multiple extensions. + +## Manifest Kind Declaration + +A **Kind** is declared in the `kind` field of the manifest, which is part of the extension registration process. The declaration is linked to a specific extension type. + +```typescript +const manifest = { + type: 'headerApp', // The type of the extension + kind: 'button', // The kind alias to inherit settings from + ... +}; +``` + +By setting the `kind` field, the extension automatically inherits all properties associated with that **Kind**. This is useful when you want to standardize a component (like a button) across multiple extensions. + +## Using the Kind for Inheritance + +A Kind not only defines the basic configuration but may also require additional metadata to fully configure the element or component. + +### Example: Using the Button Kind in a Header App + +```typescript +const manifest = { + type: 'headerApp', + kind: 'button', + name: 'My Header App Example', + alias: 'My.HeaderApp.Example', + meta: { + label: 'My Example', + icon: 'icon-home', + href: '/some/path/to/open/when/clicked', + }, +}; +``` + +In this example: + +- The `kind: 'button'` inherits all default settings from the **Button Kind**. +- The `meta` object is added to configure additional properties, such as the button's label, icon, and the URL to open when clicked. + +The Kind allows you to extend existing functionality and tailor it to specific needs while maintaining consistency. + +For a deeper dive into Kind and how to create your own, see the [Kind](extension-types/kind.md) article. diff --git a/16/umbraco-cms/customizing/extending-overview/extension-registry/README.md b/16/umbraco-cms/customizing/extending-overview/extension-registry/README.md new file mode 100644 index 00000000000..0a2ce0548c2 --- /dev/null +++ b/16/umbraco-cms/customizing/extending-overview/extension-registry/README.md @@ -0,0 +1,19 @@ +# Extension Registry + +{% hint style="warning" %} +This page is a work in progress and may undergo further revisions, updates, or amendments. The information contained herein is subject to change without notice. +{% endhint %} + +The Extensions Registry is your entry to extend or customize the Backoffice. Therefor it is crucial to understand the abilities of the Extension Registry. + +## [Extension Registration](extension-registry.md) + +The extension registry is a global registry that can be accessed and changed at anytime while Backoffice is running. + +## [Extension Manifest](extension-manifest.md) + +Each Extension Manifest has to declare its type, this is used to determine where it hooks into the system. It also looks at what data is required to declare within it. + +## [Replace, Exclude or Unregistere](./#replace-exclude-or-unregistere) + +Once you understand how to declare your own, you may want to replace or remove existing. diff --git a/16/umbraco-cms/customizing/extending-overview/extension-registry/extension-manifest.md b/16/umbraco-cms/customizing/extending-overview/extension-registry/extension-manifest.md new file mode 100644 index 00000000000..b6766690d09 --- /dev/null +++ b/16/umbraco-cms/customizing/extending-overview/extension-registry/extension-manifest.md @@ -0,0 +1,137 @@ +--- +description: Learn about the different methods for declaring an Extension Manifest. +--- + +# Extension Manifest + +The Extension Manifest is the first step for any extension. It is the declaration of what you want to register. + +In this section you will find all the Extension Types provided by the Backoffice. [See all Extension Types here.](../extension-types/README.md) + +## Extension Manifest Format + +An Extension Manifest can be written as a JavaScript or JSON Object. + +There are a few general properties, the required set of properties consists of the `type`, `alias`, and `name`. + +```typescript +const manifest = { + type: '...', + alias: 'my.customization', + name: 'My customization' + ... +}; +``` + +The `type` defines what it is declaring. The `alias` is a unique identifier for this manifest. It must be globally unique, so make sure to prefix it with something that makes your extension unique. The `name` is a representational name of this manifest, this does not need to be unique but such can be beneficial when debugging extensions. + +## Manifest Data + +Each Extension Manifest has to declare its type. This is used to determine where it hooks into the system. It also determines what data is required of this manifest. + +The abilities of the extensions rely on the specific extension type. The Type sets the scene for what the extension can do and what it needs to be utilized. Some extension types can be made purely via the manifest. Other requires files, like a JavaScript file containing a Web Component. + +The required fields of any extension manifest are: + +* `type` - The type defines the type and purpose of the extension. It is used to determine where the extension will be used and defines the data needed for this manifest. +* `alias`- The alias is used to identify the extension. This has to be unique for each extension. +* `name` - The name of the extension. This is used to identify the extension in the UI. + +Additionally, many extensions support the use of the following fields: + +* `weight` - Define a weight, to determine the importance or visual order of this extension. The higher the weight, the higher in the list it will appear. +* `overwrites` - Define one or more Extension Aliases that this extension should replace. Notice it only omits the listed Extensions when this is rendered in the same spot. [Read more in Replace, Exclude or Unregister](replace-exclude-or-unregister.md). +* `conditions` - Define one or more conditions that must be permitted for the extension to become available. [Extension Conditions](../extension-conditions.md). +* `kind` - Define a kind-alias of which this manifest should be based upon. Kinds acts like a preset for your manifest. [Extension Kinds](../extension-kind.md). + +Many of the Extension Types require additional information declared as part of a `meta` field. + +## Registration + +An Extension Manifest can be registered in multiple ways. + +The primary registration should take part of the Umbraco Package Manifest. + +You can choose to declare all Extensions in the Package Manifest, or use one of three Extension Types to registere more extensions. + +This enables you to locate your Manifests in files together with the implementation code and the ability to declare Extension Manifests in TypeScript. + +A typical structure would be to declare one or more `Bundle` extensions in the Package Manifest. Each of the Bundles points to a `manifests.js` file which declares the Extensions of interest. + +### The `bundle` extension type + +The Bundle extension type is used to declare multiple Extension Manifests from a single JavaScript file. + +The Bundle is loaded at startup. All the Extension Manifests exported of the JavaScript file will be registered. + +Read more about the `bundle` extension type in the [Bundle](../extension-types/bundle.md)article. + +### The `backofficeEntryPoint` extension type + +Run any JavaScript code when Backoffice startups, after the user is logged in. This can be used as an entry point for a package, registering more extensions or configuring your package. + +There are many use cases. To name a few, it could be to load external libraries shared by all your extensions or load **global CSS files** for the whole application. + +The entry point declares a single JavaScript file that will be loaded and run when the Backoffice starts. + +Read more about the `backofficeEntryPoint` extension type in the [Entry Point](extension-manifest.md#the-backofficeentrypoint-extension-type) article. + +### The `appEntryPoint` extension type + +Similar as `appEntryPoint` this runs as startup, the difference is that this runs before the user is logged in. Use this to initiate things before the user is logged in or to provide things for the Login screen. + +Read more about the `appEntryPoint` extension type in the [App Entry Point](../extension-types/app-entry-point.md) article. + +## Type intellisense + +It is recommend to make use of the Type intellisense that we provide. + +When writing your Manifest in TypeScript you should use the Type `UmbExtensionManifest`, see the [TypeScript setup](../../development-flow/typescript-setup.md) article to make sure you have Types correctly configured. + +{% code title="manifests.ts" %} +```typescript +export const manifests: Array = [ + { + type: '...', + alias: 'my.customization', + name: 'My customization' + ... + } +] +``` +{% endcode %} + +When writing the Umbraco Package Manifest you can use the JSON Schema located in the root of your Umbraco project called `umbraco-package-schema.json` + +```json +{ + "$schema": "../../umbraco-package-schema.json", + "name": "Fast Track Customizations", + "extensions": [ + { + "type": "...", + "alias": "my.customization", + "name": "My customization" + ... + }, + ... + ] +} +``` + +### Registration via any JavaScript code + +Alternatively, an Extension Manifest can be declared in JavaScript at any given point. + +The following example shows how to register an extension manifest via JavaScript code: + +```typescript +import { umbExtensionsRegistry } from "@umbraco-cms/backoffice/extension-registry" + +const manifest = { + type: '...', + ... +}; + +umbExtensionsRegistry.register(extension); +``` diff --git a/16/umbraco-cms/customizing/extending-overview/extension-registry/extension-registry.md b/16/umbraco-cms/customizing/extending-overview/extension-registry/extension-registry.md new file mode 100644 index 00000000000..d80d526ae18 --- /dev/null +++ b/16/umbraco-cms/customizing/extending-overview/extension-registry/extension-registry.md @@ -0,0 +1,18 @@ +# Extension Registration + +{% hint style="warning" %} +This page is a work in progress and may undergo further revisions, updates, or amendments. The information contained herein is subject to change without notice. +{% endhint %} + +The extension registry is the center piece of the Backoffice UI. It holds information about most of the Backoffice UI, as most are extensions. This includes the built-in UI. The registry can be manipulated at any time, meaning you can add or remove extensions at runtime. + +To provide new UI to the backoffice, you must register them via an Extension Manifest. This can be initiated via an Umbraco Package JSON file on the server. This will be your starting point. + +Declaring a new extension is done by declaring an [extension manifest](extension-manifest.md). This can be done in one of two ways: + +1. Via a manifest JSON file on the server. +2. In a JavaScript/TypeScript file. + +These two options can be combined as you like. + +A typical use case of such is achieved by registering a single extension manifest in your Umbraco Package JSON file. This manifest would then load a JS file, that registers the rest of your extensions. Learn more about these abilities in the [bundle](../extension-types/bundle.md) or [backoffice entry point](../extension-types/backoffice-entry-point.md) articles. diff --git a/16/umbraco-cms/customizing/extending-overview/extension-registry/replace-exclude-or-unregister.md b/16/umbraco-cms/customizing/extending-overview/extension-registry/replace-exclude-or-unregister.md new file mode 100644 index 00000000000..d4ae61646a7 --- /dev/null +++ b/16/umbraco-cms/customizing/extending-overview/extension-registry/replace-exclude-or-unregister.md @@ -0,0 +1,67 @@ +--- +description: >- + You may want to replace or completely remove an extension. Depending on your + interest, 3 different options are available. +--- + +# Replace, Exclude or Unregister + +## Replace + +If you want to bring your own extension and remove an existing, define your Extension to replace one or more other Extensions. + +This can be done by defining `overwrites` of your manifest with one Extension-alias or an array of Extension-aliases. [Read more about the Manifest Declaration here.](extension-manifest.md) + +Overwrite a single extension: + +```typescript +const manifest = { + type: 'workspaceAction', + alias: 'my.WorkspaceAction.ExternalPreview', + name: 'My workspace action for external preview', + overwrites: 'Umb.WorkspaceAction.Document.SaveAndPreview' + ... +}; +``` + +Overwrite multiple extensions: + +```typescript +const manifest = { + type: 'workspaceAction', + alias: 'my.WorkspaceAction.ExternalPreview', + name: 'My workspace action for external preview', + overwrites: ['Umb.WorkspaceAction.Document.SaveAndPreview', 'Umb.WorkspaceAction.Document.Save'] + ... +}; +``` + +Once your extension is displayed in the same spot as the one defined in `overwrites`, those will be hidden. + +If your extension has conditions, then the overwrites will only be hidden when your extension is displayed. This means that the overwrites only have an effect if all the conditions are permitted and the extensions are displayed at the same spot. + +## Exclude + +To make an extension go away completely, you should exclude it. This approach secures that the extension will never be presented. + +The following JavaScript code hides the `Save and Preview` button from the Document Workspace. + +```typescript +import { UmbExtensionRegistry } from '@umbraco-cms/backoffice/extension-api'; + +UmbExtensionRegistry.exclude('Umb.WorkspaceAction.Document.SaveAndPreview'); +``` + +## Unregister + +You can also choose to unregister an extension, this is only preferred if you registered the extension and are in control of the flow. If its not your Extension please seek to use the `Overwrites` or `Exclude` feature. + +```typescript +import { UmbExtensionRegistry } from '@umbraco-cms/backoffice/extension-api'; + +UmbExtensionRegistry.unregister('My.WorkspaceAction.AutoFillWithUnicorns'); +``` + +This will not prevent the Extension from being registered again. + +A use case for this is if you temporarily registered an extension and you like to remove it again. diff --git a/16/umbraco-cms/customizing/extending-overview/extension-types/README.md b/16/umbraco-cms/customizing/extending-overview/extension-types/README.md new file mode 100644 index 00000000000..f28761f0b51 --- /dev/null +++ b/16/umbraco-cms/customizing/extending-overview/extension-types/README.md @@ -0,0 +1,31 @@ +--- +description: >- + The Extension types have some general features and some are provide + specifically to a type. +--- + +# Extension Types + +### General features + +The general features of all Extension Types can be read as part of the [Extension Manifest Article](../extension-registry/extension-manifest.md) + +### General Extension Type + +The system provides Extension Types for certain needs and then there is a few that has a general prupose. + +### [Bundle](bundle.md) + +The `bundle` type enables you to gather many extension manifests into one. These will be registered at startup. + +### [Backoffice Entry Point](backoffice-entry-point.md) + +The `backofficeEntryPoint` type is used to execute the method of a JavaScript file when the backoffice is initialized. This file can be used to do anything, this enables more complex logic to take place on startup. + +### [Extension Conditions](condition.md) + +Most Extension Types support conditions. Defining conditions enables you to control when and where the Extension is available. This Type enables you to bring your own Conditions for the system. + +### [Kinds](kind.md) + +The Kind-type enables you to base your Extension registration on a preset manifest. A kind provides the base manifest that your manifest will be ammending. A typical Kind declaration would provide a default Element, making it posible for you to only configure the Element via properties of the Manifest. diff --git a/16/umbraco-cms/customizing/extending-overview/extension-types/app-entry-point.md b/16/umbraco-cms/customizing/extending-overview/extension-types/app-entry-point.md new file mode 100644 index 00000000000..d2f647283b6 --- /dev/null +++ b/16/umbraco-cms/customizing/extending-overview/extension-types/app-entry-point.md @@ -0,0 +1,13 @@ +--- +description: The App Entry Point extension type is used to run some JavaScript code before the user is logged in. +--- + +# App Entry Point + +This manifest declares a single JavaScript file that will be loaded and run when the Backoffice starts. Additionally, the code will also run on the login screen. + +It performs the same function as the `backofficeEntryPoint` extension type, but the difference is that this runs before the user is logged in. Use this to initiate things before the user is logged in or to provide things for the Login screen. + +Read more about `backofficeEntryPoint` to learn how to use it: + +{% content-ref url="./backoffice-entry-point.md" %} . {% endcontent-ref %} diff --git a/16/umbraco-cms/customizing/extending-overview/extension-types/auth-provider.md b/16/umbraco-cms/customizing/extending-overview/extension-types/auth-provider.md new file mode 100644 index 00000000000..e69de29bb2d diff --git a/16/umbraco-cms/customizing/extending-overview/extension-types/backoffice-entry-point.md b/16/umbraco-cms/customizing/extending-overview/extension-types/backoffice-entry-point.md new file mode 100644 index 00000000000..81dfa73e2fb --- /dev/null +++ b/16/umbraco-cms/customizing/extending-overview/extension-types/backoffice-entry-point.md @@ -0,0 +1,161 @@ +--- +description: The Backoffice Entry Point extension type is used to run some JavaScript code at startup. +--- + +# Backoffice Entry Point + +This manifest declares a single JavaScript file that will be loaded and run when the Backoffice starts. In other words this can be used as an entry point for a package. + +The `backofficeEntryPoint` extension is also the way to go if you want to load in external libraries such as jQuery, Angular, React, etc. You can use the `backofficeEntryPoint` to load in the external libraries to be shared by all your extensions. Additionally, **global CSS files** can also be used in the `backofficeEntryPoint` extension. + +{% hint style="info" %} +See also the [App Entry Point](./app-entry-point.md) article for a similar extension type that runs before the user is logged in. +{% endhint %} + +**Register a Backoffice Entry Point in the `umbraco-package.json` manifest** + +{% code title="umbraco-package.json" %} +```json +{ + "name": "Name of your package", + "alias": "My.Package", + "extensions": [ + { + "type": "backofficeEntryPoint", + "alias": "My.EntryPoint", + "js": "/App_Plugins/YourFolder/index.js" + } + ] +} +``` +{% endcode %} + +**Base structure of the entry point file** + +{% hint style="info" %} +All examples are in TypeScript, but you can use JavaScript as well. Make sure to use a bundler such as [Vite](../../development-flow/vite-package-setup.md) to compile your TypeScript to JavaScript. +{% endhint %} + +{% code title="index.ts" %} +```typescript +import type { UmbEntryPointOnInit } from '@umbraco-cms/backoffice/extension-api'; + +/** + * Perform any initialization logic when the Backoffice starts + */ +export const onInit: UmbEntryPointOnInit = (host, extensionsRegistry) => { + // Your initialization logic here +} + +/** + * Perform any cleanup logic when the Backoffice and/or the package is unloaded + */ +export const onUnload: UmbEntryPointOnUnload = (host, extensionsRegistry) => { + // Your cleanup logic here +} +``` +{% endcode %} + +{% hint style="info" %} +The `onUnload` function is optional and can be used to perform cleanup logic when the Backoffice and/or the package is unloaded. +{% endhint %} + +## Examples + +### Register additional UI extensions in the entry point file + +{% code title="index.ts" %} +```typescript +import type { UmbEntryPointOnInit } from '@umbraco-cms/backoffice/extension-api'; + +const manifest: UmbExtensionManifest = { + type: '', // type of extension + alias: '', // unique alias for the extension + elementName: '', // unique name of the custom element + js: '', // path to the javascript resource + meta: { + // additional props for the extension type + } +}; + +export const onInit: UmbEntryPointOnInit = (host, extensionsRegistry) => { + // Register the extension + extensionRegistry.register(manifest); +} + +export const onUnload: UmbEntryPointOnUnload = (host, extensionsRegistry) => { + // Unregister the extension (optional) + extension.unregister(manifest); +} +``` +{% endcode %} + +{% hint style="info" %} +If you only need to register extensions, then consider using a [bundle](./bundle.md) type instead. +{% endhint %} + +### Register global CSS + +An entry point is a good place to load global CSS files for the whole application. You can do this by creating a link element and appending it to the head of the document: + +{% code title="index.ts" %} +```typescript +import type { UmbEntryPointOnInit } from '@umbraco-cms/backoffice/extension-api'; + +export const onInit: UmbEntryPointOnInit = (host, extensionsRegistry) => { + const css = document.createElement('link'); + css.rel = 'stylesheet'; + css.href = '/App_Plugins/YourFolder/global.css'; + document.head.appendChild(css); +} +``` +{% endcode %} + +Alternatively, you can import the CSS file directly in your JavaScript file: + +{% code title="index.ts" %} +```typescript +import '/App_Plugins/YourFolder/global.css'; +``` +{% endcode %} + +## Type IntelliSense + +It is recommended to make use of the Type intellisense that we provide. + +When writing your Manifest in TypeScript you should use the Type `UmbExtensionManifest`, see the [TypeScript setup](../../development-flow/typescript-setup.md) article to make sure you have Types correctly configured. + +{% code title="index.ts" %} +```typescript +import type { UmbEntryPointOnInit } from '@umbraco-cms/backoffice/extension-api'; + +const manifests: Array = [ + { + type: '...', + alias: 'my.customization', + name: 'My customization' + ... + }, + ... +]; + +export const onInit: UmbEntryPointOnInit = (host, extensionsRegistry) => { + // Register the extensions + extensionRegistry.registerMany(manifests); +} +``` +{% endcode %} + +## What's next? + +See the Extension Types article for more information about all the different extension types available in Umbraco: + +{% content-ref url="./" %} +[README](./) +{% endcontent-ref %} + +Read about running code before log in using an `appEntryPoint`: + +{% content-ref url="./app-entry-point.md" %} +[App Entry Point](./app-entry-point.md) +{% endcontent-ref %} diff --git a/16/umbraco-cms/customizing/extending-overview/extension-types/block-custom-view.md b/16/umbraco-cms/customizing/extending-overview/extension-types/block-custom-view.md new file mode 100644 index 00000000000..469cd2746f1 --- /dev/null +++ b/16/umbraco-cms/customizing/extending-overview/extension-types/block-custom-view.md @@ -0,0 +1,82 @@ +--- +description: Bring your own representation for Blocks. +--- + +# Block Custom View + +The Block Custom View extension type lets you define a Web Component for representing blocks. + +## Build a Custom View + +1. Make a Document Type with a Property using a Block Editor of choice. +2. Configure at least one Block Type on the Block Editor. +3. Ensure the Element Type of the Blocks Content Model has a property using `headline` as the Property Alias. +4. Take note of the Element Type Alias as you will use that in the next step. +5. Add the following code to the `umbraco-package.json` file: + +{% code title="umbraco-package.json" %} +```json +{ + "$schema": "../../umbraco-package-schema.json", + "name": "My.CustomViewPackage", + "version": "0.1.0", + "extensions": [ + { + "type": "blockEditorCustomView", + "alias": "my.blockEditorCustomView.Example", + "name": "My Example Custom View", + "element": "/App_Plugins/welcome-dashboard/dist/example-block-custom-view.js", + "forContentTypeAlias": "{Insert Element Type Alias here}" + } + ] + +``` +{% endcode %} + +The code of your Web Component could look like this: + +{% code title="example-custom-view.ts" %} +```typescript +import { html, customElement, LitElement, property, css } from '@umbraco-cms/backoffice/external/lit'; +import { UmbElementMixin } from '@umbraco-cms/backoffice/element-api'; +import type { UmbBlockDataType } from '@umbraco-cms/backoffice/block'; +import type { UmbBlockEditorCustomViewElement } from '@umbraco-cms/backoffice/block-custom-view'; + +@customElement('example-block-custom-view') +export class ExampleBlockCustomView extends UmbElementMixin(LitElement) implements UmbBlockEditorCustomViewElement { + + @property({ attribute: false }) + content?: UmbBlockDataType; + + render() { + return html` +
My Custom View
+

Headline: ${this.content?.headline}

+ `; + } + + static styles = [ + css` + :host { + display: block; + height: 100%; + box-sizing: border-box; + background-color: darkgreen; + color: white; + border-radius: 9px; + padding: 12px; + } + `, + ]; + +} +export default ExampleBlockCustomView; + +declare global { + interface HTMLElementTagNameMap { + 'example-block-custom-view': ExampleBlockCustomView; + } +} + +``` +{% endcode %} diff --git a/16/umbraco-cms/customizing/extending-overview/extension-types/block-editors-custom-view.md b/16/umbraco-cms/customizing/extending-overview/extension-types/block-editors-custom-view.md new file mode 100644 index 00000000000..e69de29bb2d diff --git a/16/umbraco-cms/customizing/extending-overview/extension-types/bundle.md b/16/umbraco-cms/customizing/extending-overview/extension-types/bundle.md new file mode 100644 index 00000000000..78470d31f30 --- /dev/null +++ b/16/umbraco-cms/customizing/extending-overview/extension-types/bundle.md @@ -0,0 +1,66 @@ +--- +description: Gather Extension Manifests in one file +--- + +# Bundle + +The `bundle` Extension Type points to a single JavaScript file that exports or re-exports Extension Manifests written in JavaScript. + +It can be used as the entry point for a package, or as a grouping for a set of manifests. A Bundle can reference other Bundles. + +## Use Bundle as an entry point for a package + +If you want to declare your manifests in JavaScript/TypeScript, the Bundle is a great choice. + +The following example shows an `umbraco-package.json` that refers to one bundle, which can then declare manifests. + +{% code title="umbraco-package.json" %} +```json + { + "name": "My Package Name", + "version": "1.0.0", + "extensions": [ + { + "type": "bundle", + "alias": "My.Package.Bundle", + "name": "My Package Bundle", + "js": "/App_Plugins/my-package/manifests.js" + } + ] + } +``` +{% endcode %} + +{% code title="manifests.ts" %} +```typescript +export const manifests: Array = [ + { + type: 'dashboard', + name: 'Example Dashboard', + alias: 'example.dashboard.demo', + element: () => import('./demo-dashboard.js'), + weight: 900, + meta: { + label: 'Demo example', + pathname: 'demo-example', + }, + }, + // ... insert as many manifests as you like +] +``` +{% endcode %} + +{% hint style="info" %} +Ensure you have set up your `tsconfig.json` to include the extension-types as global types. Like this: + +```json +{ + "compilerOptions": { + ... + "types": [ + "@umbraco-cms/backoffice/extension-types" + ] + } +} +``` +{% endhint %} diff --git a/16/umbraco-cms/customizing/extending-overview/extension-types/collections/README.md b/16/umbraco-cms/customizing/extending-overview/extension-types/collections/README.md new file mode 100644 index 00000000000..323c657fb09 --- /dev/null +++ b/16/umbraco-cms/customizing/extending-overview/extension-types/collections/README.md @@ -0,0 +1,6 @@ +--- +description: >- + An overview of the available extension types related to collections. +--- + +# Extension Types: Collections diff --git a/16/umbraco-cms/customizing/extending-overview/extension-types/collections/collection-action.md b/16/umbraco-cms/customizing/extending-overview/extension-types/collections/collection-action.md new file mode 100644 index 00000000000..e69de29bb2d diff --git a/16/umbraco-cms/customizing/extending-overview/extension-types/collections/collection-view.md b/16/umbraco-cms/customizing/extending-overview/extension-types/collections/collection-view.md new file mode 100644 index 00000000000..e69de29bb2d diff --git a/16/umbraco-cms/customizing/extending-overview/extension-types/collections/collection.md b/16/umbraco-cms/customizing/extending-overview/extension-types/collections/collection.md new file mode 100644 index 00000000000..e69de29bb2d diff --git a/16/umbraco-cms/customizing/extending-overview/extension-types/condition.md b/16/umbraco-cms/customizing/extending-overview/extension-types/condition.md new file mode 100644 index 00000000000..df6ca44feec --- /dev/null +++ b/16/umbraco-cms/customizing/extending-overview/extension-types/condition.md @@ -0,0 +1,119 @@ +--- +description: >- + Learn how to declare requirements for your extensions using the Extension + Conditions. +--- + +# Extension Conditions + +Extension Conditions declare requirements that should be permitted for the extension to be available. Many, but not all, Extension Types support Conditions. + +[Read about utilizing conditions in Manifests](../extension-conditions.md#utilizing-conditions-in-your-manifest). + +## Make your own conditions + +```html + +``` + +You can make your own Conditions by creating a class that implements the `UmbExtensionCondition` interface. + +```typescript +import { + UmbConditionConfigBase, + UmbConditionControllerArguments, + UmbExtensionCondition +} from '@umbraco-cms/backoffice/extension-api'; +import { UmbConditionBase } from '@umbraco-cms/backoffice/extension-registry'; +import { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; + +export type MyExtensionConditionConfig = UmbConditionConfigBase<'My.Condition.CustomName'> & { + match?: string; +}; + +export class MyExtensionCondition extends UmbConditionBase implements UmbExtensionCondition { + constructor(host: UmbControllerHost, args: UmbConditionControllerArguments) { + super(host, args); + + // enable extension after 10 seconds + setTimeout(() => { + this.permitted = true; + args.onChange(); + }, 10000); + } +} + +// Declare the Condition Configuration Type in the global UmbExtensionConditionConfigMap interface: +declare global { + interface UmbExtensionConditionConfigMap { + MyExtensionConditionConfig: MyExtensionConditionConfig; + } +} +``` + +The global declaration on the last five lines makes your Condition appear valid for manifests using the type `UmbExtensionManifest`. Also, the Condition Config Type alias should match the alias given when registering the condition below. + +The Condition then needs to be registered in the Extension Registry: + +```typescript +export const manifest: UmbExtensionManifest = { + type: 'condition', + name: 'My Condition', + alias: 'My.Condition.CustomName', + api: MyExtensionCondition, +}; +``` + +Finally, you can make use of your condition in any manifests: + +```typescript +export const manifest: UmbExtensionManifest = { + type: 'workspaceAction', + name: 'example-workspace-action', + alias: 'My.Example.WorkspaceAction', + elementName: 'my-workspace-action-element', + conditions: [ + { + alias: 'Umb.Condition.SectionAlias', + match: 'My.Example.Workspace' + }, + { + alias: 'My.Condition.CustomName' + } + ] +} +``` + +As shown in the code above, the configuration property `match` isn't used for our condition. We can do this by replacing the timeout with some other check: + +```typescript +// ... + +export class MyExtensionCondition extends UmbConditionBase implements UmbExtensionCondition { + constructor(host: UmbControllerHost, args: UmbConditionControllerArguments) { + super(host, args); + + if (args.config.match === 'Yes') { + this.permitted = true; + args.onChange(); + } + } +} + +// ... +``` + +With all that in place, the configuration can look like shown below: + +```typescript +{ + // ... + conditions: [ + // ... + { + alias: 'My.Condition.CustomName', + match: 'Yes' + } + ] +} +``` diff --git a/16/umbraco-cms/customizing/extending-overview/extension-types/current-user-action.md b/16/umbraco-cms/customizing/extending-overview/extension-types/current-user-action.md new file mode 100644 index 00000000000..e69de29bb2d diff --git a/16/umbraco-cms/customizing/extending-overview/extension-types/dashboard-collection.md b/16/umbraco-cms/customizing/extending-overview/extension-types/dashboard-collection.md new file mode 100644 index 00000000000..e69de29bb2d diff --git a/16/umbraco-cms/customizing/extending-overview/extension-types/dashboard.md b/16/umbraco-cms/customizing/extending-overview/extension-types/dashboard.md new file mode 100644 index 00000000000..6d1272ac9ef --- /dev/null +++ b/16/umbraco-cms/customizing/extending-overview/extension-types/dashboard.md @@ -0,0 +1,144 @@ +--- +description: A guide to creating custom dashboards in Umbraco +--- + +# Dashboards + +Each section of the Umbraco backoffice has its own set of default dashboards. Your own custom sections can show dashboards, and you can create your own custom dashboards for existing sections. + +
The Getting Started dashboard in Umbraco

The Getting Started dashboard in Umbraco

+ +The dashboard area of Umbraco is used to display an "editor" for the selected item in the tree. If no item is selected, then the default set of section dashboards is shown in the dashboard area. + +Notice that [Section Views](sections/section-view.md) is another similar approach to append information to the root of a Section. Section views are thought mainly to be used as Secondary pages. These two approaches should ideally not be combined. + +## Default Dashboards in Umbraco + +The default dashboards in Umbraco are the ones that are displayed when you first enter a section in the backoffice. These dashboards are used to display information and functionality that is relevant to the section you are in. + +The default sections in Umbraco are: + +| Alias | Name | +| ----------------------- | ---------- | +| Umb.Section.Content | Content | +| Umb.Section.Media | Media | +| Umb.Section.Settings | Settings | +| Umb.Section.Members | Members | +| Umb.Section.Users | Users | +| Umb.Section.Translation | Dictionary | + +Here is a table of the default dashboards in Umbraco and the sections they are used including their aliases: + +| Alias | Section | Weight | Description | +| -------------------------------- | -------------------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------ | +| Umb.Dashboard.UmbracoNews | Umb.Section.Content | 20 | The Getting Started dashboard users see when they first enter Umbraco. Contains the latest news of Umbraco including outbound links to resources | +| Umb.Dashboard.RedirectManagement | Umb.Section.Content | 10 | Contains a list of active URL redirects | +| Umb.Dashboard.SettingsWelcome | Umb.Section.Settings | 500 | Contains a set of boxes with links to appropriate resources | + +Even though these dashboards are useful, you might want to create your own custom dashboard to display specific information or functionality. + +You can try and [create a custom dashboard](../../../tutorials/creating-a-custom-dashboard/) as a way on getting started on this topic. + +## Registering your Dashboard + +This section dives into the Dashboard Extension Manifest, shows how to register one, and append additional details. + +### Example Extension Manifest + +{% hint style="info" %} +You can read more about manifests in the tutorial [Creating Your First Extension](../../../tutorials/creating-your-first-extension.md). +{% endhint %} + +Insert this as an entry in the `extensions` list in a `umbraco-package.json` file. + +{% code title="~/App_Plugins/WelcomeDashboard/umbraco-package.json" lineNumbers="true" %} +```json +{ + "type": "dashboard", + "alias": "my.welcome.dashboard", + "name": "My Welcome Dashboard", + "element": "/App_Plugins/WelcomeDashboard/dashboard.js", + "weight": -1, + "meta": { + "label": "Welcome Dashboard", + "pathname": "welcome-dashboard" + } +} +``` +{% endcode %} + +This will register a dashboard with the alias `my.welcome.dashboard` and the name `My Welcome Dashboard`. The dashboard will be loaded from the file `/App_Plugins/WelcomeDashboard/dashboard.js`. The dashboard will be displayed with the label `Welcome Dashboard` and the URL `/welcome-dashboard` on _all sections_, e.g. `/section/content/dashboard/welcome-dashboard`. + +### Conditions + +You can specify conditions for when the dashboard should be displayed. This is done by adding a `conditions` property to the manifest. Ideally, we would like the dashboard to be shown only in a specific section. This can be done by specifying the condition called `Umb.Condition.SectionAlias` and providing the [alias of the section](dashboard.md#default-dashboards-in-umbraco) you want the dashboard to be displayed on: + +```json +"conditions": [ + { + "alias": "Umb.Condition.SectionAlias", + "match": "Umb.Section.Content" + } +] +``` + +This will make the dashboard only be displayed on the Content section. + +{% hint style="info" %} +You can read more about [Extension Conditions](condition.md) in the documentation. +{% endhint %} + +### Properties + +The dashboard manifest can contain the following properties: + +| Property | Type | Description | +| ----------- | ------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| type | string | The type of extension, should be `dashboard` | +| alias | string | A unique alias for the dashboard extension | +| name | string | The name of the dashboard extension | +| element | string | The path to the JavaScript file that exports the dashboard | +| elementName | string | (Optional) The name of the Web Component that contains the dashboard (only if not a default export) | +| weight | number | (Optional) The weight of the dashboard, higher numbers are displayed first | +| meta | object |

Additional metadata for the dashboard

PropertyTypeDescription
LabelstringThe label shown to the user
pathnamestringThe routable URL pathname
| +| Property | Type | Description | +| Label | string | The label shown to the user | +| pathname | string | The routable URL pathname | +| Property | Type | Description | +| Label | string | The label shown to the user | +| pathname | string | The routable URL pathname | +| Property | Type | Description | +| Label | string | The label shown to the user | +| pathname | string | The routable URL pathname | +| Property | Type | Description | +| Label | string | The label shown to the user | +| pathname | string | The routable URL pathname | +| conditions | array | (Optional) [Conditions](condition.md) for when the dashboard should be displayed | + +### Full Example + +{% code title="~/App_Plugins/WelcomeDashboard/umbraco-package.json" lineNumbers="true" %} +```json +{ + "type": "dashboard", + "alias": "my.welcome.dashboard", + "name": "My Welcome Dashboard", + "element": "/App_Plugins/WelcomeDashboard/dashboard.js", + "weight": -1, + "meta": { + "label": "Welcome Dashboard", + "pathname": "welcome-dashboard" + }, + "conditions": [ + { + "alias": "Umb.Condition.SectionAlias", + "match": "Umb.Section.Content" + } + ] +} +``` +{% endcode %} + +
The Welcome Dashboard shown in the Content section

The Welcome Dashboard appears in the Content section

+ +You can learn about [creating a custom dashboard](../../../tutorials/creating-a-custom-dashboard/) in the tutorials section. Here you will learn how to build the dashboard itself as a Web Component. diff --git a/16/umbraco-cms/customizing/extending-overview/extension-types/dynamic-root-origin.md b/16/umbraco-cms/customizing/extending-overview/extension-types/dynamic-root-origin.md new file mode 100644 index 00000000000..e69de29bb2d diff --git a/16/umbraco-cms/customizing/extending-overview/extension-types/dynamic-root-query-step.md b/16/umbraco-cms/customizing/extending-overview/extension-types/dynamic-root-query-step.md new file mode 100644 index 00000000000..e69de29bb2d diff --git a/16/umbraco-cms/customizing/extending-overview/extension-types/entity-actions.md b/16/umbraco-cms/customizing/extending-overview/extension-types/entity-actions.md new file mode 100644 index 00000000000..b91de56a43c --- /dev/null +++ b/16/umbraco-cms/customizing/extending-overview/extension-types/entity-actions.md @@ -0,0 +1,216 @@ +--- +description: Entity Actions perform an action on a specific item +--- + +# Entity Actions + +{% hint style="warning" %} +This page is a work in progress and may undergo further revisions, updates, or amendments. The information contained herein is subject to change without notice. +{% endhint %} + +{% hint style="info" %} +Entity Actions was previously called Tree Actions. +{% endhint %} + +Entity Actions is a feature that provides a generic place for secondary or additional functionality for an entity type. An entity type can be a media, document and so on. + +Items in an Umbraco Tree can have associated Actions. The actions visible to the currently logged in user can be controlled via User Permissions. + +You can set a User's permissions for each item in the Umbraco Content tree from the User Section of the Umbraco Backoffice. + +If you are developing a custom section or a custom Dashboard, you might want to display some different options. This depends on a User's permission set on a particular item. + +## Entity Actions in the UI + +
+ +

Sidebar Context Menu

+ + + +

Workspace Entity Action Menu

+ +
+ +
+ +

Collection

+ + + +

Pickers

+ +
+ +### Sidebar Context Menu + +Sidebar Context Menu is an entity action that can be performed on a menu item. For example in the content section you can perform some extra actions on the content such as sorting, moving, etc. + +

Default Entity Action in the Content Section

+ +## Registering an Entity Action + +```typescript +import { extensionRegistry } from '@umbraco-cms/extension-registry'; +import { MyEntityAction } from './entity-action'; + +const manifest = { + type: 'entityAction', + alias: 'My.EntityAction', + name: 'My Entity Action', + weight: 10, + api: MyEntityAction, + forEntityTypes: ['my-entity'], + meta: { + icon: 'icon-add', + label: 'My Entity Action', + repositoryAlias: 'My.Repository', + }, +}; + +extensionRegistry.register(manifest); +``` + +**Default Element** + +```typescript +interface UmbEntityActionElement {} +``` + +### The Entity Action Class + +As part of the Extension Manifest you can attach a class that will be instanciated as part of the action. It will have access to the host element, a repository with the given alias and the unique (key etc) of the entity. + +The class either provides a getHref method, or an execute method. If the getHref method is provided, the action will use the link. Otherwise the `execute` method will be used. When the action is clicked the `execute` method on the api class will be run. When the action is completed, an event on the host element will be dispatched to notify any surrounding elements. + +Example of providing a `getHref` method: + +```typescript +import { UmbEntityActionBase } from '@umbraco-cms/backoffice/entity-action'; +import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; +import type { MyRepository } from './my-repository'; + +export class MyEntityAction extends UmbEntityActionBase { + constructor(host: UmbControllerHostElement, repositoryAlias: string, unique: string) { + super(host, repositoryAlias, unique); + } + + async getHref() { + return 'my-link/path-to-something'; + } +} +``` + +Example of providing a `execute` method: + +```typescript +import { UmbEntityActionBase } from '@umbraco-cms/backoffice/entity-action'; +import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; +import type { MyRepository } from './my-repository'; + +export class MyEntityAction extends UmbEntityActionBase { + constructor(host: UmbControllerHostElement, repositoryAlias: string, unique: string) { + super(host, repositoryAlias, unique); + } + + async execute() { + await this.repository.myAction(this.unique); + } +} +``` + +If any additional contexts are needed, these can be consumed from the host element: + +```typescript +import { UmbEntityActionBase } from '@umbraco-cms/backoffice/entity-action'; +import { UmbContextConsumerController } from '@umbraco-cms/controller'; +import { UMB_MODAL_SERVICE_CONTEXT } from '@umbraco-cms/modal'; +import { MyRepository } from './my-repository'; + +export class MyEntityAction extends UmbEntityActionBase { + constructor(host: UmbControllerHostElement, repositoryAlias: string, unique: string) { + super(host, repositoryAlias, unique); + + new UmbContextConsumerController(this.host, UMB_MODAL_SERVICE_CONTEXT, (instance) => { + this.#modalService = instance; + }); + } + ... +} +``` + +We currently have a couple of generic actions that can be used across silos, so we don't have to write the same logic again. These actions include copy, move, trash, delete, etc. We can add more as we discover the needs. + +## User Permission Codes + +Here is a list of the entity actions and associated user permission codes shipped by Umbraco CMS and add-on projects, such as Umbraco Deploy. This list also includes codes used by some community packages. + +If you are building a package or adding custom entity actions to your solution, it's important to pick a permission letter. Ensure that it doesn't clash with one of these. + +If you have created a package using a custom entity action, please consider providing an update to this documentation page. You can do this via a PR to the [documentation repository](https://github.com/umbraco/UmbracoDocs). This will allow other developers to discover and avoid using the same permission letter. + +Currently, we allow two extension points on the client for user permissions: + +* **Entity User Permissions** - Relates to an entity (example document). + +

Entity User Permissions UI

+ +* **Granular User Permission** - Relates to a $type server schemaType. + +

Granular User Permission UI

+ +Each permission comes with a set of verbs, that will be checked against client and server-side. + +The Core currently ships with entity user permission for documents. The permissions are as follows: + +| Current Backoffice Letter | Verb | +| ------------------------- | -------------------------------- | +| C | Umb.Document.Create | +| F | Umb.Document.Read | +| A | Umb.Document.Update | +| D | Umb.Document.Delete | +| I | Umb.Document.CreateBlueprint | +| N | Umb.Document.Notifications | +| U | Umb.Document.Publish | +| R | Umb.Document.Permissions | +| Z | Umb.Document.Unpublish | +| O | Umb.Document.Duplicate | +| M | Umb.Document.Move | +| S | Umb.Document.Sort | +| I | Umb.Document.CultureAndHostnames | +| P | Umb.Document.PublicAccess | +| K | Umb.Document.Rollback | +| V | Umb.DocumentRecycleBin.Restore | + +**Entity User Permissions** will be registered in the extension registry with a manifest with the following type. Example: + +```typescript +{ + "type": "entityUserPermission", + "alias": "Umb.UserPermission.Document.Rollback", + "name": "Document Rollback User Permission", + "meta": { + "entityType": "document", + "verbs": ["Umb.Document.Rollback"], + "labelKey": "actions_rollback", + "descriptionKey": "actionDescriptions_rollback", + "group": "administration", + }, + }, +``` + +**Granular permissions** will also be registered. It is possible to provide a custom element to build the needed UX for that type of permission: + +```typescript +{ + "type": "userGranularPermission", + "alias": "Umb.UserGranularPermission.Document", + "name": "Document Granular User Permission", + "element": "element.js", + "meta": { + "schemaType": "DocumentPermissionPresentationModel", + "label": "Documents", + "description": "Assign permissions to specific documents", + }, + }, +``` diff --git a/16/umbraco-cms/customizing/extending-overview/extension-types/entity-bulk-actions.md b/16/umbraco-cms/customizing/extending-overview/extension-types/entity-bulk-actions.md new file mode 100644 index 00000000000..797b5aebc15 --- /dev/null +++ b/16/umbraco-cms/customizing/extending-overview/extension-types/entity-bulk-actions.md @@ -0,0 +1,57 @@ +# Entity Bulk Actions + +{% hint style="warning" %} +This page is a work in progress and may undergo further revisions, updates, or amendments. The information contained herein is subject to change without notice. +{% endhint %} + +**Entity Bulk Action:** Relates to an entity type: document, media, etc. Performs the action on a selection of items. + +

Entity Bulk Collection

+ +## Registering an Entity Bulk Action + +```typescript +import { extensionRegistry } from '@umbraco-cms/extension-registry'; +import { MyEntityBulkAction } from './entity-bulk-action'; + +const manifest = { + type: 'entityBulkAction', + alias: 'My.EntityBulkAction', + name: 'My Entity Bulk Action', + weight: 10, + api: MyEntityBulkAction, + meta: { + icon: 'icon-add', + label: 'My Entity Bulk Action', + repositoryAlias: 'My.Repository', + }, + conditions: [ + { + alias: 'Umb.Condition.CollectionAlias', + match: 'my-collection-alias', + }, + ], +}; + +extensionRegistry.register(manifest); +``` + +## The Entity Bulk Action Class + +As part of the Extension Manifest you can attach a class that will be instantiated as part of the action. It will have access to the host element, a repository with the given alias and the unique (key etc) of the entity. When the action is clicked the `execute` method on the api class will be run. When the action is completed, an event on the host element will be dispatched to notify any surrounding elements. + +```typescript +import { UmbEntityBulkActionBase } from '@umbraco-cms/entity-action'; +import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; +import { MyRepository } from './my-repository'; + +export class MyEntityBulkAction extends UmbEntityBulkActionBase { + constructor(host: UmbControllerHostElement, repositoryAlias: string, selection: Array) { + super(host, repositoryAlias, selection); + } + + async execute() { + await this.repository?.myBulkAction(this.selection); + } +} +``` diff --git a/16/umbraco-cms/customizing/extending-overview/extension-types/entity-create-option-action.md b/16/umbraco-cms/customizing/extending-overview/extension-types/entity-create-option-action.md new file mode 100644 index 00000000000..ec78f6ea732 --- /dev/null +++ b/16/umbraco-cms/customizing/extending-overview/extension-types/entity-create-option-action.md @@ -0,0 +1,67 @@ +# Entity Create Option Action + +An "Entity Create Option Action" is an additional option that can be added when creating an entity. For example, options like "Create Document Type" or "Create Document Type with Template" can be available when a Document Type is being created. + +These options will be displayed in a create options dialog when the "Create"-entity action is selected. The dialog will show the available options and allow the user to select one of them. + +To enable a "Create"-entity action to show the options dialog, use the 'create'-kind in the manifest when setting up the "Create"-entity action. This will display the options dialog if multiple options are available, or it will automatically execute the first option if only one is available. + +By using the "create"-kind for your create entity actions, you can ensure that your options are extendable by other developers. This also applies when you only have one option available + +The following code shows you to register a "Create"-entity action that can display a dialog with options: + +```typescript +const manifest = { + type: "entityAction", + kind: "create", + alias: "My.EntityAction", + name: "My Create Entity Action", + forEntityTypes: ["my-entity"], +}; +``` + +The following code demonstrates how to register an Entity Create Option Action. If only one option is available, it will be executed immediately. + +```typescript +const manifest = { + type: "entityCreateOptionAction", + alias: "My.EntityCreateOptionAction", + name: "My Create Option Action", + weight: 100, + api: () => import("./path-to-file.js"), + forEntityTypes: ["my-entity"], + meta: { + icon: "icon-unplug", + label: "My Create Option Action", + additionalOptions: false, + }, +}; +``` + +The following code shows how to implement the Create Action Option. + +```typescript +import { + UmbEntityCreateOptionActionBase, + type MetaEntityCreateOptionAction, + type UmbEntityCreateOptionActionArgs, +} from "@umbraco-cms/backoffice/entity-create-option-action"; +import type { UmbControllerHostElement } from "@umbraco-cms/backoffice/controller-api"; + +export class MyEntityCreateActionOption extends UmbEntityCreateOptionActionBase { + constructor( + host: UmbControllerHostElement, + args: UmbEntityCreateOptionActionArgs + ) { + super(host, args); + } + + override async execute() { + alert("My Create Option Action executed!"); + } +} +``` + +We currently support Create Options for the following entity types: + +- User diff --git a/16/umbraco-cms/customizing/extending-overview/extension-types/entry-point.md b/16/umbraco-cms/customizing/extending-overview/extension-types/entry-point.md new file mode 100644 index 00000000000..e69de29bb2d diff --git a/16/umbraco-cms/customizing/extending-overview/extension-types/global-context.md b/16/umbraco-cms/customizing/extending-overview/extension-types/global-context.md new file mode 100644 index 00000000000..744314d195c --- /dev/null +++ b/16/umbraco-cms/customizing/extending-overview/extension-types/global-context.md @@ -0,0 +1,24 @@ +--- +description: Establish the bond for extensions to communication across the application +--- + +# Global Context + +{% hint style="warning" %} +This page is a work in progress and may undergo further revisions, updates, or amendments. The information contained herein is subject to change without notice. +{% endhint %} + +A global context manages the logic code from your Lit Element controllers. + +## Registration of a Global Context + +You can register a global context like so: + +```typescript +{ + type: 'globalContext', + alias: 'My.GlobalContext.Products', + name: 'My Products Context', + api: 'my-products.context.js', +} +``` diff --git a/16/umbraco-cms/customizing/extending-overview/extension-types/granular-user-permissions.md b/16/umbraco-cms/customizing/extending-overview/extension-types/granular-user-permissions.md new file mode 100644 index 00000000000..e69de29bb2d diff --git a/16/umbraco-cms/customizing/extending-overview/extension-types/header-apps.md b/16/umbraco-cms/customizing/extending-overview/extension-types/header-apps.md new file mode 100644 index 00000000000..cffe04a2632 --- /dev/null +++ b/16/umbraco-cms/customizing/extending-overview/extension-types/header-apps.md @@ -0,0 +1,116 @@ +--- +description: Example how to work with extension registry +--- + +# Header Apps + +{% hint style="warning" %} +This page is a work in progress and may undergo further revisions, updates, or amendments. The information contained herein is subject to change without notice. +{% endhint %} + +A header app appears next to the user profile and global search icon on the top-right corner of Umbraco's backoffice. + +In this article, you can find an example of an extension registry. This example details the creation of a Header App in two ways, similar to how the extension registry can be created: + +1. [Using a manifest file](header-apps.md#button-header-app-with-manifest). +2. [Using the JavaScript/TypeScript file](header-apps.md#button-header-app-with-js-ts). + +

Header Apps

+ +## **Button Header App with Manifest** + +Below you can find an example of how to setup a Header App using the manifest file. + +1. Follow the [Vite Package Setup](../../development-flow/vite-package-setup.md) to create a header app and name it "`header-app`" when using the guide. +2. Create a manifest file named `umbraco-package.json` at the root of the `header-app` folder. Here we define and configure our dashboard. +3. Add the following code to `umbraco-package.json`: + +{% code title="umbraco-package.json" lineNumbers="true" %} +```typescript +{ + "$schema": "../../umbraco-package-schema.json", + "name": "My Header App", + "version": "0.1.0", + "extensions": [ + { + "type": "headerApp", + "alias": "My.HeaderApp", + "name": "My Header App", + "kind": "button", + + "meta": { + "label": "Hello Umbraco", + "icon": "icon-hearts", + "href": "https://umbraco.com/" + } + } + ] +} +``` +{% endcode %} + +* First we define the type which is a `headerApp`. Then we add a unique alias and a name to define the extension UI. +* Then we can define what kind of extension it is, where in this case we can use a pre-defined element called button. +* The button requires some metdata: an icon, label of the button (name of the button) and a link which opens once clicked. + +4. In the `header-app` folder run `npm run build` and then run the project. Then in the backoffice you will see our new Header App extension with **heart icon**: + +

Header App in the Backoffice registered via Manifest File

+ +## **Button Header App with JS/TS** + +Below you can find an example of how to setup a Header App using the TypeScript file. + +This is a continuation of the above steps from the **Button Header App with Manifest** example. We will register a new Header App directly from the .ts file. + +1. Add a reference to the .js file. Update the `umbraco-package.json` with the following: + +{% code title="umbraco-package.json" lineNumbers="true" %} +```typescript +{ + "$schema": "../../umbraco-package-schema.json", + "name": "My Header App", + "version": "0.1.0", + "extensions": [ + { + "type": "headerApp", + "alias": "My.HeaderApp", + "name": "My Header App", + "kind": "button", + "element": "/App_Plugins/header-app/dist/header-app.js", + "meta": { + "label": "Hello Umbraco", + "icon": "icon-hearts", + "href": "https://umbraco.com/" + } + } + ] +} +``` +{% endcode %} + +2. Replace the content of the `src/my-element.ts file` with the following: + +{% code title="my-element.ts" lineNumbers="true" %} +```typescript +import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry'; + +const manifest: UmbExtensionManifest = { + type: "headerApp", + alias: "My.HeaderApp.Documentation", + name: "My Header App Documentation", + kind: "button", + meta: { + label: "Hello Documentation", + icon: "icon-addressbook", + href: "https://docs.umbraco.com/" + } +}; + +umbExtensionsRegistry.register(manifest); +``` +{% endcode %} + +3. In the `header-app` folder run `npm run build` and then run the project. Then in the backoffice you will see our new Header App extension with **address book** **icon**: + +

Header App in Backoffice registered via ts File

diff --git a/16/umbraco-cms/customizing/extending-overview/extension-types/health-check.md b/16/umbraco-cms/customizing/extending-overview/extension-types/health-check.md new file mode 100644 index 00000000000..e69de29bb2d diff --git a/16/umbraco-cms/customizing/extending-overview/extension-types/icons.md b/16/umbraco-cms/customizing/extending-overview/extension-types/icons.md new file mode 100644 index 00000000000..d57653437e2 --- /dev/null +++ b/16/umbraco-cms/customizing/extending-overview/extension-types/icons.md @@ -0,0 +1,78 @@ +--- +description: Learn how to append and use Icons. +--- + +# Icons + +This article describes how to add and use more icons in your UI. + +{% hint style="warning" %} +This page is a work in progress and may undergo further revisions, updates, or amendments. The information contained herein is subject to change without notice. +{% endhint %} + +## Register a new set of icons + +New icons can be added via an extension type called `icons`. + +You must add a new manifest to the Extension API to register icons. The manifest can be added through the `umbraco-package.json` file as shown in the snippet below. + +{% code title="umbraco-package.json" %} + +```json +{ + "name": "MyPackage", + "extensions": [ + { + "type": "icons", + "alias": "MyPackage.Icons.Unicorn", + "name": "MyPackage Unicorn Icons", + "js": "/App_Plugins/MyPackage/Icons/icons.js" + } + ] +} +``` + +{% endcode %} + +The file set in the `js` field holds the details about your Icons. The file should resemble the following: + +{% code title="icons.js" %} + +```typescript +export default [ + { + name: "myPackage-unicorn", + path: () => import("./icon-unicorn.js"), + }, + { + name: "myPackage-giraffe", + path: () => import("./icon-giraffe.js"), + } +] +``` + +{% endcode %} + +The icon name needs to be prefixed to avoid collision with other icons. + +Each icon must define a path, either as a string or a dynamic import as shown above. This file must be a JavaScript file containing a default export of an SVG string. See an example of this in the code snippet below. + +{% code title="icon-unicorn.js" %} + +```typescript +export default ``; +``` + +{% endcode %} + +### Using Icons in your UI + +The `umb-icon` element is automatically able to consume any registered icon. + +The following example shows how to make a button using the above-registered icon. + +```html + + + +``` diff --git a/16/umbraco-cms/customizing/extending-overview/extension-types/kind.md b/16/umbraco-cms/customizing/extending-overview/extension-types/kind.md new file mode 100644 index 00000000000..37bb77f1da4 --- /dev/null +++ b/16/umbraco-cms/customizing/extending-overview/extension-types/kind.md @@ -0,0 +1,123 @@ +--- +description: A kind extension provides the preset for other extensions to use. +--- + +# Kind + +{% hint style="warning" %} +This page is a work in progress and may undergo further revisions, updates, or amendments. The information contained herein is subject to change without notice. +{% endhint %} + +A Kind is a preset configuration that can be inherited by extensions to ensure consistency and reduce redundancy. It defines a set of default properties or behaviors that extensions can adopt, making it easier to maintain and configure extensions that share similar functionality. + +A Kind is always linked to a specific extension type. Extensions using the same type and referencing a Kind automatically inherit its settings, ensuring uniformity across different extensions. + +## Benefits of Using a Kind + +- Reduces redundancy – Common settings are defined once and reused across extensions. +- Ensures consistency – Extensions using the same Kind follow a standardized structure and behavior. +- Simplifies extension definitions – Extensions inherit predefined properties, reducing manual configuration. + +## Kind Registration + +To register a Kind, use the same method as other extensions. The key properties that define a Kind registration are: + +- `type`: Always set to `kind`. +- `alias`: A unique identifier for the Kind. +- `matchType`: Specifies the extension type that the Kind applies to. +- `matchKind`: Defines the Kind alias, which extensions must reference. +- `manifest`: Contains the preset values that extensions will inherit. + +### Example: Registering a Button Kind for Header Apps + +The following example shows how to register a Button Kind for [**Header Apps**](../extension-types/header-apps.md). This kind provides a preset configuration for a button element that can be reused by other Header App extensions. + +```typescript +const manifest: ManifestKind = { + type: 'kind', + alias: 'Umb.Kind.MyButtonKind', // Unique alias for the Kind + matchType: 'headerApp', // Applies to Header App extensions + matchKind: 'button', // Defines the Kind alias + manifest: { + // Add default properties for the 'button' Kind + elementName: 'umb-header-app-button', + }, +}; +``` + +In this example: + +- `type` is set to 'kind' to register it as a Kind extension. +- `matchType` is 'headerApp', specifying that this Kind is for Header App extensions. +- `matchKind` is 'button', which is the alias of the Kind. +- The `manifest` contains default properties like elementName that extensions using this Kind will inherit. + +## Using the Kind in Other Extensions + +To use the Kind in other extensions, the extension must reference it by setting the `type` and `kind` properties. The extension will automatically inherit the Kind's properties. + +### Example: Header App Extension Using the Button Kind + +```typescript +const manifest = { + type: 'headerApp', // Extension type + kind: 'button', // References the 'button' Kind + name: 'My Header App Example', + alias: 'My.HeaderApp.Example', + meta: { + label: 'My Example', + icon: 'icon-home', + href: '/some/path/to/open/when/clicked', + }, +}; + +extensionRegistry.register(manifest); +``` + +In this example, the Header App extension uses the `kind: 'button'`, meaning it inherits the `elementName` defined in the Button Kind. The extension can still add custom properties (like metadata in this case) to further customize the behavior or appearance. + +## Kind Example + +Here’s an example of how to register and use the Button Kind in a Header App extension: + +```typescript +import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry'; + +const manifest: UmbExtensionManifest = { + type: 'kind', + alias: 'Umb.Kind.MyButtonKind', // Alias for the Kind + matchType: 'headerApp', // Extension type the Kind applies to + matchKind: 'button', // Defines the Kind alias + manifest: { + elementName: 'umb-header-app-button', + }, +}; + +umbExtensionsRegistry.register(manifest); +``` + +This code registers the Button Kind, so other Header App extensions using `type: 'headerApp'` and `kind: 'button'` will inherit the preset `elementName: 'umb-header-app-button'`. + +Now, another Header App extension can be created without defining `elementName`, as it will automatically inherit it from the Kind: + +```typescript +import { extensionRegistry } from '@umbraco-cms/extension-registry'; + +const manifest = { + type: 'headerApp', // Extension type + kind: 'button', // References the 'button' Kind + name: 'My Header App Example', + alias: 'My.HeaderApp.Example', + meta: { + label: 'My Example', + icon: 'icon-home', + href: '/some/path/to/open/when/clicked', + }, +}; + +extensionRegistry.register(manifest); +``` + +By referencing the Kind, the extension inherits shared properties like `elementName`, ensuring consistency and reducing redundancy across extensions. This method also makes it easier to update configurations across multiple extensions. + +By using Kinds, you can create reusable, standardized configurations for extensions, helping to streamline development, ensure consistency, and reduce duplication. Understanding how to register and reference Kinds effectively will enhance the maintainability of your Umbraco extensions. diff --git a/16/umbraco-cms/customizing/extending-overview/extension-types/localization.md b/16/umbraco-cms/customizing/extending-overview/extension-types/localization.md new file mode 100644 index 00000000000..9efc9ffb4e5 --- /dev/null +++ b/16/umbraco-cms/customizing/extending-overview/extension-types/localization.md @@ -0,0 +1,83 @@ +--- +description: Learn how to manage and use the Backoffice UI Localization files. +--- + +# Localization + +## Registering Localization + +When registering localizations to a language, you must add a new manifest to the Extension API. The manifest can be added through the `umbraco-package.json` file. + +{% hint style="info" %} +The `umbraco-package.json` file is only registered when placed directly in the `/App_Plugins/` or `/App_Plugins/{YourPackageName}` folder. It will not be recognized in nested subfolders. +{% endhint %} + +Usually, the localization keys are provided through a JavaScript module. In this example, we will use a file named `en.js`: + +{% code title="umbraco-package.json" %} +```json +{ + "name": "MyPackage", + "extensions": [ + { + "type": "localization", + "alias": "MyPackage.Localize.EnUS", + "name": "English", + "meta": { + "culture": "en" + }, + "js": "/App_Plugins/MyPackage/Localization/en.js" + } + ] +} +``` +{% endcode %} + +{% hint style="info" %} +Read more about extensions in the [Package Manifest](../../umbraco-package.md) article. +{% endhint %} + +## The Localization file + +The localization files for the UI are JavaScript modules with a default export containing a key-value structure organized in sections. + +{% code title="en.js" %} +```javascript +export default { + section: { + key1: 'value1', + key2: 'value2', + }, +}; +``` +{% endcode %} + +The sections and keys will be formatted into a map in Umbraco with the format `section_key1` and `section_key2.` These form the unique key they are requested. + +If you do not have many translations, you can also choose to include them directly in the meta-object using the `localizations` property: + +{% code title="umbraco-package.json" %} +```json +{ + "name": "MyPackage", + "extensions": [ + { + "type": "localization", + "alias": "MyPackage.Localize.EnUS", + "name": "English", + "meta": { + "culture": "en", + "localizations": { + "section": { + "key1": "value1", + "key2": "value2" + } + } + }, + } + ] +} +``` +{% endcode %} + +In this case, the `en.js` file is not required and we can remove the "js" property from the manifest. Only strings can be used in the meta-object. diff --git a/16/umbraco-cms/customizing/extending-overview/extension-types/menu-item.md b/16/umbraco-cms/customizing/extending-overview/extension-types/menu-item.md new file mode 100644 index 00000000000..e69de29bb2d diff --git a/16/umbraco-cms/customizing/extending-overview/extension-types/menu.md b/16/umbraco-cms/customizing/extending-overview/extension-types/menu.md new file mode 100644 index 00000000000..e305a7b8155 --- /dev/null +++ b/16/umbraco-cms/customizing/extending-overview/extension-types/menu.md @@ -0,0 +1,263 @@ +# Menu + +{% hint style="warning" %} +This page is a work in progress and may undergo further revisions, updates, or amendments. The information contained herein is subject to change without notice. +{% endhint %} + +

Menu

+ +## Creating a custom menu + +In this section, you can learn how to register and create a custom Menu for the Umbraco backoffice. + +### Manifest + +The manifest file can be created using either JSON or TypeScript. Both methods are shown below. + +{% tabs %} + +{% tab title="JSON" %} + +We can create the manifest using JSON in the `umbraco-package.json`. + +```json +{ + "type": "menu", + "alias": "My.Menu", + "name": "My Menu" +} +``` +{% endtab %} + +{% tab title="TypeScript" %} + +The manifest can also be written in TypeScript. + +For this TypeScript example we used a [Backoffice Entry Point](../../extending-overview/extension-types/backoffice-entry-point.md) extension to register the manifests. + +```typescript +import type { ManifestMenu } from '@umbraco-cms/backoffice/menu'; + +const menuManifest: Array = [ + { + type: 'menu', + alias: 'My.Menu', + name: 'My Menu' + } +]; +``` + +{% endtab %} + +{% endtabs %} + +# Menu Item + +

Menu Item

+ +Menu items are the items that appear in the menu. + +## Creating a custom menu items + +In this section, you can learn how to add custom Menu Items to your Umbraco backoffice Menu. + +### Manifest + +To add custom menu items, you can define a single MenuItem manifest and link an element to it. In this element, you can fetch the data and render as many menu items as you want based on that data. + +The code snippets below show how to declare a new menu item using JSON or TypeScript. + +{% tabs %} + +{% tab title="JSON" %} + +We can create the manifest using JSON in the `umbraco-package.json`. + +```json +{ + "type": "menuItem", + "alias": "My.MenuItem", + "name": "My Menu Item", + "element": "./menu-items.ts", + "meta": { + "label": "My Menu Item", + "menus": ["My.Menu"] + } +} +``` + +{% endtab %} + +{% tab title="TypeScript" %} + +The manifest can also be written in TypeScript. + +For this TypeScript example we used a [Backoffice Entry Point](../../extending-overview/extension-types/backoffice-entry-point.md) extension to register the manifests. + +{% code title="manifest.ts" overflow="wrap" lineNumbers="true" %} +```typescript +const menuItemManifest: Array = [ + { + type: 'menuItem', + alias: 'My.MenuItem', + name: 'My Menu Item', + meta: { + label: 'My Menu Item', + menus: ["My.Menu"] + }, + element: () => import('./menu-items.ts') + } +]; +``` +{% endcode %} + + +{% endtab %} + +{% endtabs %} + +### The UI Element + +#### Rendering menu items with Umbraco's UI menu item component + +To render your menu items in Umbraco, you can use the [Umbraco UI Menu Item component](https://uui.umbraco.com/?path=/docs/uui-menu-item--docs). This component allows you to create nested menu structures with a few lines of code. + +By default, you can set the `has-children` attribute to display the caret icon indicating nested items. It will look like this: `?has-children=${bool}`. + +**Example:** + +```tsx + + + + +``` + +#### Custom menu item element example + +You can fetch the data and render the menu items using the Lit element above. By putting the result of the fetch in a `@state()`, we can trigger a re-render of the component when the data is fetched. + +{% code title="menu-items.ts" overflow="wrap" lineNumbers="true" %} +```typescript +import type { UmbMenuItemElement } from '@umbraco-cms/backoffice/menu'; +import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; +import { html, TemplateResult, customElement, state } from '@umbraco-cms/backoffice/external/lit'; +import { MyMenuItemResponseModel, MyMenuResource } from '../../../api'; + +const elementName = 'my-menu-item'; + +@customElement(elementName) +class MyMenuItems extends UmbLitElement implements UmbMenuItemElement { + @state() + private _items: MyMenuItemResponseModel[] = []; // Store fetched items + + @state() + private _loading: boolean = true; // Track loading state + + @state() + private _error: string | null = null; // Track any errors + + override firstUpdated() { + this.fetchInitialItems(); // Start fetching on component load + } + + // Fetch initial items + async fetchInitialItems() { + try { + this._loading = true; + this._items = ((await MyMenuResource.getMenuApiV1()).items); // Fetch root-level items + } catch (e) { + this._error = 'Error fetching items'; + } finally { + this._loading = false; + } + } + + // Render items + renderItems(items: MyMenuItemResponseModel[]): TemplateResult { + return html` + ${items.map(element => html` + + ${element.type === 1 + ? html`` + : html``} + + ${element.hasChildren ? this.renderItems(element.children) : ''} + + `)} + `; + } + + // Main render function + override render() { + if (this._loading) { + return html``; + } + + if (this._error) { + return html` + `; + } + + // Render items if loading is done and no error occurred + return this.renderItems(this._items); + } +} + +export { MyMenuItems as element }; + +declare global { + interface HTMLElementTagNameMap { + [elementName]: MyMenuItems; + } +} + +``` +{% endcode %} + + +## Tree Menu Item + +### Manifest + +```json +// it will be something like this +{ + "type": "menuItem", + "kind": "tree", + "alias": "My.TreeMenuItem", + "name": "My Tree Menu Item", + "meta": { + "label": "My Tree Menu Item", + "menus": ["My.Menu"] + } +} +``` + +#### Default Element + +The default element supports rendering a subtree of menu items. + +```typescript +class UmbMenuItemTreeDefaultElement {} +``` + +### Adding menu items to an existing menu + +The backoffice comes with a couple of menus. + +* Content, Media, Settings, Templating, Dictionary, etc. + +To add a menu item to an existing menu, you can use the `meta.menus` property. + +```typescript +{ + "type": "menuItem", + "alias": "My.MenuItem", + "name": "My Menu Item", + "meta": { + "label": "My Menu Item", + "menus": ["Umb.Menu.Content"] + } +} +``` diff --git a/16/umbraco-cms/customizing/extending-overview/extension-types/mfa-login-provider.md b/16/umbraco-cms/customizing/extending-overview/extension-types/mfa-login-provider.md new file mode 100644 index 00000000000..e69de29bb2d diff --git a/16/umbraco-cms/customizing/extending-overview/extension-types/modals/README.md b/16/umbraco-cms/customizing/extending-overview/extension-types/modals/README.md new file mode 100644 index 00000000000..a5402abd2f6 --- /dev/null +++ b/16/umbraco-cms/customizing/extending-overview/extension-types/modals/README.md @@ -0,0 +1,104 @@ +--- +description: >- + A modal is a popup layer that darkens the surroundings and comes with a focus + lock. There are two types of modals: "dialog" and "sidebar". +--- + +# Modals + +{% hint style="warning" %} +This page is a work in progress and may undergo further revisions, updates, or amendments. The information contained herein is subject to change without notice. +{% endhint %} + +## **Modal Types** + +The Dialog modal appears in the middle of the screen. and the Sidebar Modal slide in from the right. + +## Modal Token + +For type safety, we recommend that you use Modal Tokens. The Modal Token binds the Modal Type with a Modal Data Type and a Modal Result Type. + +This can also come with defaults, for example, settings for the modal type and size. + +This is an example of a Modal Token declaration: + +```ts +import { UmbModalToken } from "@umbraco-cms/backoffice/modal"; + +export type OurSomethingPickerModalData = { + // We do not have any data to parse for this example +}; + +export type OurSomethingPickerModalValue = { + key: string; +}; + +export const MY_SOMETHING_PICKER_MODAL = new UmbModalToken< + OurSomethingPickerModalData, + OurSomethingPickerModalValue +>("Our.Modal.SomethingPicker", { + modal: { + type: "sidebar", + size: "small", + }, +}); +``` + +## Make a custom Modal Element + +To create your own modal, read the [Implementing a Custom Modal article](custom-modals.md) before proceeding with this article. + +### Basic Usage + +Consume the `UMB_MODAL_MANAGER_CONTEXT` and then use the modal manager context to open a modal. This example shows how to consume the Modal Manager Context: + +```ts +import { LitElement } from "@umbraco-cms/backoffice/external/lit"; +import { UmbElementMixin } from '@umbraco-cms/backoffice/element-api'; +import { + UMB_MODAL_MANAGER_CONTEXT, +} from "@umbraco-cms/backoffice/modal"; + +export class MyElement extends UmbElementMixin(LitElement) { + #modalManagerContext?: typeof UMB_MODAL_MANAGER_CONTEXT.TYPE; + + constructor() { + super(); + this.consumeContext(UMB_MODAL_MANAGER_CONTEXT, (instance) => { + this.#modalManagerContext = instance; + // modalManagerContext is now ready to be used. + }); + } +} +``` + +#### Open a modal + +A modal can be opened in two ways. Either you register the modal with a route or at runtime open the modal. The initial option allows users to deep-link to the modal, a potential preference in certain cases; otherwise, consider the latter. + +#### Directly open a Modal + +In this case, we use the Modal Token from above, this takes a key as its data. And if submitted then it returns the new key. + +```typescript +const modalContext = this.#modalManagerContext?.open(this, MY_SOMETHING_PICKER_MODAL, { + value: { + key: this.selectedKey, + }, +}); + +modalContext + ?.onSubmit() + .then((value) => { + this.selectedKey = value.key; + }) + .catch(() => undefined); +``` + +[See the implementing a Confirm Dialog for a more concrete example.](confirm-dialog.md) + +**Modal Route Registration** + +You can register modals with a route, making it possible to link directly to that specific modal. This also means the user can navigate back and forth in the browser history. This makes it an ideal solution for modals containing an editorial experience. + +For a more concrete example, check out the [Implementing a Confirm Dialog article](route-registration.md). diff --git a/16/umbraco-cms/customizing/extending-overview/extension-types/modals/confirm-dialog.md b/16/umbraco-cms/customizing/extending-overview/extension-types/modals/confirm-dialog.md new file mode 100644 index 00000000000..4546976d68c --- /dev/null +++ b/16/umbraco-cms/customizing/extending-overview/extension-types/modals/confirm-dialog.md @@ -0,0 +1,74 @@ +--- +description: Ask the user for confirmation +--- + +# Confirm Dialog + +{% hint style="warning" %} +This page is a work in progress and may undergo further revisions, updates, or amendments. The information contained herein is subject to change without notice. +{% endhint %} + +This example shows how to open a confirm dialog. The `UMB_CONFIRM_MODAL` is a token that represents the confirm dialog. The `open` method takes the token and an object with the data for the confirm dialog. The `onSubmit` method returns a promise that resolves when the user confirms the dialog and rejects when the user cancels the dialog. + +The confirm modal itself is built-in and does not need to be registered in the extension registry. + +The modal token describes the options that you can pass to the modal. The confirm modal token has the following properties: + +* `headline` - The headline of the modal. +* `content` - The content of the modal, which can be a TemplateResult or a string. +* `color` - (Optional) The color of the modal. This can be `positive` or `danger`. +* `confirmLabel` - (Optional) The label of the confirm button. + +## Basic Usage + +{% code title="my-element.ts" %} +```typescript +import { html, LitElement, customElement } from "@umbraco-cms/backoffice/external/lit"; +import { UmbElementMixin } from '@umbraco-cms/backoffice/element-api'; +import { UMB_MODAL_MANAGER_CONTEXT, UMB_CONFIRM_MODAL } from '@umbraco-cms/backoffice/modal'; + +@customElement('my-element') +export class MyElement extends UmbElementMixin(LitElement) { + #modalManagerContext?: typeof UMB_MODAL_MANAGER_CONTEXT.TYPE; + + constructor() { + super(); + this.consumeContext(UMB_MODAL_MANAGER_CONTEXT, (instance) => { + this.#modalManagerContext = instance; + }); + } + + #onRequestDisable() { + const modalContext = this.#modalManagerContext?.open( + this, UMB_CONFIRM_MODAL, + { + data: { + headline: `${this.localize.term("actions_disable")}`, + content: `${this.localize.term( + "defaultdialogs_confirmdisable" + )}`, + color: "danger", + confirmLabel: "Disable", + } + } + ); + modalContext + ?.onSubmit() + .then(() => { + console.log("User has approved"); + }) + .catch(() => { + console.log("User has rejected"); + }); + } + + render() { + return html``; + } +} +``` +{% endcode %} diff --git a/16/umbraco-cms/customizing/extending-overview/extension-types/modals/custom-modals.md b/16/umbraco-cms/customizing/extending-overview/extension-types/modals/custom-modals.md new file mode 100644 index 00000000000..321382a9159 --- /dev/null +++ b/16/umbraco-cms/customizing/extending-overview/extension-types/modals/custom-modals.md @@ -0,0 +1,147 @@ +--- +description: >- + New modals can be added to the system via the extension registry. This article + goes through how this is done. +--- + +# Custom Modals + +{% hint style="warning" %} +This page is a work in progress and may undergo further revisions, updates, or amendments. The information contained herein is subject to change without notice. +{% endhint %} + +There are two parts to creating a custom modal: + +* First, you need to create a modal element which you need to register in the extension registry. +* Second, you need to create and export a modal token. + +## Create a modal token + +A modal token is a string that identifies a modal. This is the modal extension alias. It is used to open a modal and is also to set default options for the modal. It should also have a unique alias to avoid conflicts with other modals. + +```ts +import { UmbModalToken } from '@umbraco-cms/backoffice/modal'; + +export type MyModalData = { + headline: string; +} + +export type MyModalValue = { + myData: string; +} + +export const MY_MODAL_TOKEN = new UmbModalToken('My.Modal', { + modal: { + type: 'sidebar', + size: 'small' + } +}); +``` + +A modal token is a generic type that takes two type arguments. The first is the type of the data that is passed to the modal when it is opened. The second is the type of the value that is returned when the modal is closed. + +## Create a modal element + +A modal element is a web component that is used to render the modal. It should implement the `UmbModalExtensionElement` interface. The modal context is injected into the element when the modal is opened in the `modalContext` property. The modal context is used to close the modal, update the value and submit the modal. + +Additionally, the modal element can see its data parameters through the `modalContext` property. In this example, the modal data is of type `MyModalData` and the modal value is of type `MyModalValue`. The modal context is of type `UmbModalContext`. We are using the data to render a headline and the value to update the value and submit the modal. + +{% code title="my-modal.element.ts" %} +```ts +import { customElement, html, property } from '@umbraco-cms/backoffice/external/lit'; +import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; +import { UmbModalExtensionElement } from '@umbraco-cms/backoffice/modal'; +import type { UmbModalContext } from '@umbraco-cms/backoffice/modal'; +import type { MyModalData, MyModalValue } from './my-modal.token.js'; + +@customElement('my-dialog') +export default class MyDialogElement + extends UmbLitElement + implements UmbModalExtensionElement { + + @property({ attribute: false }) + modalContext?: UmbModalContext; + + @property({ attribute: false }) + data?: MyModalData; + + private _handleCancel() { + this.modalContext?.submit(); + } + + private _handleSubmit() { + this.modalContext?.updateValue({ myData: 'hello world' }); + this.modalContext?.submit(); + } + + render() { + return html` +
+

${this.modalContext?.data.headline ?? 'Default headline'}

+ + +
+ `; + } +} +``` +{% endcode %} + +## Register in the extension registry + +The modal element needs to be registered in the extension registry. This is done by defining the modal in the manifest file. The `element` property should point to the file that contains the modal element. + +```json +{ + "type": "modal", + "alias": "My.Modal", + "name": "My Modal", + "element": "../path/to/my-modal.element.js" +} +``` + +## Open the modal + +To open the modal, you need to consume the `UmbModalManagerContext` and then use the modal manager context to open a modal. This example shows how to consume the Modal Manager Context: + +{% code title="my-element.ts" %} +```ts +import { customElement, html } from '@umbraco-cms/backoffice/external/lit'; +import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; +import { MY_MODAL_TOKEN } from './my-modal.token.js'; +import { UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal'; + +@customElement('my-element') +class MyElement extends UmbLitElement { + #modalManagerContext?: typeof UMB_MODAL_MANAGER_CONTEXT.TYPE; + + constructor() { + super(); + this.consumeContext(UMB_MODAL_MANAGER_CONTEXT, (instance) => { + this.#modalManagerContext = instance; + // modalManagerContext is now ready to be used. + }); + } + + override render() { + return html` + + `; + } + + private _openModal() { + this.#modalManagerContext?.open(this, MY_MODAL_TOKEN, { + data: { + headline: "My modal headline", + }, + }); + } +} + +declare global { + interface HTMLElementTagNameMap { + 'my-element': MyElement; + } +} +``` +{% endcode %} diff --git a/16/umbraco-cms/customizing/extending-overview/extension-types/modals/route-registration.md b/16/umbraco-cms/customizing/extending-overview/extension-types/modals/route-registration.md new file mode 100644 index 00000000000..82245a956de --- /dev/null +++ b/16/umbraco-cms/customizing/extending-overview/extension-types/modals/route-registration.md @@ -0,0 +1,126 @@ +# Route Registration + +{% hint style="warning" %} +This page is a work in progress and may undergo further revisions, updates, or amendments. The information contained herein is subject to change without notice. +{% endhint %} + +You can register modals with a route, making it possible to link directly to that specific modal. This also means the user can navigate back and forth in the browser history. + +A modal can be registered via the `UmbModalRouteRegistrationController`. The registration accepts a modal token (or extension alias). + +```ts +this.myModalRegistration = new UmbModalRouteRegistrationController( + this, + UMB_LINK_PICKER_MODAL +) + .onSubmit((submitData) => { + console.log("Modal submitted with data".submitData); + }) + .observeRouteBuilder((routeBuilder) => { + this._modalRouteBuilder = routeBuilder; + }); +``` + +The registration holds an instance of its `UmbModalHandler` when the modal is active. The modal registration accepts 4 different callbacks: + +* `onSetup` - called when the modal is opened +* `onSubmit` - called when the modal is submitted +* `onReject` - called when the modal is rejected +* `observeRouteBuilder` - called when the modal route changes. Use the given route builder to build a route to open the modal + +**Additional features of the route Registration:** + +* Adds unique parts to the path. +* A modal registered in a dashboard can be setup in few steps +* A modal registered in a property editor needs to become specific for the property and the variant of that property. +* Builds some data for the setup. +* Rejects a modal by returning false in setup. +* Uses a parameter as part of the setup to determine the data going to the modal. + +## Modal registration for UI as part of a Property Editor + +When configuring a routed modal from a Property Editor, it's important to be aware of some facts. Those facts are that the Property Editor shares the same URL path as other Property Editors. This means we need to ensure the registration is unique so it doesn't collide with other Property Editors. To do so we will make use of the Property Alias and the Variant ID. + +```ts + + + @property() + public set alias(value: string | undefined) { + this.myModalRegistration.setUniquePathValue('propertyAlias', value); + } + + @property() + public set variantId(value: string | UmbVariantId | undefined) { + this.myModalRegistration.setUniquePathValue('variantId', value?.toString()); + } + + private _items = [ + { name: 'Item 1' }, + { name: 'Item 2' }, + { name: 'Item 3' }, + ] + + + constructor() { + super(); + + this.myModalRegistration = new UmbModalRouteRegistrationController( + this, + MY_MODAL_TOKEN + ) + .addAdditionalPath(`:index`) + .addUniquePaths(['propertyAlias', 'variantId']) + .onSetup((params) => { + // Get item index: + const indexParam = params.index; + if (!indexParam) return false; + let index: number | null = parseInt(params.index); + if (Number.isNaN(index)) return false; + + // Use the index to find data: + let data = null; + if (index >= 0 && index < this._items.length) { + data = this._items[index]; + } else { + // If not then make a new pick: + index = null; + } + + return { + index: index, + itemData: { + name: data?.name + }, + }; + }) + .onSubmit((submitData) => { + if (!submitData) return; + this._items[submitData.index] = submitData.itemData; + }) + .observeRouteBuilder((routeBuilder) => { + this._modalRouteBuilder = routeBuilder; + }); + } + + render() { + return html` + ${this._items?.map((item, index) => + html`Add` + )} + `; + } +``` + +**Generate the URL to a Modal Route Registration** + +The Modal registration has an option to retrieve a URL Builder. This is a function that can be used to generate a URL to a modal: + +```ts +const modalLink = _myUrlBuilder?.({ alias: "my-input-alias" }); +``` + +The `modalLink` from above could look like this: `/umbraco/backoffice/my/location/modal/Our.Modal.SomethingPicker/my-input-alias` + +Notice the Property Editor registration will add the property alias and variant ID to the URL, so it becomes: + +`/umbraco/backoffice/my/location/modal/Our.Modal.SomethingPicker/my-property-alias/en-us/my-input-alias` diff --git a/16/umbraco-cms/customizing/extending-overview/extension-types/monaco-markdown-editor-action.md b/16/umbraco-cms/customizing/extending-overview/extension-types/monaco-markdown-editor-action.md new file mode 100644 index 00000000000..e69de29bb2d diff --git a/16/umbraco-cms/customizing/extending-overview/extension-types/package-view.md b/16/umbraco-cms/customizing/extending-overview/extension-types/package-view.md new file mode 100644 index 00000000000..e69de29bb2d diff --git a/16/umbraco-cms/customizing/extending-overview/extension-types/preview-app-provider.md b/16/umbraco-cms/customizing/extending-overview/extension-types/preview-app-provider.md new file mode 100644 index 00000000000..e69de29bb2d diff --git a/16/umbraco-cms/customizing/extending-overview/extension-types/property-action.md b/16/umbraco-cms/customizing/extending-overview/extension-types/property-action.md new file mode 100644 index 00000000000..e69de29bb2d diff --git a/16/umbraco-cms/customizing/extending-overview/extension-types/property-editor-schema.md b/16/umbraco-cms/customizing/extending-overview/extension-types/property-editor-schema.md new file mode 100644 index 00000000000..e69de29bb2d diff --git a/16/umbraco-cms/customizing/extending-overview/extension-types/property-editor-ui.md b/16/umbraco-cms/customizing/extending-overview/extension-types/property-editor-ui.md new file mode 100644 index 00000000000..e69de29bb2d diff --git a/16/umbraco-cms/customizing/extending-overview/extension-types/property-value-preset.md b/16/umbraco-cms/customizing/extending-overview/extension-types/property-value-preset.md new file mode 100644 index 00000000000..f5535e6339d --- /dev/null +++ b/16/umbraco-cms/customizing/extending-overview/extension-types/property-value-preset.md @@ -0,0 +1,88 @@ +--- +description: Provide a preset value for a Property. +--- + +# Property Value Preset + +The Property Value Preset is an Extension Type that uses an API to provide a Preset Value. The preset value is used when a user scaffolds a new set of Content. + +The following Manifest declares a preset for the `Umb.PropertyEditorUi.TextBox` Property Editor UI: + +```typescript +export const manifest = { + type: 'propertyValuePreset'; + alias: 'my.propertyValuePreset.TextBox', + name: 'My Property Value Preset for TextBox', + api: () => import('./my-property-value-preset.js'), + forPropertyEditorUiAlias: 'Umb.PropertyEditorUi.TextBox' +} +``` + +Such Property Preset Value API could look like this: + +{% code title="my-property-value-preset.js" %} +```typescript +export class MyPropertyValuePresetApi implements UmbPropertyValuePreset { + async processValue(value: undefined | string, config: UmbPropertyEditorConfig) { + return value ? value : 'Hello there'; + } + + destroy(): void {} +} + +export { UmbTrueFalsePropertyValuePreset as api }; +``` +{% endcode %} + +This API will set the value to "Hello there" for all properties using the `Umb.PropertyEditorUi.TextBox` Property Editor UI. + +### Target a Property Editor Schema + +You can also choose to target your Preset for a Property Editor Schema.\ +\ +Define `forPropertyEditorSchemaAlias` to show the Preset Value for all Properties based on that Schema. + +If both `forPropertyEditorSchemaAlias` and `forPropertyEditorUiAlias` are defined, it will not limit the target. The matching is independently for each of them. + +Notice that `forPropertyEditorSchemaAlias` only targets the Properties used on the Content Type based data. This could affect Documents, Media, Members, and Blocks, and not properties of a Data Type Configuration. + +## Utilize the Data-Type configuration + +The `processValue` method takes two arguments, the current value of the Preset and the Data-Type Configuration. + +The following example is the built-in Property Value Preset for the Umbraco Toggle. The Toggle Data Type has a 'preset state' configuration that is used as the value of the Toggle. + +{% code title="my-property-value-preset.js" %} +```typescript +export class UmbTrueFalsePropertyValuePreset + implements UmbPropertyValuePreset +{ + async processValue(value: undefined | UmbTogglePropertyEditorUiValue, config: UmbPropertyEditorConfig) { + const initialState = (config.find((x) => x.alias === 'presetState')?.value as boolean | undefined) ?? false; + return value !== undefined ? value : initialState; + } + + destroy(): void {} +} + +export { UmbTrueFalsePropertyValuePreset as api }; +``` +{% endcode %} + +## Utilize anything + +The `processValue` method is async. You can request the server or use the Context-API to retrieve the necessary information to construct your value. + +{% hint style="info" %} +**Only relevant for Umbraco 16 (release date: June 12th, 2025)** + +For retrieving contexts, upgrading to version 16, where the `getContext` method will have a timeout feature is recommended. In this case, such will be needed for the preset not to get stuck if the context is unavailable when the reset is constructed. +{% endhint %} + +## Extend Presets + +Because the `processValue` method takes a value as its first argument, you can append the value constructed by other Presets. In this way, multiple Presets can shape the preset value for a property. + +In the case of multiple Property Value Presets targeting the same Property. The `weight` of the Manifest determines the order they are executed. + +This opens up for you to overwrite or alter the Preset Value for Properties that use a Built-in Property Value Preset. diff --git a/16/umbraco-cms/customizing/extending-overview/extension-types/search-provider.md b/16/umbraco-cms/customizing/extending-overview/extension-types/search-provider.md new file mode 100644 index 00000000000..e69de29bb2d diff --git a/16/umbraco-cms/customizing/extending-overview/extension-types/search-result-item.md b/16/umbraco-cms/customizing/extending-overview/extension-types/search-result-item.md new file mode 100644 index 00000000000..e69de29bb2d diff --git a/16/umbraco-cms/customizing/extending-overview/extension-types/sections/README.md b/16/umbraco-cms/customizing/extending-overview/extension-types/sections/README.md new file mode 100644 index 00000000000..b3c2aa0ce97 --- /dev/null +++ b/16/umbraco-cms/customizing/extending-overview/extension-types/sections/README.md @@ -0,0 +1,6 @@ +--- +description: >- + An overview of the availabe extension types related to sections. +--- + +# Extension Types: Sections diff --git a/16/umbraco-cms/customizing/extending-overview/extension-types/sections/section-route.md b/16/umbraco-cms/customizing/extending-overview/extension-types/sections/section-route.md new file mode 100644 index 00000000000..e69de29bb2d diff --git a/16/umbraco-cms/customizing/extending-overview/extension-types/sections/section-sidebar.md b/16/umbraco-cms/customizing/extending-overview/extension-types/sections/section-sidebar.md new file mode 100644 index 00000000000..3b008c02fe2 --- /dev/null +++ b/16/umbraco-cms/customizing/extending-overview/extension-types/sections/section-sidebar.md @@ -0,0 +1,75 @@ +# Section Sidebar + +{% hint style="warning" %} +This page is a work in progress and may undergo further revisions, updates, or amendments. The information contained herein is subject to change without notice. +{% endhint %} + +

Section Sidebar

+ +## Section Sidebar Apps + +

Section Sidebar Apps

+ +**Manifest** + +```typescript +{ + "type": "sectionSidebarApp", + "alias": "My.SectionSidebarApp", + "name": "My Section Sidebar App", + "meta": { + "sections": ["My.Section"] + } +} +``` + +**Default Element** + +```typescript +interface UmbSectionSidebarAppElement {} +``` + +## **Menu Sidebar App** + +**Sidebar Menu**: + +* The Backoffice comes with a menu sidebar app that can be used to create a menu in the sidebar. +* To register a new menu sidebar app, add the following to your manifest +* The menu sidebar app will reference a menu that you have registered in the menu with a menu manifest + +

Menu Sidebar App

+ +**Manifest** + +```typescript +{ + "type": "sectionSidebarApp", + "kind": "menu", + "alias": "My.SectionSidebarApp.MyMenu", + "name": "My Menu Section Sidebar App", + "meta": { + "label": "My Sidebar Menu", + "menu": "My.Menu" + }, + "conditions": [ + { + "alias": "Umb.Condition.SectionAlias", + "match": "My.Section" + } + ] +} +``` + +**Default Element** + +```typescript +interface UmbMenuSectionSidebarAppElement {} +``` + +**Adding Items to an existing menu** + +This will make it possible to compose a sidebar menu from multiple Apps: + +

Composed sidebar menu

+ +You can read more about this in the [Menu](../menu.md) article. diff --git a/16/umbraco-cms/customizing/extending-overview/extension-types/sections/section-view.md b/16/umbraco-cms/customizing/extending-overview/extension-types/sections/section-view.md new file mode 100644 index 00000000000..39be2284a63 --- /dev/null +++ b/16/umbraco-cms/customizing/extending-overview/extension-types/sections/section-view.md @@ -0,0 +1,119 @@ +--- +description: >- + Append a secondary view for a Section, use it for additional features or + information. +--- + +# Section View + +{% hint style="warning" %} +This page is a work in progress and may undergo further revisions, updates, or amendments. The information contained herein is subject to change without notice. +{% endhint %} + +

Section View

+ +## Creating a custom Section View + +In this section, you can learn how to register and create a custom Section View for the Umbraco backoffice. + +### Manifest + +The manifest file can be created using either JSON or TypeScript. Both methods are shown below. + +{% tabs %} +{% tab title="Json" %} +We can create the manifest using json in the `umbraco-package.json`. + +```json +{ + "type": "sectionView", + "alias": "My.SectionView", + "name": "My Section View", + "element": "./my-section.element.js", + "meta": { + "label": "My View", + "icon": "icon-add", + "pathname": "my-view" + }, + "conditions": [ + { + "alias": "Umb.Condition.SectionAlias", + "match": "My.Section", + } + ] +} +``` +{% endtab %} + +{% tab title="TypeScript" %} +The manifest can also be written in TypeScript. + +For this TypeScript example we used a [Backoffice Entry Point](../backoffice-entry-point.md) extension to register the manifests. + +```typescript +import { ManifestSectionView } from '@umbraco-cms/backoffice/section'; + +const sectionViews: Array = [ + { + type: "sectionView", + alias: "My.SectionView", + name: "My Section View", + element: () => import('./my-section.element.ts'), + meta: { + label: "My View", + icon: "icon-add", + pathname: "my-view", + }, + conditions: [ + { + alias: 'Umb.Condition.SectionAlias', + match: 'My.Section', + } + ] + } +] +``` +{% endtab %} +{% endtabs %} + +### Lit Element + +Creating the Section View Element using a Lit Element. + +**my-section.element.ts:** + +```typescript +import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; +import { css, html, customElement, property } from '@umbraco-cms/backoffice/external/lit'; + +@customElement('my-sectionview-element') +export class MySectionViewElement extends UmbLitElement { + + override render() { + return html` + + Sectionview content goes here + + ` + } + + static override readonly styles = [ + css` + :host { + display: block; + padding: 20px; + } + `, + ]; + +} + +export default MySectionViewElement; + +declare global { + interface HTMLElementTagNameMap { + 'my-sectionview-element': MySectionViewElement; + } +} + +``` diff --git a/16/umbraco-cms/customizing/extending-overview/extension-types/sections/section.md b/16/umbraco-cms/customizing/extending-overview/extension-types/sections/section.md new file mode 100644 index 00000000000..ea01a688e02 --- /dev/null +++ b/16/umbraco-cms/customizing/extending-overview/extension-types/sections/section.md @@ -0,0 +1,76 @@ +--- +description: A guide to creating a section +--- + +# Sections + +{% hint style="warning" %} +This page is a work in progress and may undergo further revisions, updates, or amendments. The information contained herein is subject to change without notice. +{% endhint %} + +The Umbraco backoffice consists of Sections. Section is the main division shown in the top navigation. + +For example, when you load the backoffice, you'll see the 'Content' section, 'Settings' section, and so on. + +You can create your own sections to extend Umbraco with room for more features. + +

Section

+ +## **Creating a section** + +### **Manifests** + +When creating a new section it's recommended to use a [Entry Point](../backoffice-entry-point.md)-extension in your [Umbraco Package Manifest](../../../umbraco-package.md). This is to get better control over all the additional extensions required for the new section. + +Create a section by defining a manifest for it: + +```typescript +{ + "type": "section", + "alias": "My.Section", + "name": "My Section", + "meta": { + "label": "My.Section", + "pathname": "my-section" + } +} +``` + +Once registered, you will be able to select this action for your User Group Permissions. Once that is permitted, you can view your section. + +

Section

+ +#### **Extend with Sidebar, Dashboards and more** + +Once a section is registered, it can be extended like any other section. + +Here is a list of appropriate extensions to append to your section: + +* [Creating a Custom Dashboard](../../../../tutorials/creating-a-custom-dashboard/) +* [Section Sidebar](section-sidebar.md) +* [Section View](section-view.md) + +#### **Manifest with empty element** + +If you prefer full control over the content of your section you can choose to define an element for the content of your section. + +{% hint style="warning" %} +This is not recommended as it limits the content of your section to this element. Instead, it is recommended to use a single Dashboard or Section View. +{% endhint %} + +If you like to have full control, you can define an element like this: + +```typescript +const section : UmbExtensionManifest = { + type: "section", + alias: "Empty.Section", + name : 'Empty Section', + element : () => import('./empty-section.element.js'), + meta : { + label : 'Empty Section', + pathname : 'empty-section' + } +} +``` + +The element file must have an `element` or `default` export, or specify the element name in the `elementName` field. diff --git a/16/umbraco-cms/customizing/extending-overview/extension-types/stores-and-repositories/README.md b/16/umbraco-cms/customizing/extending-overview/extension-types/stores-and-repositories/README.md new file mode 100644 index 00000000000..a87681d96ef --- /dev/null +++ b/16/umbraco-cms/customizing/extending-overview/extension-types/stores-and-repositories/README.md @@ -0,0 +1,6 @@ +--- +description: >- + An overview of the available extension types related to stores and repositories. +--- + +# Extension Types: Stores and repositories diff --git a/16/umbraco-cms/customizing/extending-overview/extension-types/stores-and-repositories/item-store.md b/16/umbraco-cms/customizing/extending-overview/extension-types/stores-and-repositories/item-store.md new file mode 100644 index 00000000000..e69de29bb2d diff --git a/16/umbraco-cms/customizing/extending-overview/extension-types/stores-and-repositories/repository.md b/16/umbraco-cms/customizing/extending-overview/extension-types/stores-and-repositories/repository.md new file mode 100644 index 00000000000..e69de29bb2d diff --git a/16/umbraco-cms/customizing/extending-overview/extension-types/stores-and-repositories/store.md b/16/umbraco-cms/customizing/extending-overview/extension-types/stores-and-repositories/store.md new file mode 100644 index 00000000000..e69de29bb2d diff --git a/16/umbraco-cms/customizing/extending-overview/extension-types/stores-and-repositories/tree-store.md b/16/umbraco-cms/customizing/extending-overview/extension-types/stores-and-repositories/tree-store.md new file mode 100644 index 00000000000..e69de29bb2d diff --git a/16/umbraco-cms/customizing/extending-overview/extension-types/theme.md b/16/umbraco-cms/customizing/extending-overview/extension-types/theme.md new file mode 100644 index 00000000000..e69de29bb2d diff --git a/16/umbraco-cms/customizing/extending-overview/extension-types/tiny-mce-plugin.md b/16/umbraco-cms/customizing/extending-overview/extension-types/tiny-mce-plugin.md new file mode 100644 index 00000000000..e69de29bb2d diff --git a/16/umbraco-cms/customizing/extending-overview/extension-types/tree-item.md b/16/umbraco-cms/customizing/extending-overview/extension-types/tree-item.md new file mode 100644 index 00000000000..e69de29bb2d diff --git a/16/umbraco-cms/customizing/extending-overview/extension-types/tree.md b/16/umbraco-cms/customizing/extending-overview/extension-types/tree.md new file mode 100644 index 00000000000..705fe19ac59 --- /dev/null +++ b/16/umbraco-cms/customizing/extending-overview/extension-types/tree.md @@ -0,0 +1,141 @@ +--- +description: A guide to creating a custom tree in Umbraco +--- + +# Trees + +{% hint style="warning" %} +This page is a work in progress and may undergo further revisions, updates, or amendments. The information contained herein is subject to change without notice. +{% endhint %} + +The tree is a hierarchical structure of nodes and is registered in the Backoffice extension registry. A tree can be rendered anywhere in the Backoffice with the help of the umb-tree element. + +## Creating trees + +To Create a Tree in a section of the Umbraco backoffice, you need to take multiple steps: + +### Registering a tree + +The backoffice comes with two different tree item kinds out of the box: entity and fileSystem. + +Tree Manifest: + +```typescript +// TODO: add interface +{ + "type": "tree", + "alias": "My.Tree.Alias", + "name": "My Tree", + "meta": { + "repositoryAlias": "My.Repository.Alias" + } +}, +{ + "type": "treeItem", + "kind": "entity", + "alias": "My.TreeItem.Alias", + "name": "My Tree Item", + "conditions": { + "entityType": "my-entity-type", + }, +} +``` + +### Rendering a tree + +```typescript + +``` + +### Render a Custom Tree Item + +#### **The Tree Item Manifest** + +```typescript +{ + "type": "treeItem", + "alias": "Umb.TreeItem.Alias", + "name": "My Tree Item", + "element": "./my-tree-item.element.js", + "conditions": { + "entityType": "my-entity-type", + }, +}; +``` + +#### The Tree Item Element + +```typescript +import { css, html, nothing } from 'lit'; +import { customElement, property } from 'lit/decorators.js'; +import { UmbElementMixin } from '@umbraco-cms/backoffice/element-api'; +import { UmbMyTreeItemContext, MyTreeItemDataModel } from './my-tree-item.context'; + +@customElement('my-tree-item') +export class MyTreeItemElement extends UmbElementMixin(LitElement) { + private _item?: MyTreeItemDataModel; + @property({ type: Object, attribute: false }) + public get item() { + return this._item; + } + public set item(value: MyTreeItemDataModel | undefined) { + this._item = value; + this.#context.setTreeItem(value); + } + + #context = new UmbMyTreeItemContext(this); + + render() { + if (!this.item) return nothing; + return html` Some custom markup `; + } +} + +export default MyTreeItemElement; +``` + +#### The Tree Item Context + +```typescript +// TODO: auto-generate this from the interface +export interface UmbTreeItemContext { + host: UmbControllerHostElement; + unique?: string; + type?: string; + + treeItem: Observable; + hasChildren: Observable; + isLoading: Observable; + isSelectable: Observable; + isSelected: Observable; + isActive: Observable; + hasActions: Observable; + path: Observable; + + setTreeItem(treeItem: T | undefined): void; + + requestChildren(): Promise<{ + data: PagedResponse | undefined; + error: ProblemDetails | undefined; + asObservable?: () => Observable; + }>; + toggleContextMenu(): void; + select(): void; + deselect(): void; + constructPath(pathname: string, entityType: string, unique: string): string; +} +``` + +#### Extending the Tree Item Context base + +We provide a base class for the tree item context. This class provides some default implementations for the context. You can extend this class to overwrite any of the default implementations. + +```typescript +export class UmbMyTreeItemContext extends UmbTreeItemContextBase { + constructor(host: UmbControllerHostElement) { + super(host, (x: MyTreeItemDataModel) => x.unique); + } + + // overwrite any methods or properties here if needed +} +``` diff --git a/16/umbraco-cms/customizing/extending-overview/extension-types/ufm-component.md b/16/umbraco-cms/customizing/extending-overview/extension-types/ufm-component.md new file mode 100644 index 00000000000..e69de29bb2d diff --git a/16/umbraco-cms/customizing/extending-overview/extension-types/user-profile-app.md b/16/umbraco-cms/customizing/extending-overview/extension-types/user-profile-app.md new file mode 100644 index 00000000000..e69de29bb2d diff --git a/16/umbraco-cms/customizing/extending-overview/extension-types/workspaces/README.md b/16/umbraco-cms/customizing/extending-overview/extension-types/workspaces/README.md new file mode 100644 index 00000000000..5581b2339ac --- /dev/null +++ b/16/umbraco-cms/customizing/extending-overview/extension-types/workspaces/README.md @@ -0,0 +1,6 @@ +--- +description: >- + An overview of the available extension types related to workspaces. +--- + +# Extension Types: Workspaces diff --git a/16/umbraco-cms/customizing/extending-overview/extension-types/workspaces/workspace-action-menu-item.md b/16/umbraco-cms/customizing/extending-overview/extension-types/workspaces/workspace-action-menu-item.md new file mode 100644 index 00000000000..e69de29bb2d diff --git a/16/umbraco-cms/customizing/extending-overview/extension-types/workspaces/workspace-context.md b/16/umbraco-cms/customizing/extending-overview/extension-types/workspaces/workspace-context.md new file mode 100644 index 00000000000..4ec5f1af6ed --- /dev/null +++ b/16/umbraco-cms/customizing/extending-overview/extension-types/workspaces/workspace-context.md @@ -0,0 +1,83 @@ +--- +description: Establish an extension to communicate across the application. +--- + +# Workspace Context + +{% hint style="warning" %} +This page is a work in progress and may undergo further revisions, updates, or amendments. The information contained herein is subject to change without notice. +{% endhint %} + +A Workspace context is a container for the data of a workspace. It is a wrapper around the data of the entity that the workspace is working on. It is responsible for loading and saving the data to the server. Workspace Contexts are used to bring additional context alongside the default context of a workspace. + +* A workspace context knows about its entity type (for example content, media, member, etc.) and holds its unique string (for example: key). +* Most workspace contexts hold a draft state of its entity data. It is a copy of the entity data that can be modified at runtime and sent to the server to be saved. + +If a workspace wants to utilize Property Editor UIs, then it must provide a variant context for the property editors. The variant-context is the generic interface between workspace and property editors. + +```ts +interface UmbWorkspaceContext {} +``` + +## Example of Workspace + +## Example of Workspace Context + +The API will be initiated with the same host as the default Workspace Context. + +```typescript +{ + type: 'workspaceContext', + alias: 'My.WorkspaceContext.Counter', + name: 'My Counter Context', + api: 'my-workspace-counter.context.js', + conditions: [ + { + alias: 'Umb.Condition.WorkspaceAlias', + match: 'Umb.Workspace.Document', + } + ] +} +``` + +The code of such an API file could look like this: + +```typescript +import { + UmbController, + UmbControllerHost, +} from "@umbraco-cms/backoffice/controller-api"; +import { UmbContextToken } from "@umbraco-cms/backoffice/context-api"; +import { UmbNumberState } from "@umbraco-cms/backoffice/observable-api"; + +export class MyContextApi extends UmbController { + #counter = new UmbNumberState(0); + readonly counter = this.#counter.asObservable(); + + constructor(host: UmbControllerHost) { + super(host); + this.provideContext(UMB_APP_CONTEXT, this); + } + + increment() { + this.#counter.next(this.#counter.value + 1); + } +} + +export const api = MyContextCounterApi; +``` + +{% hint style="info" %} +Context APIs have to be self-providing. To do so it has to be an Umbraco Controller. +{% endhint %} + +A Context Token for a Workspace Context Extension should look like this: + +```typescript +export const UMB_APP_CONTEXT = new UmbContextToken( + "UmbWorkspaceContext", + "My.WorkspaceContext.Counter" +); +``` + +We recommend using `UmbWorkspaceContext` as the Context Alias for your Context Token. This will ensure that the requester only retrieves this Context if it's present at their nearest Workspace Context. Use the Extension Manifest Alias as the API Alias for your Context Token. For more information, see the [Context API](../../../foundation/working-with-data/context-api.md) article. diff --git a/16/umbraco-cms/customizing/extending-overview/extension-types/workspaces/workspace-editor-actions.md b/16/umbraco-cms/customizing/extending-overview/extension-types/workspaces/workspace-editor-actions.md new file mode 100644 index 00000000000..2eb896ea50a --- /dev/null +++ b/16/umbraco-cms/customizing/extending-overview/extension-types/workspaces/workspace-editor-actions.md @@ -0,0 +1,55 @@ +# Workspace Actions + +{% hint style="warning" %} +This page is a work in progress and may undergo further revisions, updates, or amendments. The information contained herein is subject to change without notice. +{% endhint %} + +Workspace actions are a set of functionalities or operations that can be performed within a workspace. These actions involve creating documents within the workspace, organizing and categorizing documents, publishing content and so on. + +Workspace action relates to a workspace alias (Umb.Workspace.Document) and has Access to the workspace context. + +

Workspace Actions

+ +**JavaScript Manifest example** + +
import { extensionRegistry } from '@umbraco-cms/extension-registry';
+import { MyWorkspaceAction } from './my-workspace-action';
+
+const manifest = {
+ type: 'workspaceAction',
+ alias: 'My.WorkspaceAction',
+ name: 'My Workspace Action',
+ api: MyWorkspaceAction,
+ meta: {
+  label: 'My Action',
+ },
+ conditions: [
+  {
+   alias: 'Umb.Condition.WorkspaceAlias',
+   match: 'My.Workspace',
+  },
+ ],
+};
+
+extensionRegistry.register(manifest);
+
+ +## The Workspace Action Class + +As part of the Extension Manifest you can attach a class that will be instantiated as part of the action. It will have access to the host element and the Workspace Context. When the action is clicked the `execute` method on the API class will be run. When the action is completed, an event on the host element will be dispatched to notify any surrounding elements. + +```ts +import { UmbWorkspaceActionBase } from '@umbraco-cms/backoffice/workspace'; + +export class MyWorkspaceAction extends UmbWorkspaceActionBase { + execute() { + this.workspaceContext.myAction(this.selection); + } +} +``` + +**Default Element** + +```typescript +interface UmbWorkspaceActionElement {} +``` diff --git a/16/umbraco-cms/customizing/extending-overview/extension-types/workspaces/workspace-footer-app.md b/16/umbraco-cms/customizing/extending-overview/extension-types/workspaces/workspace-footer-app.md new file mode 100644 index 00000000000..e69de29bb2d diff --git a/16/umbraco-cms/customizing/extending-overview/extension-types/workspaces/workspace-views.md b/16/umbraco-cms/customizing/extending-overview/extension-types/workspaces/workspace-views.md new file mode 100644 index 00000000000..7f46a46edf0 --- /dev/null +++ b/16/umbraco-cms/customizing/extending-overview/extension-types/workspaces/workspace-views.md @@ -0,0 +1,100 @@ +--- +description: Append a view to any Workspace +--- + +# Workspace Views + +{% hint style="warning" %} +This page is a work in progress and may undergo further revisions, updates, or amendments. The information contained herein is subject to change without notice. +{% endhint %} + +{% hint style="info" %} +Workspace Views was previously called Content Apps. +{% endhint %} + +Workspace Views are customizable companion **tabs** with the ability to take place in any workspace. + +

Workspace Views

+ +**In Content Section** + +With Workspace Views, editors can switch from editing 'Content' to accessing contextual information related to the item they are editing. + +The default workspace view is **'Info'** - displaying Links, History and Status of the current content item. + +## Example of a Workspace View + +1. Follow the [Vite Package Setup](../../../development-flow/vite-package-setup.md) by creating a new project folder called "`workspaceview`" in `App_Plugins`. +2. Create a manifest file named `umbraco-package.json` at the root of the `workspaceview` folder. Here we define and configure our workspace view. +3. Add the following code to `umbraco-package.json`: + +{% code title="umbraco-package.json" lineNumbers="true" %} +```json +{ + "$schema": "../../umbraco-package-schema.json", + "name": "My workspace", + "version": "0.1.0", + "extensions": [ + { + "type": "workspaceView", + "alias": "My.WorkspaceView", + "name": "My Workspace View", + "element": "/App_Plugins/workspaceview/dist/workspaceview.js", + "meta": { + "label": "My Workspace View", + "pathname": "/my-workspace-view", + "icon": "icon-add" + }, + "conditions": [ + { + "alias": "Umb.Condition.WorkspaceAlias", + "match": "Umb.Workspace.Document" + } + ] + } + ] +} +``` +{% endcode %} + +4. Add the following code to the existing `my-element.ts` from the `src`folder: + +{% code title="my-element.ts" lineNumbers="true" %} +```typescript +import { LitElement, html, customElement, css } from "@umbraco-cms/backoffice/external/lit"; +import { UmbElementMixin } from "@umbraco-cms/backoffice/element-api"; + +@customElement('my-workspaceview') +export default class MyWorspaceViewElement extends UmbElementMixin(LitElement) { + + render() { + return html` + + Welcome to my newly created workspace view. + + ` + } + + static styles = css` + uui-box { + margin: 20px; + } + ` +} + +declare global { + interface HTMLElementTagNameMap { + 'my-workspaceview': MyWorspaceViewElement + } +} + +``` +{% endcode %} + +In the `workspaceview` folder run `npm run build` and then run the project. Then in the content section of the Backoffice you will see our new Workspace View: + +

Workspace View Example

+ +{% hint style="info" %} +To see the Workspace View that we have created in the content section, first you will need to have some content created. +{% endhint %} diff --git a/16/umbraco-cms/customizing/extending-overview/extension-types/workspaces/workspace.md b/16/umbraco-cms/customizing/extending-overview/extension-types/workspaces/workspace.md new file mode 100644 index 00000000000..e69de29bb2d diff --git a/16/umbraco-cms/customizing/foundation/README.md b/16/umbraco-cms/customizing/foundation/README.md new file mode 100644 index 00000000000..a71168b8642 --- /dev/null +++ b/16/umbraco-cms/customizing/foundation/README.md @@ -0,0 +1,35 @@ +--- +description: Getting started with backoffice setup and configurations +--- + +# Foundation + +In this section, you can find the common terms, concepts, and guides used to extend the Umbraco backoffice. + +{% hint style="warning" %} +This page is a work in progress and may undergo further revisions, updates, or amendments. The information contained herein is subject to change without notice. +{% endhint %} + +## [Working with Bellissima](https://github.com/umbraco/Umbraco.Packages/tree/main/bellissima) + +An overview of community articles related to the New backoffice "Bellissima". + +## [Working with Data](working-with-data/) + +An overview of concepts on how to work with data when extending the backoffice. + +## [Contexts](contexts/) + +An overview of concepts on how to work with contexts when extending the backoffice. + +## [Umbraco Element](umbraco-element/) + +An overview of concepts on how to work with Umbraco element when extending the backoffice. + +## [Sorting](sorting.md) + +An overview of concepts on how to work with sorting when extending the backoffice. + +## [Routes](routes.md) + +An overview of concepts on how to work with routes when extending the backoffice. diff --git a/16/umbraco-cms/customizing/foundation/contexts/README.md b/16/umbraco-cms/customizing/foundation/contexts/README.md new file mode 100644 index 00000000000..b9e7ce16f9a --- /dev/null +++ b/16/umbraco-cms/customizing/foundation/contexts/README.md @@ -0,0 +1,15 @@ +--- +description: Contexts +--- + +# Contexts + +{% hint style="warning" %} +This page is a work in progress and may undergo further revisions, updates, or amendments. The information contained herein is subject to change without notice. +{% endhint %} + +Below you can find some articles on how you can work with different contexts: + +## [Property Dataset Context](./property-dataset-context.md) + +A Dataset Context is the connection point between a Property Editor and a Workspace and covers a set of properties. diff --git a/16/umbraco-cms/customizing/foundation/contexts/property-dataset-context.md b/16/umbraco-cms/customizing/foundation/contexts/property-dataset-context.md new file mode 100644 index 00000000000..8fb610ae303 --- /dev/null +++ b/16/umbraco-cms/customizing/foundation/contexts/property-dataset-context.md @@ -0,0 +1,25 @@ +--- +description: The Variant Context is a context that holds the data for a set of properties. +--- + +# Property Dataset Context + +{% hint style="warning" %} +This page is a work in progress and may undergo further revisions, updates, or amendments. The information contained herein is subject to change without notice. +{% endhint %} + +Property Editors UIs require the Dataset Context to be present to work. This enables Property Editor UIs to have a generic relation with its ownership. + +The Dataset Context holds a name and a set of properties. What makes a property can vary but an alias and a value are required. + +### Dataset Context Concerning Property Editors and Workspaces + +A Dataset Context is the connection point between a Property Editor and a Workspace. + +The hierarchy is as follows: + +* Workspace Context + * Dataset Context + * Property Editor UIs + +A dataset context covers a set of properties, in some cases a workspace then needs to have multiple variants. An example of such is Document Workspace. Each variant has its own set of properties and a name. diff --git a/16/umbraco-cms/customizing/foundation/icons.md b/16/umbraco-cms/customizing/foundation/icons.md new file mode 100644 index 00000000000..255b38b2040 --- /dev/null +++ b/16/umbraco-cms/customizing/foundation/icons.md @@ -0,0 +1,3 @@ +# Icons + +The icons from the Umbraco backoffice are based on [Lucide Icons](https://lucide.dev/). The syntax for getting the icons starts with`icon-`. You can find the list of all icons in the [Icon registry list on GitHub](https://github.com/umbraco/Umbraco.CMS.Backoffice/tree/762e43b2f49ca483df9cfe28de20f31ca07bb22b/src/packages/core/icon-registry/icons). diff --git a/16/umbraco-cms/customizing/foundation/localization.md b/16/umbraco-cms/customizing/foundation/localization.md new file mode 100644 index 00000000000..d96f6ce4663 --- /dev/null +++ b/16/umbraco-cms/customizing/foundation/localization.md @@ -0,0 +1,194 @@ +--- +description: Learn how to manage and use the Backoffice UI Localization files. +--- + +# Backoffice Localization + +This article describes how you can translate the Umbraco Backoffice UI into different languages. You can use the existing localizations from Umbraco or register your own localizations. You can also use the localization in your custom elements and controllers. + +## Registering Localization + +Localizations can be registered via the Extension Registry. [Read more about the Localization Extension Type](../extending-overview/extension-types/localization.md). + +### Missing Localization Keys + +As Umbraco is an evolving product, new text is regularly added to the English version of these files. Therefore, some of the languages may no longer be up-to-date. + +If a key is not found in the current language, the fallback language will be used. The fallback language is **English** with the culture code **en**. + +If a translation is missing, the default value within `umb-localize` will be shown in the user interface: + +```html +Default value +``` + +Instead of showing the default value we can show the key alias if we set `debug="true"`: + +```html + +``` + +## Using the Localizations + +### Localize Element + +The following example shows how you can display localized text with the `umb-localize` element: + +```html + +``` + +{% hint style="info" %} +You can have a look and try out the element in the [UI API Documentation](https://apidocs.umbraco.com/v15/ui/?path=/docs/api-localization-umblocalizeelement--docs). +{% endhint %} + +### **Localize Controller** + +In some situations, you need the localization as a variable that can be parsed. In this case, the Localization Controller can be used in your `element.ts` file. This can be setup in two ways: + +* Using [Umbraco Element](localization.md#umbraco-element) +* Using [Umbraco Controller](localization.md#umbraco-controller) + +#### Umbraco Element + +When using an [**Umbraco Element**](../../customizing/foundation/umbraco-element/)**,** the **Localization Controller** is already initialized on the `localize` property via the `UmbElementMixin`. + +```typescript +import { LitElement, css, html } from "lit"; +import { customElement } from "@umbraco-cms/backoffice/external/lit"; +import { UmbElementMixin } from "@umbraco-cms/backoffice/element-api"; + +export default class MyElement extends UmbElementMixin(LitElement) { + render() { + return html` + `; + } +} +``` + +The arguments will be passed to the function in the localization file if it is a function. + +#### Umbraco Controller + +If you are working with an Umbraco Controller, then you need to initialize the Localization Controller on your own via the `UmbLocalizationController`: + +```typescript +import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api'; +import { UmbLocalizationController } from '@umbraco-cms/backoffice/localization-api'; + +export class MyController extends UmbControllerBase { + // Create a new instance of the controller and attach it to the element + #localize = new UmbLocalizationController(this); + + render() { + return html` `; + } +} +``` + +## Using arguments + +Sometimes you need to pass arguments to the localization to return different values based on the arguments. A localization value can be either a string or a function. Given a localization file like this, we can return different values based on the number of items: + +```javascript +export default { + section: { + numberOfItems: (count) => { + count = parseInt(count, 10); + if (count === 0) return 'Showing nothing'; + if (count === 1) return 'Showing only one item'; + return `Showing ${count} items`; + }, + }, +}; +``` + +{% hint style="info" %} +You can try out the arguments feature in the [UI API Documentation](https://apidocs.umbraco.com/v15/ui/?path=/story/api-localization-umblocalizeelement--with-arguments). +{% endhint %} + +**Using the Localize Element** + +You can pass arguments to the localization by adding them as additional attributes: + +```html + +Showing items +``` + +The arguments will be passed to the function in the localization file if it is a function. The `args` attribute must be JSON-serializable and each array value will be passed to the function as an extra argument. + +**Using the Localize Controller** + +You can pass arguments to the localization by calling the `term` method with the arguments: + +```typescript +// Outputs: Showing 5 items +this.localize.term('section_numberOfItems', 5); +``` + +The arguments will be passed to the function in the localization file if it is a function. Each argument of `term` will be passed to the function as an extra argument. + +### Using placeholders + +You can also use placeholders in the localization keys to replace parts of the string with dynamic values. Placeholders are defined by curly braces `{0}` or percentage signs `%0%` in the localization key. The placeholders will be replaced one-to-one with the arguments passed to the localization. It works the same as the arguments feature, except you cannot calculate the value based on the arguments. + +Given a localization file like this: + +{% code title="en.js" %} +```javascript +export default { + section: { + numberOfItems: 'Showing {0} items', + }, +}; +``` +{% endcode %} + +You can use the same `args` attribute to pass the arguments: + +```html + + +``` + +### Using with manifests +You can localize values in a manifest. For example, prefix the name of the dashboard tab visible in the UI with a `#`. + +#### Example +A manifest registering a dashboard with `umbraco-package.json` or JavaScript can localize the `label` property in the `meta` object like this. + +{% code title="umbraco-package.json" lineNumbers="true" %} + +```json +{ + "name": "My.WelcomePackage", + "extensions": [ + { + "type": "dashboard", + ... + "meta": { + "label": "#welcomeDashboard_label", + "pathname": "welcome-dashboard" + }, + }, + ] +} +``` + +{% endcode %} + + +## Examples + +You can add your own localization keys using the principles you have learned, and apply them in a number of ways: + +### Using localization in a custom element + +You can find a localization example in the [Adding localization to the dashboard](../../tutorials/creating-a-custom-dashboard/adding-localization-to-the-dashboard.md) article. This will get you started with using localization in your custom elements. You can apply the same principles to all extensions. + +### Using localization in property descriptions and labels + +Property descriptions and labels can also be localized. They are formatted as Markdown and can contain localization keys using the built-in [Umbraco Flavored Markdown](../../reference/umbraco-flavored-markdown.md) syntax. diff --git a/16/umbraco-cms/customizing/foundation/routes.md b/16/umbraco-cms/customizing/foundation/routes.md new file mode 100644 index 00000000000..8dd3d3f0340 --- /dev/null +++ b/16/umbraco-cms/customizing/foundation/routes.md @@ -0,0 +1,72 @@ +--- +description: Get started with Routing in the backoffice. +--- + +# Routes + +{% hint style="warning" %} +This page is a work in progress and may undergo further revisions, updates, or amendments. The information contained herein is subject to change without notice. +{% endhint %} + +## Routing + +The routing in the backoffice is flexible and customizable. In this article, you can find a couple of starting points for routing. + +The overall **divider** is the [Section](../extending-overview/extension-types/sections/README.md) which is a `ManifestSection` extension type. It is also used internally by the following sections: Content, Media, Settings, Members, and so on. + +Depending on which section you are working on, there are different options: + +* **SectionView**: The [Section View](../extending-overview/extension-types/sections/section-view.md) is a view in a section and one of the automatic router extension types. It can be an entry point to a section. If a section has multiple views defined (or both dashboards and views) then the tabs and icons will be rendered. As some examples, you can check the **Packages** and **Member** sections. +* **Dashboard**: The [Dashboard](../extending-overview/extension-types/dashboard.md) is an entry point to a section. If there is more than one section view or dashboard then the defined tabs and icons will be rendered to make it possible to navigate. +* **Workspace**: The [Workspace](../../customizing/workspaces.md) concept has built-in features to facilitate editing of an entity of a certain entity type. It is used by many entities in the backoffice like content, media, content types, data types, dictionaries and so on. +* **Custom element**: A [Custom Element](umbraco-element/README.md) is a section that can be configured to use any web component as the **entry point**. The `element()` can be configured in the manifest. By doing this we'll disable the possibility of using dashboards and section views for the section since they will not be automatically routed/rendered. This option should be used only when necessary. + +### Building routing + +Almost any component can host routable sub-components by defining a list of routes and render a `umb-router-slot` element. Let's assume we have a **custom section** with pathname `custom-section` and a **section view** with pathname `organization`. In this context we can create an element with routes, like this: + +```typescript +@state() +_routes: UmbRoute[] = [ + { + // Adding :personId as a parameter + path: 'person/:personId', + component: () => import('./person.element.js'), + setup: (_component, info) => { + + console.log('personId:',info.match.params.personId); + }, + }, + { + path: 'people', + component: () => import('./people.element.js'), + setup: (_component, info) => { + + console.log('view-route-info',info); + + }, + }, + { + path: '', + redirectTo: 'people', + }, +]; +``` + +{% hint style="info" %} +The order in which the routes are defined is important as the first match will be used. So make sure to add more specific routes in the beginning. +{% endhint %} + +In the render method of the element, render the `umb-router-slot`: + +```html + +``` + +One can create links to allow navigation to a given route: + +```html +People +Person 1 +Person 2 +``` diff --git a/16/umbraco-cms/customizing/foundation/sorting.md b/16/umbraco-cms/customizing/foundation/sorting.md new file mode 100644 index 00000000000..1e7449deeee --- /dev/null +++ b/16/umbraco-cms/customizing/foundation/sorting.md @@ -0,0 +1,96 @@ +--- +description: Enable sorting elements via drag and drop +--- + +# Sorting + +{% hint style="warning" %} +This page is a work in progress and may undergo further revisions, updates, or amendments. The information contained herein is subject to change without notice. +{% endhint %} + +The Umbraco Sorter enables you to make a list of elements sortable via drag-and-drop interaction. You have to set up the sorter once on the Element that renders the items to be sorted. As part of the configuration, you shall provide an `onChange` callback method, which will be executed every time the sorter makes a difference to the data. + +### Configuration + +The following example shows a basic setup of the Sorter. + +```typescript + +type ModelEntryType = { + id: string; + name: string; +} + +this.#sorter = new UmbSorterController(this, { + itemSelector: '.sorter-item', + containerSelector: '.sorter-container', + getUniqueOfElement: (element) => { + return element.getAttribute('data-sorter-id'); + }, + getUniqueOfModel: (modelEntry) => { + return modelEntry.id; + }, + onChange: ({ model }) => { + const oldValue = this._items; + this._items = model; + this.requestUpdate('_items', oldValue); + }, +}); +``` + +The properties provided are the following: + +* `itemSelector`: A query selector that matches the items that should be draggable. +* `containerSelector`: A query elector that matches the parent element of the items. +* `getUniqueOfElement`: A method that returns the unique element +* `getUniqueOfModel`: Provide a method that returns the unique of a given model entry +* `onChange`: Provide a method to retrieve the changed model. This is called every time the model is changed, including when the user is dragging around. + +### Data Model + +The model given to the Sorter must be an Array. The following example extends the example from above: + +```typescript + + const model: Array = [ + { + id: 1, + name: 'First item' + }, + { + id: 2, + name: 'second item' + } + { + id: 3, + name: 'Third item' + } + ] + + // Set the Model, if you have changes to the model not coming from the Sorter. Then set the model again: + this.#sorter.setModel(model); +``` + +### Rendering + +The Sorter does not move elements, instead, it updates the model as the user drags an item around. This puts higher pressure on the rendering of the sortable Elements. This means we need to make sure that the rendering re-uses the same element despite sorting the data differently. + +Lit does provide a render helper method called `repeat` that does this for us. The following example shows a render method that continues the work of the examples above: + +```typescript + + + render() { + return html` + + ${repeat( + this._items, + (item) => item.id, + (item) => + html`${item.name} + `, + )} + + `; + } +``` diff --git a/16/umbraco-cms/customizing/foundation/terminology.md b/16/umbraco-cms/customizing/foundation/terminology.md new file mode 100644 index 00000000000..9651ea3064f --- /dev/null +++ b/16/umbraco-cms/customizing/foundation/terminology.md @@ -0,0 +1,23 @@ +--- +description: A list of some of the key concepts with working the Umbraco Backoffice. +--- + +# Terminology + +Understanding certain key concepts is essential when customizing the backoffice. These terminologies can help you decode the purpose of code effectively: + +* **Repository:** An API enables communication with a server. +* **Store:** An API representing data, generally coming from the server. Most stores would talk with one or more resources. You can read more about this in the [Store](../../customizing/foundation/working-with-data/store.md) article. +* **State:** A reactive container holding data, when data is changed all its Observables will be notified. You can read more about state and observables in the [States](../../customizing/foundation/working-with-data/states.md) article. + * **Observable:** An observable is the hook for others to subscribe to the data of a State. + * **Observe:** Observe describes what we do when subscribing to an Observable. +* **Context-API:** The name used to serve APIs (instances/classes) for a certain context in the DOM. An API that is served via the Context-API is called a Context. You can read more about this in the [Context API](../../customizing/foundation/working-with-data/context-api.md) article. + * **Context Provider:** One that provides a class instance as a Context API. + * **Context Consumer:** One that consumer subscribes to a class instance as a Context API. +* **Controller:** An abstract term for a thing that hooks into the lifecycle of an element. Many things in our system are Controllers. +* **Umbraco Controller:** Enables hosting controllers. Additionally, it provides a few shortcut methods for initializing core Umbraco Controllers. You can read more about this in the [Controllers](../../customizing/foundation/umbraco-element/controllers/) article. + * **Controller Host:** A class that can host controllers. + * **Controller Host Element:** The element that can host controllers. +* **Umbraco Element:** The `UmbLitElement` or `UmbElemenMixin` enables hosting controllers. Additionally, it provides a few shortcut methods for initializing core Umbraco Controllers. You can read more about this in the [Umbraco Element](../../customizing/foundation/umbraco-element/) article. + +Read more about how to get started with extending the backoffice in the [Backoffice Setup](../../customizing/extending-overview/) article. diff --git a/16/umbraco-cms/customizing/foundation/umbraco-element/README.md b/16/umbraco-cms/customizing/foundation/umbraco-element/README.md new file mode 100644 index 00000000000..d0a557de110 --- /dev/null +++ b/16/umbraco-cms/customizing/foundation/umbraco-element/README.md @@ -0,0 +1,29 @@ +--- +description: Ease the integration with Backoffice by using a Umbraco Element +--- + +# Umbraco Element + +{% hint style="warning" %} +This page is a work in progress and may undergo further revisions, updates, or amendments. The information contained herein is subject to change without notice. +{% endhint %} + +This provides a few methods to connect with the Backoffice, giving you the ability to: + +* Consume a Context — [Learn more about Consuming Contexts](../working-with-data/context-api.md) +* Provide Context — [Learn more about Providing Contexts](../working-with-data/context-api.md#provide-a-context-api) +* Observe a State — [Learn more about States](../working-with-data/states.md#observe-a-state-via-umbraco-element-or-umbraco-controller) +* Localization — [Learn more about Localization](../../../extending/language-files/) +* Host Controllers — [Learn more about Controllers](controllers/) + +## Create an Umbraco Element + +You can turn any Web Component into an Umbraco Element by using the Umbraco Element Mixin, as done in the following example: + +```ts +import { UmbElementMixin } from '@umbraco-cms/backoffice/element-api' + +class MyExtensionElement extends UmbElementMixin(HTMLElement) { + +} +``` diff --git a/16/umbraco-cms/customizing/foundation/umbraco-element/controllers/README.md b/16/umbraco-cms/customizing/foundation/umbraco-element/controllers/README.md new file mode 100644 index 00000000000..986f1f6e350 --- /dev/null +++ b/16/umbraco-cms/customizing/foundation/umbraco-element/controllers/README.md @@ -0,0 +1,48 @@ +# Controllers + +A Controller enables a class to hook into the life cycle of a Web Component + +{% hint style="warning" %} +This page is a work in progress and may undergo further revisions, updates, or amendments. The information contained herein is subject to change without notice. +{% endhint %} + +Controllers have the ability to declare the following methods: + +* `hostConnected()` — Called when the Host Element is Connected to the DOM. +* `hostDisconnected()` — Called when the Host Element is Disconnected from the DOM. +* `destroy()` — Called when the controller is taken out of commission. + +Additionally, the Umbraco Controllers implement a `getHostElement()` method, which enables any Controller to receive the Element that hosts the Controllers. + +### Host Element + +A Controller will have to be assigned to a Host Element. An assignment can be indirect as Controllers can host other Controllers. + +The Host Element is a Controller Host Web Component. The Umbraco Element turns any Web Component into a Controller Host. For more information check the [Umbraco Element](../) article. + +### Controller Alias + +Any controller can be identified by a Controller Alias, using either a **String** or **Symbol**.\ +If you utilize a Controller with a Controller Alias, then it will be destroyed when another Controller with same Alias gets Added to same Host.\ +\ +In this way, you can keep your controllers tidy, without a lot of managing.\ +\ +The example below shows how to initialize a Controller with a Controller Alias. + +

+function mySetActiveDocument(id: string) {
+	new UmbObserverController(
+		this.#host,
+		this.myGetObservableForDocumentOfId(id),
+		(document) => {
+			// Callback receiving the specific document.
+			Console.log("Active document data ", document)
+		},
+		'_observeStateById',
+	);
+}
+
+ +The creation of this Controller will replace its previous instance. Leaving only the latest observation to be present, as the previous instance will be removed and destroyed. + +You can find another example in the [Write your own Controller](write-your-own-controller.md) article. diff --git a/16/umbraco-cms/customizing/foundation/umbraco-element/controllers/write-your-own-controller.md b/16/umbraco-cms/customizing/foundation/umbraco-element/controllers/write-your-own-controller.md new file mode 100644 index 00000000000..6c160b48ffe --- /dev/null +++ b/16/umbraco-cms/customizing/foundation/umbraco-element/controllers/write-your-own-controller.md @@ -0,0 +1,31 @@ +--- +description: Reuse functionality across components by writing it as a Controller +--- + +# Write your own Controller + +{% hint style="warning" %} +This page is a work in progress and may undergo further revisions, updates, or amendments. The information contained herein is subject to change without notice. +{% endhint %} + +A Controller must follow the interface of UmbController. To ease the implementation you can base your class on the `UmbControllerBase`: + +```typescript +import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api'; + +class MyController extends UmbControllerBase { + + hostConnected() { + super.hostConnected(); + // Your code when the Host element is connected. + } + hostDisconnected() { + super.hostDisconnected(); + // Your code when the Host element is disconnected. + } + destroy() { + super.destroy(); + // Your code for when this controller gets destroyed. + } +} +``` diff --git a/16/umbraco-cms/customizing/foundation/working-with-data/README.md b/16/umbraco-cms/customizing/foundation/working-with-data/README.md new file mode 100644 index 00000000000..bff06641049 --- /dev/null +++ b/16/umbraco-cms/customizing/foundation/working-with-data/README.md @@ -0,0 +1,23 @@ +# Working with Data + +Learn how to work with data or request the data when extending the backoffice. + +{% hint style="warning" %} +This page is a work in progress and may undergo further revisions, updates, or amendments. The information contained herein is subject to change without notice. +{% endhint %} + +## [Repositories](repositories.md) + +Repositories are used for talking to the server by requesting data and getting notified about updates. + +## [Context API](context-api.md) + +Context APIs work with “local/runtime” data and enables receiving APIs. + +## [Store](store.md) + +A store holds data throughout the session. It is used to create reactivity across different parts. + +## [States](states.md) + +A reactive container holding data, when data is changed all its Observables will be notified. diff --git a/16/umbraco-cms/customizing/foundation/working-with-data/context-api.md b/16/umbraco-cms/customizing/foundation/working-with-data/context-api.md new file mode 100644 index 00000000000..bb2c8ed5f9c --- /dev/null +++ b/16/umbraco-cms/customizing/foundation/working-with-data/context-api.md @@ -0,0 +1,200 @@ +--- +description: Communicate across different boundaries with the Context API +--- + +# Context API + +{% hint style="warning" %} +This page is a work in progress and may undergo further revisions, updates, or amendments. The information contained herein is subject to change without notice. +{% endhint %} + +The Context API enables receiving APIs. Depending on where your code is executed from, it affects which and what instances of APIs can be received. + +The Context API enables an element or a controller to receive an API provided via any ascending element. In other words, it can receive APIs provided via a parent element or parent of a parent element, and so forth. + +The Context API enables connections between Elements and APIs. DOM structure defines the context of which an API is exposed for. APIs are provided via an element and can then be consumed by any decending element. + +## Consume a Context API + +There are different ways to consume a Context API. The most straightforward implementation is done on an Umbraco Element with a Context Token. + +All Umbraco Context APIs have a Context Token which can be imported and used for consumption, for example: + +```typescript +import { UMB_NOTIFICATION_CONTEXT } from '@umbraco-cms/backoffice/notification'; + +... + +this.consumeContext(UMB_NOTIFICATION_CONTEXT, (context) => { + // Notice this is a subscription, as the context might change or a new one appears. + console.log("I've got the typed context: ", context); +}); +``` + +The above example takes place in an Umbraco Element or Umbraco Controller. + +### Alternative solutions + +The above examples utilize an Umbraco Controller to hook into an element's life cycle. This Controller is named `UmbContextConsumerController`. + +If you need to consume a Context API from a non-controller host, then look at the `UmbContextConsumer`. + +## **Write your own Context Token** + +A Context Token is a context identifier and is generally a string matched with a type. In this way, users of the token can be sure to get the right type of context. + +```typescript +import { UmbContextToken } from "@umbraco-cms/backoffice/context-api"; + +type MyContext = { + foo: string; + bar: number; +}; + +const MY_CONTEXT = new UmbContextToken ("My.Context.Token"); +``` + +### **Context Token with an API Alias** + +For additions to Contexts, we can use the API Aliases to identify the additional API. Using the same Context Alias for additional APIs will ensure that such API must be present with the first encounter of that Context Alias. Otherwise, a request will be rejected. In other words, if the addition is not part of the nearest matching Context, the request will be rejected. + +{% hint style="info" %} +Using API Alias only provides value when two or more APIs should share the same Context. This is needed for Context Extensions that are provided along with other Contexts. +{% endhint %} + +```typescript +import { UmbContextToken } from "@umbraco-cms/backoffice/context-api"; + +type MyAdditionalContext = { + additional: string; +}; + +const MY_ADDITIONAL_API_TOKEN = new UmbContextToken( + "My.ContextFrame.Alias", + "My.API.Alias" +); +``` + +The Token declared above can be used to provide an additional Context API at the same Element as another Context API is provided at. Below is an example of how the two APIs are made available. + +```typescript +const contextElement = new UmbLitElement(); +contextElement.provideContext( + MY_API_TOKEN, + new MyAPiFromSomewhereNotPartOfThisExample() +); +contextElement.provideContext( + MY_ADDITIONAL_API_TOKEN, + new MyAdditionalAPiFromSomewhereNotPartOfThisExample() +); + +const consumerElement = new UmbLitElement(); +contextElement.appendChild(consumerElement); +consumerElement.consumeContext(MY_API_TOKEN, (context) => { + console.log("I've got the default api", context); +}); +consumerElement.consumeContext(MY_ADDITIONAL_API_TOKEN, (context) => { + console.log("I've got the additional api", context); +}); +``` + +This is no different than using two different Context Aliases. But it has an important effect on what happens if one of them is not provided. This is demonstrated in the example below: + +```typescript +const upperContextElement = new UmbLitElement(); + +const contextElement = new UmbLitElement(); +upperContextElement.appendChild(contextElement); +contextElement.provideContext( + MY_API_TOKEN, + new MyAPiFromSomewhereNotPartOfThisExample() +); + +const consumerElement = new UmbLitElement(); +contextElement.appendChild(consumerElement); +consumerElement.consumeContext(MY_API_TOKEN, (context) => { + console.log("I've got the default api", context); +}); +consumerElement.consumeContext(MY_ADDITIONAL_API_TOKEN, (context) => { + // This will never happen + console.log("I've got the additional api", context); +}); +``` + +The consumption of the Additional API will never happen as the token uses the same Context Alias as `MY_API_TOKEN`. This means that any request containing this Context Alias will be stopped at the first API it encounters. To ensure addition to a specific context, do it locally at the nearest API that uses the same Context Alias. + +### **Context Token with a Type Discriminator** + +{% hint style="info" %} +This is only relevant if you are going to make multiple context API for the same context. Discriminator only gives value for consumption of Context APIs that have a varying interface. The backoffice uses this for the different types of Workspace Contexts. +{% endhint %} + +In some cases, it is needed to have different APIs for the same context. For example, the [Workspace Contexts](../../extending-overview/extension-types/workspaces/workspace-context.md). + +If someone wants the workspace name, they might not care about the specific API of the Workspace Context. These implementations can use a standard Context Token with a type of generic Workspace Context. + +The features related to Publishing in the **Document Workspace Context** do not require a new Context. However, we should not accidentally retrieve the workspace context of a parent workspace when in a Workspace. Therefore, we need to provide a workspace context in each workspace, and the one we retrieve is the one we will be using. Since Publishing is not part of the generic Workspace Context, check if the context is a Document Workspace Context and recast it accordingly. + +To avoid each implementation taking care of this, Context Tokens can be extended with a **Type Discriminator**. This will discard the given API if it does not meet the necessary requirements. When it is the desired type, the API will be converted to the appropriate type. + +This example shows how to create a discriminator Context Token that will discard the API if it is not a Publishable Context: + +**Context Token Example:** + +```typescript +import { UmbContextToken } from "@umbraco-cms/backoffice/context-api"; + + +interface MyBaseContext { + foo: string; + bar: number; +} + +interface MyPublishableContext extends MyBaseContext { + publish(); +} + +const MY_PUBLISHABLE_CONTEXT = new UmbContextToken< + + MyContext, + MyPublishableContext +>("My.Context.Token", (context): context is MyPublishableContext => { + return "publish" in context; +}); +``` + +**Implementation of Context Token Example:** + +```typescript +const contextElement = new UmbLitElement(); +contextElement.provideContext( + MY_PUBLISHABLE_CONTEXT, + new MyPublishableContext() +); + +const consumerElement = new UmbLitElement(); +contextElement.appendChild(contextElement); +consumerElement.consumeContext(MY_PUBLISHABLE_CONTEXT, (context) => { + + // context is of type 'MyPublishableContext' + console.log("I've got the context of the right type", context); +}); +``` + +This allows implementers to request a publishable context without needing to know the Type or how to identify the context. + +In detail, the Context API will search for the first API that matches the alias `My.Context.Token`, and not look further. If the API meets the type discriminator, it will be returned, otherwise the consumer will never reply. + +## Provide a Context API + +You can provide a Context API from an Umbraco Element or Umbraco Controller: + +```typescript +this.provideContext('myContextAlias', new MyContextApi()); +``` + +Or with a Controller using a 'host' reference to Controller Host (Umbraco Element/Controller): + +```typescript +new UmbContextProviderController(host, 'myContextAlias', new MyContextApi()); +``` diff --git a/16/umbraco-cms/customizing/foundation/working-with-data/repositories.md b/16/umbraco-cms/customizing/foundation/working-with-data/repositories.md new file mode 100644 index 00000000000..b094f93569e --- /dev/null +++ b/16/umbraco-cms/customizing/foundation/working-with-data/repositories.md @@ -0,0 +1,31 @@ +# Repositories + +{% hint style="warning" %} +This page is a work in progress and may undergo further revisions, updates, or amendments. The information contained herein is subject to change without notice. +{% endhint %} + +A repository is the Backoffices entry point to request data and get notified about updates. Each domain should register their own repository in the Backoffice. + +### Register a Repository + +```typescript +import { umbExtensionRegistry } from '@umbraco-cms/backoffice/extension-registry'; +import { MyRepository } from './MyRepository'; + +const repositoryManifest = { + type: 'repository', + alias: 'My.Repository', + name: 'My Repository', + api: MyRepository, +}; +``` + +With a repository we can have different data sources depending on the state of the app. The data sources can come from places like a server, an offline database, a store, or a Signal-R connection. That means that the consumer will not have to be concerned how to access the data, add or remove items from a collection, etc. This means we get a loose connection between the consumer and the data-storing procedures hiding all complex implementation. + +### Data flow with a repository + +

Data flow

+ +A repository has to be instanced in the context where it is used. It should take a host element as part of the constructor. This ensures that any contexts consumed in the repository, like notifications or modals, are rendered in the correct DOM context. + +A repository can be called directly from an element, but will often be instantiated in a context, like the Workspace Context. diff --git a/16/umbraco-cms/customizing/foundation/working-with-data/states.md b/16/umbraco-cms/customizing/foundation/working-with-data/states.md new file mode 100644 index 00000000000..9e20508b951 --- /dev/null +++ b/16/umbraco-cms/customizing/foundation/working-with-data/states.md @@ -0,0 +1,79 @@ +--- +description: Make reactivity with Umbraco States +--- + +# States + +{% hint style="warning" %} +This page is a work in progress and may undergo further revisions, updates, or amendments. The information contained herein is subject to change without notice. +{% endhint %} + +Umbraco States enables you to create [Observables](states.md#observables) based on a State. The Observables can then be observed, and when a change to the State occurs, all observers of Observables will be triggered. + +## State types + +Umbraco comes with a State type for the most common types of data: + +* Array State +* Boolean State +* Class State +* Number State +* Object State +* String State + +## Observables + +### Observe a state via Umbraco Element or Umbraco Controller + +The Umbraco Element or Controllers comes with the ability to observe an Observable. + +While observing all changes will result in the callback being executed. + +The example below creates a State and then turns the whole state into an Observable, which then can be observed. + +
import { UmbArrayState } from '@umbraco-cms/backoffice/observable-api';
+
+...
+
+this.#selectionState = UmbArrayState<string>(['item1', 'item2']);
+this.selection = this.#selectionState.asObservable();
+
+this.observe(
+	this.selection,
+	(selection) => {
+		// This call will be executed initially and on each change of the state
+	}
+);
+
+ +### Change the value of a state + +The value of a state can be changed via the `setValue` method. This replaces the current data with new data. + +The following example shows how to change the value of the state to hold `item2` and `item3`. As the example extends the example from above, it means that `item1` is no longer part of the value of this state. + +
import { UmbArrayState } from '@umbraco-cms/backoffice/observable-api';
+
+...
+
+this.#selectionState.setValue(['item2', 'item3']);
+
+
+ +**Observe part of a state** + +With the `asObservablePart` method, you can set up an Observable that provides a transformed outcome, based on the State. + +```typescript +this.selectionLength = this.#selectionState.asObservablePart(data => data.length); + +this.observe( + this.selectionLength, (length) => { + // This call will be executed, initially and on each change of the specific value that this observer provides. + // This means that this will only be executed when the length changed. Not if the value was replaced with a new value value with the exact same length. + console.log("Length of selection is now ", length) + } +); +``` + +In the above example, the `asObservablePart` mapping function will be executed every time there is a change to the State. If the result of the method is different than before it will trigger an update to its observers. diff --git a/16/umbraco-cms/customizing/foundation/working-with-data/store.md b/16/umbraco-cms/customizing/foundation/working-with-data/store.md new file mode 100644 index 00000000000..94cf0a21246 --- /dev/null +++ b/16/umbraco-cms/customizing/foundation/working-with-data/store.md @@ -0,0 +1,150 @@ +--- +description: >- + A store holds data throughout the session. It is used to create reactivity + across different parts. +--- + +# Store + +{% hint style="warning" %} +This page is a work in progress and may undergo further revisions, updates, or amendments. The information contained herein is subject to change without notice. +{% endhint %} + +## Store + +A store is the link between a Resource and a Repository. A store is mainly taken from a Context API. In other words, we have to Consume the Context (Store) to get the Store. + +Generally, a Store will hold one or more [State Objects](states.md), with each Subject made available for Observation via Observables. + +### A Basic Store + +```typescript +class MyProductStore { + #products = new UmbArrayState(>[], (product) => product.id); + + public readonly products = this.#products.asObservable(); + + protected host: UmbControllerHostElement; + constructor(host: UmbControllerHostElement) { + this.host = host; + + // The Store provides it self as a Context-API. + new UmbContextProviderController(_host, 'MyStoreContextAlias', this); + } +} +``` + +In this example, we created an ArrayState, A State which is specific to Arrays. This holds the data for one or more Observables to convey to outsiders. + +This example shows how to use the store: + +```typescript +class MyImplementation extends UmbLitElement { + private _myProductStore?: MyProductStore; + + constructor() { + super(); + + // Notice the consume callback is triggered initially and every time the Context is changed. + this.consume('MyStoreContextAlias', (context) => { + this._myProductStore = context; + + this._observeAllProducts(); + }); + } + + private _observeAllProducts() { + if (!this._myProductStore) return; + + // Notice this callback will be triggered initially and each time the products change: + this.observe(this._myProductStore.products, (products) => { + console.log('The data of all products is:', products); + }); + } +} +``` + +#### A bit more meaningful Store + +Here we added a method that returns an Observable that is specific to the requested product. + +```typescript +class MyProductStore { + + ... + + getByKey(id: string) { + + // Request data via a Resource to then take part of this state when received. + tryExecuteAndNotify(this.host, ProductResource.getByKey({id})).then(({ data }) => { + if (data) { + this.#products.append(data.items); + } + }); + + // Return a Observable part, to listen for this specific product and the future changes of it. + return this.#data.asObservablePart((documents) => + documents.find((document) => document.id === id) + ); + } +} +``` + +An example implementation using this method: + +```typescript +class MyImplementation extends UmbLitElement { + private _myProductStore?: MyProductStore; + + constructor() { + super(); + + // Notice the consume callback is triggered initially and every time the Context is changed. + this.consume('MyStoreContextAlias', (context) => { + this._myProductStore = context; + + this._observeASpecificProduct(); + }); + } + + private _observeASpecificProduct() { + if (!this._myProductStore) return; + + // Notice this callback will be triggered initially and each time the specific product change: + this.observe(this._myProductStore.getByKey('1234'), (product) => { + console.log('The data of product `1234`` is:', product); + }); + } +} +``` + +**Create many Observables** + +A Store must hold different Observables, some exceptionally general and others highly specific, all in perspective of the types of observers we aim to accommodate. + +This example gives some inspiration to how fine-grained this can become: + +```typescript +class MyProductStore { + #products = new UmbArrayState(>[]); + + public readonly products = this.#products.asObservable(); + public readonly amountOfProducts = this.#products.asObservablePart((products) => products.length); + public readonly topTenRatedProducts = this.#products.asObservablePart((products) => products.sort((a, b) => b.rating - a.rating).slice(0, 10)); + + ... +} +``` + +An observer of an Observable will only be triggered if the specific part of that data has changed. With this we can make a high-performance application, only triggering the parts that need to update when data is changed. + +**Ensure unique data** + +For incoming data to replace existing data, we need to clarify what makes an entry of the array unique. In the examples of this guide, each product has an `id`. We have clarified this to the State by giving it the little method `(product) => product.id` as part of its creation: + +```typescript +class MyProductStore { + #products = new UmbArrayState(>[], (product) => product.id); + ... +} +``` diff --git a/16/umbraco-cms/customizing/overview.md b/16/umbraco-cms/customizing/overview.md new file mode 100644 index 00000000000..30d2c4c936d --- /dev/null +++ b/16/umbraco-cms/customizing/overview.md @@ -0,0 +1,32 @@ +--- +description: >- + Get an overview of the different options for extending and customizing the + Umbraco CMS backoffice. +--- + +# Extend and customize the editing experience + +The backoffice is one of the main forces of Umbraco CMS. It is where you build the foundations for the website and where your content editors will work with the content. + +{% hint style="info" %} +Are you looking to **extend and customize the Umbraco CMS**? + +Resources are available in the [Extending](../extending/build-on-umbraco-functionality.md) section for when you are looking to extend Umbraco functionality beyond the backoffice. +{% endhint %} + +In this section, you will find all the resources you need to build an intuitive and fluid editor experience for your content editors. + +
DashboardsThe dashboard is where all the main functionality of the backoffice will be.dashboard.mdDocumentations Icons_Umbraco_CMS_Extending_Dashboards.png
Sections and TreesExtend the backoffice by customizing existing and building new sections and trees.section-trees.mdDocumentations Icons_Umbraco_CMS_Extending_Sections_and_Trees.png
Property EditorsLearn how to extend and build some of the main building blocks in Umbraco CMS.property-editorsDocumentations Icons_Umbraco_CMS_Extending_Property_Editors.png

Backoffice UI API

See what all of the modules export, interfaces, hierarchy, code examples, and much more.

https://apidocs.umbraco.com/v15/ui-api/index.htmlDocumentations Icons_Umbraco_CMS_Tutorials_the_Starter_Kit.png
+ +{% content-ref url="foundation/" %} +[foundation](foundation/) +{% endcontent-ref %} + +{% content-ref url="extending-overview/extension-types/" %} +[extension-types](extending-overview/extension-types/) +{% endcontent-ref %} + +*** + +{% include "../.gitbook/includes/umbraco-extending-the-backoffice-training-course.md" %} + diff --git a/16/umbraco-cms/customizing/project-bellissima.md b/16/umbraco-cms/customizing/project-bellissima.md new file mode 100644 index 00000000000..a819247ddd2 --- /dev/null +++ b/16/umbraco-cms/customizing/project-bellissima.md @@ -0,0 +1,15 @@ +--- +description: >- + Learn more about project Bellissima that transformed the Umbraco Backoffice + using modern methods and technology. +--- + +# Project Bellissima + +"Bellissima", the code name for the frontend code of the backoffice, is a re-creation of the backoffice of Umbraco. While replacing the deprecated angular code with [Lit](https://lit.dev/) and [TypeScript](https://www.typescriptlang.org/), you can customize and extend everything in the backoffice. + +

An overview of all the customizable sections and elements in the Umbraco CMS backoffice.

+ +Each block is an extension point that can be extended, customized, replaced, or removed. + +You are not limited to the mentioned frameworks. You can use any other framework or library of your choice. diff --git a/16/umbraco-cms/customizing/property-editors/README.md b/16/umbraco-cms/customizing/property-editors/README.md new file mode 100644 index 00000000000..03463d2d372 --- /dev/null +++ b/16/umbraco-cms/customizing/property-editors/README.md @@ -0,0 +1,40 @@ +--- +description: Guide on how to work with and create Property Editors in Umbraco +--- + +# Property Editors + +{% hint style="info" %} +[This tutorial](../../tutorials/creating-a-property-editor/) contains step-by-step instructions for building a custom Property editor. +{% endhint %} + +{% hint style="warning" %} +The Property Editor articles are a work in progress and may undergo further revisions, updates, or amendments. The information contained herein is subject to change without notice. +{% endhint %} + +This section describes how to work with and create Property Editors. A property editor is the editor used to insert content into Umbraco. [See here for definition](../../fundamentals/backoffice/property-editors/) + +## [Property Editors Composition](composition/) + +A property editor is an editor used to insert content into Umbraco. A Property Editor is composed of two extensions: Property Editor Schema and Property Editor UI. + +## [Package Manifest](../umbraco-package.md) + +Reference for the package.manifest JSON file format to register one or more property editors for Umbraco. + +## [Property Value Converters](property-value-converters.md) + +Convert the stored property data value to a useful object returned by the Published Content APIs. + +## [Property Actions](property-actions.md) + +Use Property Actions to add additional functionaility to your custom property editors. + +## [Tracking References](tracking.md) + +Learn how to extend Property editors to track entity references inside the property editor. + +## More information + +* [Built in Property Editors](../../fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/) +* [Creating a property editor](../../tutorials/creating-a-property-editor/) diff --git a/16/umbraco-cms/customizing/property-editors/composition/README.md b/16/umbraco-cms/customizing/property-editors/composition/README.md new file mode 100644 index 00000000000..f0298a26a69 --- /dev/null +++ b/16/umbraco-cms/customizing/property-editors/composition/README.md @@ -0,0 +1,24 @@ +--- +description: This section describes how to work with and create Property Editors. +--- + +# Property Editors Composition + +{% hint style="warning" %} +This page is a work in progress and may undergo further revisions, updates, or amendments. The information contained herein is subject to change without notice. +{% endhint %} + +A property editor is an editor used to insert content into Umbraco. A Property Editor is composed of two extensions. To form a full Property Editor you will need a: + +* [Property Editor Schema](property-editor-schema.md) +* [Property Editor UI](property-editor-ui.md) + +A Property Editor UI is utilizing a Property Editor Schema, and you can have multiple Property Editor UIs for one Schema. This means you can find a Schema that solves your needs. You only need to build a Property Editor UI. + +* Each Property Editor can have multiple Property Editor UIs. +* Both a Property Editor Schema and Property Editor UI can define the Settings used for their configuration. + +### Configuration + +* Data Type Settings for a Property Editor or Property Editor UI is defined in the manifests. +* They both use the same format for their settings. diff --git a/16/umbraco-cms/customizing/property-editors/composition/property-editor-schema.md b/16/umbraco-cms/customizing/property-editors/composition/property-editor-schema.md new file mode 100644 index 00000000000..2b0d2728eda --- /dev/null +++ b/16/umbraco-cms/customizing/property-editors/composition/property-editor-schema.md @@ -0,0 +1,25 @@ +--- +description: The Server side part of a Property Editor +--- + +# Property Editor Schema + +{% hint style="warning" %} +This page is a work in progress and may undergo further revisions, updates, or amendments. The information contained herein is subject to change without notice. +{% endhint %} + +The Property Editor Schema is server code, written in C#. This handles the storage of a Property Editor and defines _Server Side Validation_ and _Property Value Converters_. + +### Property Editor Schema + +The Property Editor Schema settings are used for configuration that the server needs to know about. + +**Manifest** + +```json +{ + "type": "propertyEditorSchema", + "name": "Text Box", + "alias": "Umbraco.TextBox", +}; +``` diff --git a/16/umbraco-cms/customizing/property-editors/composition/property-editor-ui.md b/16/umbraco-cms/customizing/property-editors/composition/property-editor-ui.md new file mode 100644 index 00000000000..b0b3c5c6624 --- /dev/null +++ b/16/umbraco-cms/customizing/property-editors/composition/property-editor-ui.md @@ -0,0 +1,143 @@ +--- +description: Presenting the Editing Experience of a Property Editor +--- + +# Property Editor UI + +{% hint style="warning" %} +This page is a work in progress and may undergo further revisions, updates, or amendments. The information contained herein is subject to change without notice. +{% endhint %} + +The Property Editor UI is the UI that is used to edit the data in the backoffice. + +The Property Editor UI is a pure front-end extension. This determines how the data of a Property Editor is presented and manipulated. The Extension points to a Web Component. + +### Property Editor UI + +{% code title="umbraco-package.json" %} +```json +{ + "type": "propertyEditorUi", + "alias": "Umb.PropertyEditorUi.TextBox", + "name": "Text Box Property Editor UI", + "element": "/App_Plugins/my-text-box/dist/my-text-box.js", + "elementName": "my-text-box", + "meta": { + "label": "My Text Box", + "propertyEditorSchemaAlias": "Umbraco.TextBox", + "icon": "icon-autofill", + "group": "common" + } +} +``` +{% endcode %} + +The Property Editor UI cannot be used for Content Types if no Property Editor Schema is specified in the manifest. However, it can still be utilized to manipulate JSON. A case of that could be a Settings property for another Property Editor UI or Schema. + +### Settings + +The Property Editor UI settings are used for configuration related to rendering the UI in the backoffice. This is the same for Property Editor Schemas: + +{% hint style="info" %} +The Property Editor UI inherits the Settings of its Property Editor Schema. +{% endhint %} + +**Manifest** + +{% code title="umbraco-package.json" %} +```json +{ + "type": "propertyEditorUi", + "alias": "My.PropertyEditorUI.TextArea", + //... more + "meta": { + //... more + "settings": { + "properties": [ + { + "alias": "rows", + "label": "Number of rows", + "description": "If empty - 10 rows would be set as the default value", + "propertyEditorUiAlias": "Umb.PropertyEditorUi.Integer", + }, + ], + "defaultData": [ + { + "alias": "rows", + "value": 10, + }, + ], + }, + }, +}; +``` +{% endcode %} + +## The Property Editor UI Element + +Implement the `UmbPropertyEditorUiElement` interface, to secure your Element live up to the requirements of this. + +```typescript +interface UmbPropertyEditorUiElement extends HTMLElement { + name?: string; + value?: unknown; + config?: UmbPropertyEditorConfigCollection; + mandatory?: boolean; + mandatoryMessage?: string; + destroy?: () => void; +} +``` + +{% hint style="info" %} +The `UmbPropertyEditorUiElement` interface ensures that your Element has the necessary properties and methods to be used as a Property Editor UI Element. + +See the [UI API documentation](https://apidocs.umbraco.com/v15/ui-api/interfaces/packages_core_property-editor.UmbPropertyEditorUiElement.html) for more information. +{% endhint %} + +**Example with LitElement** + +{% code title="my-text-box.ts" lineNumbers="true" %} +```typescript +import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; +import { css, customElement, html, property } from '@umbraco-cms/backoffice/external/lit'; +import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; +import type { + UmbPropertyEditorConfigCollection, + UmbPropertyEditorUiElement, +} from '@umbraco-cms/backoffice/property-editor'; +import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; + +@customElement('umb-property-editor-ui-text-box') +export default class UmbPropertyEditorUITextBoxElement extends UmbLitElement implements UmbPropertyEditorUiElement { + @property() + value?: string; + + @property({ attribute: false }) + config?: UmbPropertyEditorConfigCollection; + + #onInput(e: InputEvent) { + this.value = (e.target as HTMLInputElement).value; + this.dispatchEvent(new UmbChangeEvent()); + } + + override render() { + return html``; + } + + static override readonly styles = [ + UmbTextStyles, + css` + uui-input { + width: 100%; + } + `, + ]; +} + +declare global { + interface HTMLElementTagNameMap { + 'umb-property-editor-ui-text-box': UmbPropertyEditorUITextBoxElement; + } +} +``` +{% endcode %} diff --git a/16/umbraco-cms/customizing/property-editors/full-examples-value-converters.md b/16/umbraco-cms/customizing/property-editors/full-examples-value-converters.md new file mode 100644 index 00000000000..a094928f6a1 --- /dev/null +++ b/16/umbraco-cms/customizing/property-editors/full-examples-value-converters.md @@ -0,0 +1,55 @@ +# Content Picker Value Converter Example + +{% include "../../.gitbook/includes/obsolete-warning-snapshot-publishedcache.md" %} + +{% code title="ContentPickerPropertyConverter.cs" %} + +```csharp +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Cms.Core.PublishedCache; + +namespace UmbracoDocs.Samples; + +public class ContentPickerPropertyConverter : IPropertyValueConverter +{ + private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor; + + // Injecting the PublishedSnapshotAccessor for fetching content + public ContentPickerPropertyConverter(IPublishedSnapshotAccessor publishedSnapshotAccessor) + => _publishedSnapshotAccessor = publishedSnapshotAccessor; + + public bool IsConverter(IPublishedPropertyType propertyType) + => propertyType.EditorAlias.Equals("Umbraco.ContentPicker"); + + public bool? IsValue(object? value, PropertyValueLevel level) + { + return level switch + { + PropertyValueLevel.Source => value is string stringValue && string.IsNullOrWhiteSpace(stringValue) is false, + _ => throw new NotSupportedException($"Invalid level: {level}.") + }; + } + + public Type GetPropertyValueType(IPublishedPropertyType propertyType) + => typeof(IPublishedContent); + + public PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) + => PropertyCacheLevel.Elements; + + public object? ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object? source, bool preview) + // parse the source string to a GuidUdi intermediate value + => source is string stringValue && UdiParser.TryParse(stringValue, out GuidUdi? guidUdi) + ? guidUdi + : null; + + public object? ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object? inter, bool preview) + // inter is expected to be a GuidUdi at this point (see ConvertSourceToIntermediate) + => inter is GuidUdi guidUdi + ? _publishedSnapshotAccessor.GetRequiredPublishedSnapshot().Content?.GetById(guidUdi.Guid) + : null; +} +``` + +{% endcode %} diff --git a/16/umbraco-cms/customizing/property-editors/images/JSON-schema.png b/16/umbraco-cms/customizing/property-editors/images/JSON-schema.png new file mode 100644 index 00000000000..70003504b6b Binary files /dev/null and b/16/umbraco-cms/customizing/property-editors/images/JSON-schema.png differ diff --git a/16/umbraco-cms/customizing/property-editors/images/data-types-references.png b/16/umbraco-cms/customizing/property-editors/images/data-types-references.png new file mode 100644 index 00000000000..d09a0db0f02 Binary files /dev/null and b/16/umbraco-cms/customizing/property-editors/images/data-types-references.png differ diff --git a/16/umbraco-cms/customizing/property-editors/images/document-references (1).png b/16/umbraco-cms/customizing/property-editors/images/document-references (1).png new file mode 100644 index 00000000000..504116f0e2a Binary files /dev/null and b/16/umbraco-cms/customizing/property-editors/images/document-references (1).png differ diff --git a/16/umbraco-cms/customizing/property-editors/images/document-references.png b/16/umbraco-cms/customizing/property-editors/images/document-references.png new file mode 100644 index 00000000000..504116f0e2a Binary files /dev/null and b/16/umbraco-cms/customizing/property-editors/images/document-references.png differ diff --git a/16/umbraco-cms/customizing/property-editors/images/example-of-property-actions (1).jpg b/16/umbraco-cms/customizing/property-editors/images/example-of-property-actions (1).jpg new file mode 100644 index 00000000000..2e94865f559 Binary files /dev/null and b/16/umbraco-cms/customizing/property-editors/images/example-of-property-actions (1).jpg differ diff --git a/16/umbraco-cms/customizing/property-editors/images/example-of-property-actions.jpg b/16/umbraco-cms/customizing/property-editors/images/example-of-property-actions.jpg new file mode 100644 index 00000000000..2e94865f559 Binary files /dev/null and b/16/umbraco-cms/customizing/property-editors/images/example-of-property-actions.jpg differ diff --git a/16/umbraco-cms/customizing/property-editors/images/media-references.png b/16/umbraco-cms/customizing/property-editors/images/media-references.png new file mode 100644 index 00000000000..504116f0e2a Binary files /dev/null and b/16/umbraco-cms/customizing/property-editors/images/media-references.png differ diff --git a/16/umbraco-cms/customizing/property-editors/integrate-property-editors.md b/16/umbraco-cms/customizing/property-editors/integrate-property-editors.md new file mode 100644 index 00000000000..30640fa5cda --- /dev/null +++ b/16/umbraco-cms/customizing/property-editors/integrate-property-editors.md @@ -0,0 +1,42 @@ +# Integrate Property Editors + +Property Editors can be used and implemented anywhere in the Umbraco Backoffice. + +## Property & Property Dataset Components + +The simplest way to integrate one or more Property Editors is done using two Components: the Property Dataset component and a Property component. + +The `umb-property` component renders a property using a Property Editor UI. + +The `umb-property-dataset` component provides the dataset for any properties within. It holds the data even if the actual property is not rendered. This makes it possible to hide properties in tabs or other ways. + +In the following example a dataset is implemented with two properties: + +```js + + + + +``` + +Notice how the values of the properties are handled by the dataset, leaving you with one component to integrate. + +[Read more about Property Dataset here](property-dataset.md) diff --git a/16/umbraco-cms/customizing/property-editors/integrate-validation.md b/16/umbraco-cms/customizing/property-editors/integrate-validation.md new file mode 100644 index 00000000000..2b960b020cb --- /dev/null +++ b/16/umbraco-cms/customizing/property-editors/integrate-validation.md @@ -0,0 +1,88 @@ +--- +description: >- + Learn how to bind and use the validation system when working with Form + Controls and Umbraco CMS backoffice. +--- + +# Integrate Validation + +The Validation System provides abilities to validate different Form Controls. Such can be native or custom, like a Property Editor. + +It also allows for binding server validation to the Form Controls making the validation experience as synergetic as possible. + +## Validation Context + +Validation Context, the hub of the Validation, is the core of this system. Everything that holds opinions about the Validation, is a Validator and is connected to the Validation Context. + +You can ask the Validation Context to validate. This will evaluate all validators, and once all validator instances have been validated successfully, the Validation Context will be valid. + +## Validators + +We provide a few built-in Validators that handle most cases. + +### Form Control Validator + +This Validator binds a Form Control Element with the Validation Context. When the Form Control becomes Invalid, its Validation Message is appended to the Validation Context. + +Notice this one also comes as a Lit Directive called `umbBindToValidation`. This enables you to integrate an element with one line of code within a Lit Render method. See the following example for a demonstration: + +```typescript + +#validation = new UmbValidationContext(this); + +#validate = () => { + this.#validation.validate().then(() => { + console.log('Valid'); + }, () => { + console.log('Invalid'); + }); +} + +render() { + return html` + + + + Validate + `; +} +``` + +## Integrate Umb-Property Elements + +The `umb-property` element automatically binds to its nearest validation context. + +This is demonstrated in the example below: + +```typescript + +#validation = new UmbValidationContext(this); + +#validate = () => { + this.#validation.validate().then(() => { + console.log('Valid'); + }, () => { + console.log('Invalid'); + }); +} + +render() { + return html` + + + Validate + `; +} +``` + +## Server Validation and more + +This documentation is not available at the moment. For the moment you can find more information in the [Backoffice repository](https://github.com/umbraco/Umbraco-CMS/tree/ced3db8542d390bb12082ca63ef71b790da220c5/src/Umbraco.Web.UI.Client/src/packages/core/validation). diff --git a/16/umbraco-cms/customizing/property-editors/property-actions.md b/16/umbraco-cms/customizing/property-editors/property-actions.md new file mode 100644 index 00000000000..e40d6b560b6 --- /dev/null +++ b/16/umbraco-cms/customizing/property-editors/property-actions.md @@ -0,0 +1,111 @@ +--- +description: Guide on how to implement Property Actions for Property Editors in Umbraco +--- + +# Property Actions + +{% hint style="warning" %} +This page is a work in progress and may undergo further revisions, updates, or amendments. The information contained herein is subject to change without notice. +{% endhint %} + +Property Actions are a built-in feature that provide a generic place for secondary functionality for property editors. + +Property Actions appear as a small button next to the label of the property, which expands to show the available actions. They are defined and implemented in the Property Editor, making it open as to what a Property Action is. + +## Data Structure of Property Actions + +Property Actions are an array of objects defining each action. An action is defined by the following properties: + +```js +{ + labelKey: 'clipboard_labelForRemoveAllEntries', + labelTokens: [], + icon: 'trash', + method: removeAllEntries, + isDisabled: true +} +``` + +We use `labelKey` and `labelTokens` to retrieve a localized string that is displayed as the Actions label. [See localization for more info.](../../extending/language-files/) + +`isDisabled` is used to disable an Action, which change the visual appearance and prevents interaction. Use this option when an action wouldn't provide any change. In the example above, the action `remove all entries` would not have any impact if there is no entries. + +## Implementation + +The implementation of Property Actions varies depending on whether your Property Editor is implemented with a Controller or as a Component. + +### Controller Implementation + +When your Property Editor is implemented with a Controller, use the following approach for the Property Action: + +```js +angular.module("umbraco").controller("My.MarkdownEditorController", function ($scope) { + +function myActionExecutionMethod() { + alert('My Custom Property Action Clicked'); + // Disable the action so it can not be re-run + // You may have custom logic to enable or disable the action + // Based on number of items selected etc... + myAction.isDisabled = true; +}; + +var myAction = { + labelKey: 'general_labelForMyAction', + labelTokens: [], + icon: 'action', + method: myActionExecutionMethod, + isDisabled: false +} + +var propertyActions = [ + myAction +]; + +this.$onInit = function () { + if ($scope.umbProperty) { + $scope.umbProperty.setPropertyActions(propertyActions); + } +}; + + +}); +``` + +### Component Implementation + +Follow this guide if your Property Editor is implemented as a Component. The Component must be configured to retrieve an optional reference to `umbProperty`. The requirement must be optional because property-editors are implemented in scenarios where it's not presented. + +See the following example: + +```js +angular.module('umbraco').component('myPropertyEditor', { + controller: MyController, + controllerAs: 'vm', + require: { + umbProperty: '?^umbProperty' + } + … +}); +``` + +See the following example for implementation of Property Actions in a Component, notice the difference is that we are parsing actions to `this.umbProperty.setPropertyActions(...)`. + +```js +var myAction = { + labelKey: 'general_labelForMyAction', + labelTokens: [], + icon: 'action', + method: myActionExecutionMethod, + isDisabled: false +} + +var propertyActions = [ + myAction +]; + +this.$onInit = function () { + if (this.umbProperty) { + this.umbProperty.setPropertyActions(propertyActions); + } +}; +``` diff --git a/16/umbraco-cms/customizing/property-editors/property-dataset.md b/16/umbraco-cms/customizing/property-editors/property-dataset.md new file mode 100644 index 00000000000..c28a50ded44 --- /dev/null +++ b/16/umbraco-cms/customizing/property-editors/property-dataset.md @@ -0,0 +1,52 @@ +# Property Dataset + +A Property Dataset is a Context API that holds the data for a set of properties. + +It is required for the `umb-property` element to have a Property Dataset provided. It can be provided via JavaScript code or an Element as documented below. + +## Property dataset component + +The `umb-property-dataset` component provides a Property Dataset Context for any properties within. This provides a way to implement such purely via Elements. + +In the following example a dataset is implemented by using the `umb-property-dataset` component together with with two `umb-property` components: + +```xml + + + + +``` + +## Consume values + +Since a Property Dataset is a Context any descending code can consume it and utilize the values. + +Such a case could be a Workspace View that wants to display the value of a specific property. + +The following example shows how to consume the Property Dataset and observe the value of a property with the alias of `my-property-alias`. + +```typescript +this.consumeContext(UMB_PROPERTY_DATASET_CONTEXT, async (datasetContext) => { + this.observe(await datasetContext?.propertyValueByAlias('my-property-alias'), (value) => { + console.log('the value of `my-property-alias` is', value) + }); +}); +``` diff --git a/16/umbraco-cms/customizing/property-editors/property-value-converters.md b/16/umbraco-cms/customizing/property-editors/property-value-converters.md new file mode 100644 index 00000000000..268afd7b7b0 --- /dev/null +++ b/16/umbraco-cms/customizing/property-editors/property-value-converters.md @@ -0,0 +1,206 @@ +--- +description: "A guide to creating a custom property value converter in Umbraco" +--- + + +# Property Value Converters + +A Property Value Converter converts a property editor's database-stored value to another type. The converted value can be accessed from MVC Razor or any other Published Content API. + +Published property values have four "Values": + +- **Source** - The raw data stored in the database, this is generally a `String` +- **Intermediate** - An object of a type that is appropriate to the property, for example a nodeId should be an `Int` or a collection of nodeIds would be an integer array, `Int[]` +- **Object** - The object to be used when accessing the property using a Published Content API, for example UmbracoHelper's `GetPropertyValue` method + +## Registering PropertyValueConverters + +PropertyValueConverters are automatically registered when implementing the interface. Any given PropertyEditor can only utilize a single PropertyValueConverter. + +If you are implementing a PropertyValueConverter for a PropertyEditor that doesn't already have one, creating the PropertyValueConverter will automatically enable it. No further actions are needed. + +If you aim to override an existing PropertyValueConverter, possibly from Umbraco or a package, additional steps are necessary. Deregister the existing one to prevent conflicts in this scenario. + +```csharp +using System.Linq; +using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Core.DependencyInjection; + +public class MyComposer : IComposer +{ + public void Compose(IUmbracoBuilder builder) + { + //If the type is accessible (not internal) you can deregister it by the type: + builder.PropertyValueConverters().Remove(); + + //If the type is not accessible you will need to locate the instance and then remove it: + var contentPickerValueConverter = builder.PropertyValueConverters().GetTypes().FirstOrDefault(x => x.Name == "ContentPickerValueConverter"); + if (contentPickerValueConverter != null) + { + builder.PropertyValueConverters().Remove(contentPickerValueConverter); + } + } +} +``` + +The built-in PropertyValueConverters included with Umbraco, are currently marked as internal. This means you will not be able to remove them by type since the type isn't accessible outside of the namespace. In order to remove such PropertyValueConverters, you will need to look up the instance by name and then deregister it by the instance. This could be the case for other PropertyValueConverters included by packages as well, depending on the implementation details. + +## Implementing the Interface + +Implement `IPropertyValueConverter` from the `Umbraco.Cms.Core.PropertyEditors` namespace on your class + +```csharp +public class ContentPickerValueConverter : IPropertyValueConverter +``` + +## Methods - Information + +### IsConverter(IPublishedPropertyType propertyType) + +This method is called for each PublishedPropertyType (Document Type Property) at application startup. By returning `True` your value converter will be registered for that property type and your conversion methods will be executed whenever that value is requested. + +Example: Checking if the IPublishedPropertyType EditorAlias property is equal to the alias of the core content editor. +This check is a string comparison but we recommend creating a constant for it to avoid spelling errors: + +```csharp +public bool IsConverter(IPublishedPropertyType propertyType) +{ + return propertyType.EditorAlias.Equals(Constants.PropertyEditors.Aliases.ContentPicker); +} +``` + +### IsValue(object value, PropertyValueLevel level) + +This method is called to determine if the passed-in value is a value, and is of the level specified. There's a basic implementation of this in `PropertyValueConverterBase`. + +### GetPropertyValueType(IPublishedPropertyType propertyType) + +This is where you can specify the type returned by this Converter. This type will be used by ModelsBuilder to return data from properties using this Converter in the proper type. + +Example: Content Picker data is being converted to `IPublishedContent`. + +```csharp +public Type GetPropertyValueType(IPublishedPropertyType propertyType) +{ + return typeof(IPublishedContent); +} +``` + +### PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) + +Here you specify which level the property value is cached at. + +A property value can be cached at the following levels: + +#### `PropertyCacheLevel.Unknown` + +Do not use this cache level unless you know exactly what you're doing. We recommend using the `PropertyCacheLevel.Element` level. + +#### `PropertyCacheLevel.Element` + +The property value will be cached until its _element_ is modified. The element is what holds (or owns) the property. For example: + +- For properties used at the page level, the element is the entire page. +- For properties contained within Block List items, the element is the individual Block List item. + +This is the most commonly used cache level and should be your default, unless you have specific reasons to do otherwise. + +#### `PropertyCacheLevel.Elements` + +The property value will be cached until _any_ element (see above) is changed. This means that any change to any page will clear the property value cache. + +This is particularly useful for property values that contain references to other content or elements. For example, this cache level is utilized by the Content Picker to clear its property values from the cache upon content updates. + +#### `PropertyCacheLevel.Snapshot` + +{% hint style="warning" %} +`PropertyCacheLevel.Snapshot` is obsolete in Umbraco 15 and will be removed in a future version. +{% endhint %} + +The property value will only be cached for the duration of the current _snapshot_. + +A snapshot represents a point in time. For example, a snapshot is created for every content request from the frontend. When accessing a property in a snapshot using this cache level, it gets converted, cached throughout the snapshot, and later cleared. + +For all intents and purposes, think of this cache level as "per request". If your property value should _only_ be cached per request, this is the cache level you should use. Use it with caution, as the added property conversions incur a performance penalty. + +#### `PropertyCacheLevel.None` + +The property value will _never_ be cached. Every time a property value is accessed (even within the same snapshot) property conversion is performed explicitly. + +Use this cache level with extreme caution, as it incurs a massive performance penalty. + +```csharp +public PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) +{ + return PropertyCacheLevel.Elements; +} +``` + +## Methods - Conversion + +There are a few different levels of conversion which can occur. + +### ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object source, bool preview) + +This method should convert the raw data value into an appropriate type. For example, a node identifier stored as a `String` should be converted to an `Int` or `Udi`. + +Include a `using Umbraco.Extensions;` to be able to use the `TryConvertTo` extension method. + +```csharp +public object ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object source, bool preview) +{ + if (source == null) return null; + + var attemptConvertInt = source.TryConvertTo(); + if (attemptConvertInt.Success) + return attemptConvertInt.Result; + + var attemptConvertUdi = source.TryConvertTo(); + if (attemptConvertUdi.Success) + return attemptConvertUdi.Result; + + return null; +} +``` + +### ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) + +This method converts the Intermediate to an Object. The returned value is used by the `GetPropertyValue` method of `IPublishedContent`. + +{% include "../../.gitbook/includes/obsolete-warning-ipublishedsnapshotaccessor.md" %} + +The below example converts the nodeId (converted to `Int` or `Udi` by _ConvertSourceToIntermediate_) into an 'IPublishedContent' object. + +```csharp +public object ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) +{ + if (inter == null) + return null; + + if ((propertyType.Alias != null && PropertiesToExclude.Contains(propertyType.Alias.ToLower(CultureInfo.InvariantCulture))) == false) + { + IPublishedContent content; + if (inter is int id) + { + content = _publishedSnapshotAccessor.PublishedSnapshot.Content.GetById(id); + if (content != null) + return content; + } + else + { + var udi = inter as GuidUdi; + if (udi == null) + return null; + content = _publishedSnapshotAccessor.PublishedSnapshot.Content.GetById(udi.Guid); + if (content != null && content.ContentType.ItemType == PublishedItemType.Content) + return content; + } + } + + return inter; +} +``` + +## Sample + +[Content Picker to `IPublishedContent` using `IPropertyValueConverter` interface](full-examples-value-converters.md) diff --git a/16/umbraco-cms/customizing/property-editors/tracking.md b/16/umbraco-cms/customizing/property-editors/tracking.md new file mode 100644 index 00000000000..a476528d812 --- /dev/null +++ b/16/umbraco-cms/customizing/property-editors/tracking.md @@ -0,0 +1,95 @@ +--- +description: >- + Guide on how to implement tracking entity references for Property Editors in + Umbraco +--- + +# Tracking References + +Property editors can be extended further to track entity references that may be selected or referenced inside the property editor. For example in the core of the CMS we have added this to numerous property editors. + +A good example of this is the Media Picker. The CMS stores a reference to the selected media item, enabling the identification of content nodes that use that particular media item. This avoids it being accidentally deleted if it is being used. + +When a content node is saved it will save the entity references as relations. + +## Viewing References + +### For Media Items + +1. Go to the **Media** section. +2. Select a media item and click the **Info** tab. + +![Viewing media references](images/media-references.png) + +### For Content Nodes + +1. Go to the **Settings** section. +2. Under the **Relations** from the **Advanced** section, select **Related Document** relations. + +![Viewing document references](images/document-references.png) + +### For Data Types + +1. Go to the **Settings** section. +2. Expand the **Data Types** folder. +3. Select the **Data Type** you wish to view the references. +4. Navigate to the **Info** tab. + +![Viewing Data Type references](images/data-types-references.png) + +## Example + +The following example shows how to implement tracking for the inbuilt CMS property editor **Content Picker**. It will always add a specific media reference, regardless of what value is picked in the content picker. In your own implementations, you will need to parse the value stored from the property editor you are implementing. You will also need to find any references to picked items in order to track their references. + +{% code title="TrackingExample.cs" %} +```csharp +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.Editors; +using Umbraco.Cms.Core.PropertyEditors; + +namespace UmbracoDocs.Samples; + +public class TrackingExample : IDataValueReferenceFactory, IDataValueReference +{ + public IDataValueReference GetDataValueReference() => this; + + // Which Data Editor (Data Type) does this apply to - in this example it is the built in content picker of Umbraco + public bool IsForEditor(IDataEditor? dataEditor) + => dataEditor?.Alias.InvariantEquals(Constants.PropertyEditors.Aliases.ContentPicker) is true; + + public IEnumerable GetReferences(object? value) + { + // Value contains the raw data that is being saved for a property editor + // You can then analyse this data be it a complex JSON structure or something more trivial + // To add the chosen entities as references (as any UDI type including custom ones) + + // A very simple example + // This will always ADD a specific media reference to the collection list + // When it's a ContentPicker datatype + var references = new List(); + var udiType = UmbracoObjectTypes.Media.GetUdiType(); + var udi = Udi.Create(udiType, Guid.Parse("fbbaa38d-bd93-48b9-b1d5-724c46b6693e")); + var entityRef = new UmbracoEntityReference(udi); + references.Add(entityRef); + return references; + } +} +``` +{% endcode %} + +You'll need a Composer to enable the tracking example: + +{% code title="TrackingExampleComposer.cs" %} +```csharp +using Umbraco.Cms.Core.Composing; + +namespace UmbracoDocs.Samples; + +public class TrackingExampleComposer : IComposer +{ + public void Compose(IUmbracoBuilder builder) + => builder.DataValueReferenceFactories().Append(); +} +``` +{% endcode %} diff --git a/16/umbraco-cms/customizing/searchable-trees.md b/16/umbraco-cms/customizing/searchable-trees.md new file mode 100644 index 00000000000..b569a0ef57f --- /dev/null +++ b/16/umbraco-cms/customizing/searchable-trees.md @@ -0,0 +1,165 @@ +# Searchable Trees (ISearchableTree) + +{% hint style="warning" %} +This page is a work in progress and may undergo further revisions, updates, or amendments. The information contained herein is subject to change without notice. +{% endhint %} + +When you type a search term into the Umbraco backoffice search field, you'll see search results from all the Section Trees that your user account has permission to access: + +![Content Section Dashboards](<../../../10/umbraco-cms/extending/section-trees/images/backoffice-search-v8 (1).png>) + +The results are grouped by 'Section Tree' like Content, Media, Document Types. Each 'Tree' has its own associated search mechanism that receives the search term and looks for matches in the tree that is responsible for searching. + +You can create your own search mechanisms for your own custom sections or replace the default search implementation for a particular section tree. + +## Custom Tree Search + +To create a search for your own custom tree you need to create a C# class that implements the interface `Umbraco.Cms.Core.Trees.ISearchableTree`. + +### ISearchableTree + +```csharp +using System.Collections.Generic; +using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Core.Models.ContentEditing; + +namespace My.Website; + +public interface ISearchableTree : IDiscoverable +{ + /// + /// The alias of the tree that the belongs to + /// + string TreeAlias { get; } + + /// + /// Searches for results based on the entity type + /// + /// The search term used for finding matching results. + /// The number of records to return for a page of results. + /// The 0-based index for retrieving a page of search results. + /// Populated with the total number of results matching the provided search term. + /// + /// The starting point for the search, generally a node ID, but for members this is a member type alias. + /// + /// + Task SearchAsync(string query, int pageSize, long pageIndex, string? searchFrom = null); +} +``` + +Your implementation needs to return an IEnumerable of `SearchResultEntity` items: + +```csharp +public class SearchResultEntity : EntityBasic +{ + public SearchResultEntity() { + /// + /// The score of the search result + /// + [DataMember(Name = "score")] + public float Score { get; set; } + }; + +} +``` + +A `SearchResultEntity` consists of a Score (a Float value) identifying its relevance to the search term, and the set of `EntityBasic` properties that all Umbraco objects share: eg Name, Id, Udi, Icon, Trashed, Key, ParentId, Path, Alias, AdditionalData. + +#### Example implementation of ISearchableTree + +If we have a custom section Tree with the alias 'favouriteThingsAlias' (see the [custom tree example](extending-overview/extension-types/tree.md)) then we could implement searchability by creating the following C# class in our site: + +```csharp +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Models.ContentEditing; +using Umbraco.Cms.Core.Trees; + +namespace Umbraco.Docs.Samples.Web.Trees; + +public class FavouriteThingsSearchableTree : ISearchableTree +{ + public string TreeAlias => "favouriteThingsAlias"; + + public async Task SearchAsync(string query, int pageSize, long pageIndex, string searchFrom = null) + { + // your custom search implementation starts here + Dictionary favouriteThings = new Dictionary(); + favouriteThings.Add(1, "Raindrops on Roses"); + favouriteThings.Add(2, "Whiskers on Kittens"); + favouriteThings.Add(3, "Skys full of Stars"); + favouriteThings.Add(4, "Warm Woolen Mittens"); + favouriteThings.Add(5, "Cream coloured Unicorns"); + favouriteThings.Add(6, "Schnitzel with Noodles"); + + var searchResults = new List(); + + var matchingItems = favouriteThings.Where(f => f.Value.StartsWith(query, true, System.Globalization.CultureInfo.CurrentCulture)); + foreach (var matchingItem in matchingItems) + { + // Making up the Id/Udi for this example! - these would normally be different for each search result. + searchResults.Add(new SearchResultEntity() + { + Id = 12345, + Alias = "favouriteThingItem", + Icon = "icon-favorite", + Key = new Guid("325746a0-ec1e-44e8-8f7b-6e7c4aab36d1"), + Name = matchingItem.Value, + ParentId = -1, + Path = "-1,12345", + Score = 1.0F, + Trashed = false, + Udi = Udi.Create("document", new Guid("325746a0-ec1e-44e8-8f7b-6e7c4aab36d1")) + }); + } + // Set number of search results found + var totalFound = matchingItems.Count(); + // Return your results + return new EntitySearchResults(searchResults, totalFound); + } +} +``` + +That's all we need, after an application pool recycle, if we now search in the backoffice we'll see matches from our custom 'Favourite Things' tree: + +![Content Section Dashboards](<../../../10/umbraco-cms/extending/section-trees/images/favouritethings-search-v8 (1).png>) + +Umbraco automatically finds any implementation of `ISearchableTree` in your site and automatically configures it to be used for the custom section mentioned in the TreeAlias property. Be careful not to accidentally have two `ISearchableTree` implementations trying to search the 'same' TreeAlias, it's _one_ `ISearchableTree` per TreeAlias. + +## Replacing an existing Section Tree Search + +Perhaps you want to change the logic for searching an existing section of the site, (why? - well you might have a 'company name' property on a MemberType in the Member section, and you want searches for that company name to filter the members who work there, the default implementation will only search on Member Name). + +Or perhaps you want to replace Examine search in the backoffice with an external Search Service, e.g. Azure Search. In a cloud-hosted implementation you don't need to build the Examine indexes on each new server as your cloud hosting scales out. + +### Example + +First create your replacement custom `ISearchableTree` implementation, using the same approach as above, but specifying the TreeAlias of the Tree you aim to replace, e.g. 'Member'. + +```csharp +public string TreeAlias => "member"; +``` + +To avoid your custom implementation clashing with the default `ISearchableTree` for a Tree, you need to remove its `ISearchableTree` implementation from the collection of SearchableTrees using an `IComposer` when Umbraco starts up: + +```csharp +using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Core.DependencyInjection; +using Umbraco.Cms.Web.BackOffice.Trees; + +namespace Umbraco.Docs.Samples.Web.Trees; + +public class RemoveCoreMemberSearchableTreeComposer : IComposer +{ + public void Compose(IUmbracoBuilder builder) + { + // Remove core MemberTreeController + builder.SearchableTrees().Exclude(); + } +} +``` + +This would then allow your custom implementation of `ISearchableTree` with TreeAlias 'member' to be used when searching the Member Section Tree. diff --git a/16/umbraco-cms/customizing/section-trees.md b/16/umbraco-cms/customizing/section-trees.md new file mode 100644 index 00000000000..344f1035db2 --- /dev/null +++ b/16/umbraco-cms/customizing/section-trees.md @@ -0,0 +1,29 @@ +--- +description: An explanation on sections and trees in Umbraco +--- + +# Sections & Trees + +{% hint style="warning" %} +The Section & Trees articles are a work in progress and may undergo further revisions, updates, or amendments. The information contained herein is subject to change without notice. +{% endhint %} + +The Umbraco backoffice consists of sections (sometimes referred to as applications) which contain Trees. + +Each section is identified by its name in the top navigation ribbon of the Umbraco Backoffice. + +For example, when you load the backoffice, you'll see that the 'Content' section contains one tree: the content tree. Meanwhile, the 'Settings' section contains a number of trees such as Stylesheets, Document Types, Media Types, etc... + +You can create your own sections and trees to extend Umbraco. + +## [Sections](extending-overview/extension-types/sections/section.md) + +Describes Umbraco Sections, configuration, and APIs. + +## [Trees](extending-overview/extension-types/tree.md) + +Describes Umbraco Trees, configuration, APIs, and events. + +## [Searchable Trees (ISearchableTree)](searchable-trees.md) + +Explains how to customize the backoffice search of a Section Tree. diff --git a/16/umbraco-cms/customizing/ui-library.md b/16/umbraco-cms/customizing/ui-library.md new file mode 100644 index 00000000000..ad0d3f57824 --- /dev/null +++ b/16/umbraco-cms/customizing/ui-library.md @@ -0,0 +1,51 @@ +--- +description: Find out more about Umbraco UI Library, UI API and Storybook. +--- + +# UI Library + +## UI Library and UI API + +With the UI Library, you get a collection of visual building blocks that consists of pieces to build any UI in Umbraco. Each component is a building block updating its display according to the data passed to it. + +{% hint style="info" %} +**Are you looking for the AngularJS documentation?** + +With Umbraco 14 the Umbraco backoffice has been rebuilt using Web Components and TypeScript. This means that AngularJS is no longer being used in Umbraco CMS, hence the removal of the corresponding documentation. +{% endhint %} + +With the UI API, you get a set of collections related to modules export, interfaces, and hierarchy. This includes code examples and much more that you can use to extend the backoffice. + + +
Backoffice UI LibrarySee, test, and get a feel for the familiar backoffice components built using the new UI components.https://apidocs.umbraco.com/v15/ui/Documentations Icons_Umbraco_CMS_Fundamentals_Backoffice (1) (2).png
Backoffice UI APIFind reference documentation about all types and contexts in the Backoffice.https://apidocs.umbraco.com/v15/ui-api/Documentations Icons_Umbraco_CMS_Fundamentals_Code.png
+ +## UI Library Storybook + +The Umbraco UI Library is a set of web components that can be used to build Umbraco User Interfaces. The UI Library separates the user interface from Umbraco’s business logic and creates a unified user experience. This is done with coherent styling and naming, across all the Umbraco platforms and projects including the ones developed by you. + +[Storybook](https://uui.umbraco.com/) is an application that gathers all the components together of the UI Library. It holds the documentation for the components and showcases different use case scenarios. You can explore all the components through stories reflecting their use cases. + +Each story has interactive controls that allow you to change the state of the component in real time. Every publicly available property is editable in Storybook, so you can test out custom configurations and use cases. + +You can also modify the custom properties in the stylesheet to see how the component will look. Every story has a code example that you can copy and paste into your project. This will allow you to implement the components in your own packages and extensions. + +### Getting Started with the UI Library + +The [Storybook](https://uui.umbraco.com/) is the starting point for working with the Umbraco UI Library. The Storybook contains two tabs: + +1. Canvas - The Canvas tab allows to use the interactive controls. + +
+2. Documentation - Here, you can find code examples for all the stories and use them in your markup. You can look it up by tag name or head to the project repository, where, in the packages folder, you will find all the component packages with all the necessary scripts and examples in the readme files. + +
+ +### Import UI Library Components + +You can also work with the components on a code level. If you want to do so here is how you import it: + +```typescript +import { UUIButtonElement } from '@umbraco-cms/backoffice/external/uui'; +``` + +This requires that your Package has the `@umbraco-cms/backoffice` dependency. diff --git a/16/umbraco-cms/customizing/umbraco-package.md b/16/umbraco-cms/customizing/umbraco-package.md new file mode 100644 index 00000000000..e48638e5615 --- /dev/null +++ b/16/umbraco-cms/customizing/umbraco-package.md @@ -0,0 +1,251 @@ +--- +description: An extension begins with a Umbraco Package +--- + +# Umbraco Package + +A Package is declared via an Umbraco Package JSON file. This describes the Package and declares one or more UI Extensions. The Package declaration is a JSON file that is stored in the `App_Plugins/{YourPackageName}` folder. The file is named `umbraco-package.json`. + +## Sample + +Here is a sample package. It should be stored in a folder in `App_Plugins/{YourPackageName}`, with the name `umbraco-package.json`. In this example, the package name is `SirTrevor` and is a text box property Data Type. + +{% hint style="info" %} +Before Umbraco 14, a package was declared in a `package.manifest` file instead of `umbraco-package.json`. The old format is no longer supported, but you can migrate the contents to the new format. +{% endhint %} + +{% code title="umbraco-package.json" lineNumbers="true" %} +```json +{ + "id": "My.Nuget.Package", + "name": "Sir Trevor", + "version": "1.0.0-beta", + "extensions": [ + { + "type": "propertyEditorUi", + "alias": "Sir.Trevor", + "name": "Sir Trevor Property Editor UI", + "element": "/App_Plugins/SirTrevor/SirTrevor.js", + "meta": { + "label": "Sir Trevor", + "propertyEditorSchemaAlias": "Umbraco.TextBox", + "icon": "icon-code", + "group": "Pickers" + } + } + ] +} +``` +{% endcode %} + +## Root fields + +The `umbraco-package` accepts these fields: + +```json +{ + "id": "", + "name": "", + "version": "", + "allowTelemetry": true, + "allowPublicAccess": false, + "importmap": { + "imports": { + "": "" + }, + "scopes": { + "": "" + } + }, + "extensions": [] +} +``` + +### Id + +The unique identifier for your package. This is used to identify your package and should be unique to your package. If you are creating a package that is distributed via NuGet, you should use the NuGet package ID as the ID for your package. + +### Name + +Allows you to specify a friendly name for your package that will be used for telemetry. If no name is specified the name of the folder will be used instead. + +### Version + +The version of your package, if this is not specified there will be no version-specific information for your package. This is used for telemetry and to help users understand what version of your package they are using. It is also used for package migrations. The version should follow the [Semantic Versioning](https://semver.org/) format. + +### Allow Telemetry + +With this field, you can control the telemetry of this package, this will provide Umbraco with the knowledge of how many installations use this package. + +The default is `true`. + +Also known as: `allowPackageTelemetry` + +### Allow Public Access + +This field is used to allow public access to the package. If set to `true`, the package will be available for anonymous usage, for example on the login screen. If set to `false`, the package will only be available to logged-in users. + +The default is `false`. + +### Importmap + +The `importmap` field is an object that contains two properties: `imports` and `scopes`. This is used to define the import map for the package. The `imports` property is an object that contains the import map for the package. The `scopes` property is an object that contains the scopes for the package. + +**Example** + +This example shows how to define an import map for a module exported by a package: + +{% code title="umbraco-package.json" lineNumbers="true" %} +```json +{ + "importmap": { + "imports": { + "mypackage/services": "/App_Plugins/MyPackage/services/index.js", + } + } +} +``` +{% endcode %} + +The `imports` object contains the import map for the package. The key is the specifier for the module that is being imported, and the value is the URL of the module. + +This allows developers to consume modules that are exported by a package without having to know the exact path to the module: + +{% code title="index.js" %} +```javascript +import { MyService } from 'mypackage/services'; +``` +{% endcode %} + +Umbraco supports the current specification of the property as outlined on MDN Web Documentation: [importmap](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script/type/importmap). + +### Extensions + +The `extensions` field is an array of Extension Manifest objects. Each object describes a single client extension. + +Read more about these in the [Extension Manifests article](extending-overview/extension-registry/extension-manifest.md). + +These are the current types of UI Extensions: + +| Type | Description | +| -------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| authProvider | An authentication provider for [external login](../reference/security/external-login-providers.md). | +| appEntryPoint | An app entry point is a JavaScript module that is executed when any app is loaded (Login, Installer, Upgrader, and Backoffice). It will never be destroyed. Read more about [Entry Points](extending-overview/extension-types/app-entry-point.md). | +| backofficeEntryPoint | A backoffice entry point is a JavaScript module that is executed when the backoffice is loaded. It will be destroyed when the backoffice is closed or logged out. Read more about [Entry Points](extending-overview/extension-types/backoffice-entry-point.md). | +| blockEditorCustomView | A custom view for a block in the block editor. | +| bundle | A bundle is a collection of other manifests that can be loaded together. You would use this in lieu of a `backofficeEntryPoint` if all you needed was to load extensions through JavaScript. | +| condition | A condition that can be used to control the visibility of other UI Extensions. Read more about [Conditions](extending-overview/extension-types/condition.md). | +| currentUserAction | A current user action is a button that can be added to the current user view. | +| dashboard | A dashboard is a view that can be added to any section. It is displayed in the dashboard view with tabs. Read more about [Dashboards](extending-overview/extension-types/dashboard.md). | +| dashboardCollection | A dashboard collection is a view that can be added to a collection. | +| dynamicRootOrigin | A dynamic root origin is a dynamic root origin that can be added to the Dynamic Root selector. | +| dynamicRootQueryStep | A dynamic root query step is a query step that can be added to the Dynamic Root selector. | +| entityAction | An entity action is a button that can be added to any entity, like a document, media, member, etc. It will be shown in the entity actions menu and in the entity actions menu. | +| entityBulkAction | An entity bulk action is a button that can be added to the bulk actions menu, which is shown when multiple entities are selected in a collection view. | +| entryPoint | (Deprecated) Old name for `backofficeEntryPoint`. | +| globalContext | A global context is a context instance that is available to all components in the Backoffice. It is used to share state between components and to provide a way to communicate between components. Read more about [Global Context](extending-overview/extension-types/global-context.md). | +| granularUserPermissions | A granular user permission is a permission that can be added to a user. It is used to control access to specific parts of the Backoffice. | +| headerApp | A header app is a component that can be added to the header such as a button or a link. Read more about [Header Apps](extending-overview/extension-types/header-apps.md). | +| healthCheck | A health check is a check that can be added to the health check dashboard. Read more about the backend side of [Health Checks](../reference/configuration/healthchecks.md). | +| icons | An icon is a set of icons that can be added to the icon picker. Read more in the [Icons article](extending-overview/extension-types/icons.md). | +| localization | A localization is a set of translations that can be added to the localization service. Read more about [Localization](foundation/localization.md) in the UI. | +| menu | A menu is a component that can be added to the menu bar. Read more about [Menus](extending-overview/extension-types/menu.md). | +| menuItem | A menu item is a component that can be added to a menu. | +| mfaLoginProvider | This type of login provider is the UI component of [Two-Factor Authentication](../reference/security/two-factor-authentication.md) used to enable/disable the provider. | +| modal | A modal dialog. Read more about [Modals](extending-overview/extension-types/modals/). | +| monacoMarkdownEditorAction | A Monaco Markdown Editor action is a button that can be added to the toolbar of the [Markdown Property Editor](../fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/markdown-editor.md). | +| packageView | A package view is an optional view that can be shown in the "Packages" section of the Backoffice. The user can navigate to this view to see more information about the package and to manage it. | +| previewAppProvider | A preview app provider is a provider that can be used to provide a preview app for the Save and Preview action on a document. | +| propertyAction | A property action is a button that can be added to the property actions menu. | +| propertyEditorSchema | A property editor schema is a model that describes a Data Editor and its properties from the backend to the UI. It is used by Property Editor UIs. Read more about [Property Editors](property-editors/). | +| propertyEditorUi | A property editor UI is a UI component that can be added to content types. It is used to render the UI of a Data Editor. Read more about [Property Editors](property-editors/). | +| searchProvider | A search provider is a provider that can be used to provide search results for the search bar in the Backoffice. | +| searchResultItem | A search result item is a component that can be added to the search results. | +| theme | A theme is a set of styles that can be added to the Backoffice. The user can select their preferred theme in the current user modal. | +| tinyMcePlugin | A TinyMCE plugin is a plugin that can be added to the TinyMCE editor. Read more about [TinyMCE Plugins](../fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor-tinymce/plugins.md). | +| treeItem | A tree item that can be added to the tree. | +| tree | A tree that can be added to a section. | +| ufmComponent | This type of component is a formatter that can be added to the [Umbraco Flavoured Markdown](../reference/umbraco-flavored-markdown.md), which is used in property descriptions and advanced labels. | +| userProfileApp | A user profile app is a component that can be added to the current user view. | + +**Collections** + +| Type | Description | +| ---------------- | -------------------------------------------------------------------------- | +| collection | A collection to show a list of entities (documents, media, members, etc.). | +| collectionAction | A collection action is a button that can be added to a collection view. | +| collectionView | A collection view is a view that can be added to a collection. | + +**Stores and repositories** + +| Type | Description | +| ---------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| repository | A repository is a class that can be used to interact with a data source. It is used either by context classes or elements directly to interact with the data source. It typically holds a `store` instance. Read more about [Repositories](foundation/working-with-data/repositories.md). | +| store | A store is a context instance that is available to repositories. It is used by repositories to store data. Read more about [Stores](foundation/working-with-data/store.md). | +| itemStore | An item store is a store that can be used to store items. It is used by repositories to store items. | +| treeStore | A tree store is a store that can be used to store tree data. It is used by tree repositories to store tree data. | + +**Sections** + +| Type | Description | +| ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| section | A section is a section that can be added to the navigation bar of the Backoffice like the "Content" or "Media" sections that are built-in. Read more about [Sections](extending-overview/extension-types/sections/section.md). | +| sectionRoute | A section route is a route that can be added to a section. It is used to define the URL of the view that is displayed in the main content area of the Backoffice. | +| sectionSidebarApp | A section sidebar app that can be added to the section sidebar. Read more about [Section Sidebar Apps](extending-overview/extension-types/sections/section-sidebar.md). | +| sectionView | A section view is a view that can be added to a section. It is displayed in the main content area of the Backoffice. More than one view can be added to a section, and the user can switch between them. In that case, the views are displayed as tabs. Read more about [Section Views](extending-overview/extension-types/sections/section-view.md). | + +**Workspaces** + +| Type | Description | +| ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| workspace | A workspace is a component that can be added to an entity type. This is the editor you see, when you edit an entity. Read more about [Workspaces](workspaces.md). | +| workspaceActionMenuItem | A workspace action menu item is a button that can be added to the workspace action menu. | +| workspaceAction | A workspace action is a button that can be added to the workspace such as the "Save" button. Read more about [Workspace Actions](extending-overview/extension-types/workspaces/workspace-editor-actions.md). | +| workspaceContext | A workspace context is a context instance that is available to all components in the workspace. It is used to share state between components and to provide a way to communicate between components. Read more about [Workspace Context](extending-overview/extension-types/workspaces/workspace-context.md). | +| workspaceFooterApp | A workspace footer app is a component that can be added to the workspace footer. | +| workspaceView | A workspace view is a view that can be added to a workspace. It is displayed in the main content area of the workspace. More than one view can be added to a workspace, and the user can switch between them. In that case, the views are displayed as tabs. Read more about [Workspace Views](extending-overview/extension-types/workspaces/workspace-views.md). | + +Read more about [Extension Types](extending-overview/extension-types/README.md) in the Backoffice to get a better understanding of the different types of extensions. + +## Package Manifest IntelliSense + +Make your IDE be aware about the opportunities of the `umbraco-package.json` by adding a JSON schema. This gives your code editor abilities to autocomplete and knowledge about the format. This helps to avoid mistakes or errors when editing the `umbraco-package.json` file. + +### Adding inline schema + +Editors like Visual Studio can use the `$schema` notation in your file. + +```json +{ + "$schema": "../../umbraco-package-schema.json", + "name": "" +} +``` + +Hover over any of the properties to see the description of the property. You can also use the `Ctrl + Space` (Windows/Linux) or `CMD + Space` (macOS) shortcut to see the available properties. + +## Load Package Manifest files + +Umbraco scans the `App_Plugins` folder for `umbraco-package.json` files **two levels deep**. When found, the packages are loaded into the system. + +You may need to restart the application, if you add a new file or modify an existing manifest: + +If the runtime mode is `Production`, the manifests are cached for 30 days or until the application is restarted to improve performance. In other runtime modes, the cache is cleared every 10 seconds. + +{% hint style="info" %} +You can implement the interface [IPackageManifestReader](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Infrastructure.Manifest.IPackageManifestReader.html) to provide your own package manifest reader. This can be useful if you want to load package manifests from a different location or source. +{% endhint %} + +## Razor Class Library + +Umbraco also supports [Razor Class Library (RCL)](https://learn.microsoft.com/en-us/aspnet/core/razor-pages/ui-class?view=aspnetcore-8.0\&tabs=visual-studio#create-an-rcl-with-static-assets) projects that contain static web assets. The `umbraco-package.json` file can be placed in the `wwwroot` folder of the RCL project. The package will be loaded when the RCL is referenced by the main project. You must map the web path to `App_Plugins` in your `.csproj` file: + +{% code title="MyProject.Assets.csproj" %} +```xml + + App_Plugins/{YourPackageName} + +``` +{% endcode %} + +Read more about getting set up for Backoffice development in the [Customize Backoffice](overview.md) section. diff --git a/16/umbraco-cms/customizing/workspaces.md b/16/umbraco-cms/customizing/workspaces.md new file mode 100644 index 00000000000..9606400ec88 --- /dev/null +++ b/16/umbraco-cms/customizing/workspaces.md @@ -0,0 +1,24 @@ +# Workspaces + +{% hint style="warning" %} +This page is a work in progress and may undergo further revisions, updates, or amendments. The information contained herein is subject to change without notice. +{% endhint %} + +A Workspace is the editor for a specific entity type. It can either be a view of data or a complex editor with multiple views. + +* A workspace is based on an entity type (for example content, media, member, etc.) and a unique string (ex: key). +* Most workspaces hold a draft state of an entity. It is a copy of the entity data that can be modified at runtime and sent to the server to be saved. +* A workspace can be a single view or consist of multiple views. +* A workspace should host a workspace context, with which anything within can communicate. + +

Workspace

+ +```ts +interface UmbWorkspaceElement {} +``` + +## [Workspace Context](extending-overview/extension-types/workspaces/workspace-context.md) + +## [Workspace Views](extending-overview/extension-types/workspaces/workspace-views.md) + +## [Workspace Actions](extending-overview/extension-types/workspaces/workspace-editor-actions.md) diff --git a/16/umbraco-cms/extending/backoffice-search.md b/16/umbraco-cms/extending/backoffice-search.md new file mode 100644 index 00000000000..ff396b6f9e5 --- /dev/null +++ b/16/umbraco-cms/extending/backoffice-search.md @@ -0,0 +1,103 @@ +--- +description: A guide to customization of Backoffice Search +--- + +# Backoffice Search + +The search facility of the Umbraco Backoffice allows the searching 'across sections' of different types of Umbraco entities, for example Content, Media, Members. However 'by default' only a small subset of standard fields are searched: + +| Node Type | Propagated Fields | +| ------------ | ---------------------- | +| All Nodes | Id, _NodeId_ and _Key_ | +| Media Nodes | UmbracoFileFieldName | +| Member Nodes | email, loginName | + +An Umbraco implementation might have additional custom properties that it would be useful to include in a Backoffice Search. For example: an 'Organisation Name' property on a Member Type, or a 'Product Code' field for a 'Product' content item. + +## Adding custom properties to backoffice search + +To add custom properties, it is required to register a custom implementation of `IUmbracoTreeSearcherFields`. We recommend to override the existing `UmbracoTreeSearcherFields`. + +Your custom implementation needs to be registered in the container. For example in the `Program.cs` file or in a composer, as an alternative. + +```csharp +builder.CreateUmbracoBuilder() + .AddBackOffice() + .AddWebsite() + .AddDeliveryApi() + .AddComposers() + .Build(); + +builder.Services.AddUnique(); +``` + +or + +```csharp +using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Core.DependencyInjection; +using Umbraco.Cms.Infrastructure.Examine; +using Umbraco.Extensions; + +namespace Umbraco.Docs.Samples.Web.BackofficeSearch; + +public class BackofficeSearchComposer : IComposer +{ + public void Compose(IUmbracoBuilder builder) + { + builder.Services.AddUnique(); + } +} +``` + +{% hint style="warning" %} +The below example is using `ILocalizationService` which is currently obselete and will be removed in v15. Use `ILanguageService` instead. +{% endhint %} + +```csharp +using System.Collections.Generic; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Infrastructure.Examine; +using Umbraco.Cms.Infrastructure.Search; + +namespace Umbraco.Docs.Samples.Web.BackofficeSearch; + +public class CustomUmbracoTreeSearcherFields : UmbracoTreeSearcherFields, IUmbracoTreeSearcherFields +{ + public CustomUmbracoTreeSearcherFields(ILocalizationService localizationService) : base(localizationService) + { + } + + //Adding custom field to search in all nodes + public new IEnumerable GetBackOfficeFields() + { + return new List(base.GetBackOfficeFields()) { "nodeType" }; + } + + //Adding custom field to search in document types + public new IEnumerable GetBackOfficeDocumentFields() + { + return new List(base.GetBackOfficeDocumentFields()) { "nodeType" }; + } + + //Adding custom field to search in media + public new IEnumerable GetBackOfficeMediaFields() + { + return new List(base.GetBackOfficeMediaFields()) { "nodeType" }; + } + + //Adding custom field to search in members + public new IEnumerable GetBackOfficeMembersFields() + { + return new List(base.GetBackOfficeMembersFields()) { "nodeType" }; + } +} +``` + +{% hint style="warning" %} +You cannot use this to search on integer types in the index, as an example `parentID` does not work. +{% endhint %} + +## More advanced extensions + +For further extensibility of the Umbraco Backoffice search implementation check [ISearchableTree](../customizing/searchable-trees.md) diff --git a/16/umbraco-cms/extending/build-on-umbraco-functionality.md b/16/umbraco-cms/extending/build-on-umbraco-functionality.md new file mode 100644 index 00000000000..a32a1bc6c98 --- /dev/null +++ b/16/umbraco-cms/extending/build-on-umbraco-functionality.md @@ -0,0 +1,38 @@ +--- +description: >- + Learn more about how to extend and build in the features and functionalities + with the Umbraco CMS. +--- + +# Build on Umbraco functionality + +One of the biggest strengths of the Umbraco CMS is its flexibility. It is possible to extend and customize all aspects of the product, tailoring it to fit perfectly to any project. + +{% hint style="info" %} +Are you looking to **customize and build extensions for the Umbraco backoffice?** + +All articles related to customizing and extending the Umbraco backoffice have been moved to the [Customize the Backoffice](../customizing/overview.md) section. +{% endhint %} + +In this section, you can find resources to build and extend Umbraco CMS functionality. + +
Backoffice SearchLearn how to customize the built-in search functionality in the backoffice.Documentations Icons_Umbraco_CMS_Reference_Searching.pngbackoffice-search.md
PackagesAdd your custom code into packages and distribute them to the rest of the Umbraco users.Documentations Icons_Umbraco_CMS_Extending_Packages.pngpackages
Health ChecksLearn about the health checks that you can run on your site to verify its state.Documentations Icons_Umbraco_CMS_Extending_Health_Checks.pnghealth-check
+ +## Other ways to extend the Umbraco CMS backoffice + +* [Embedded Media Providers](embedded-media-providers.md) +* [Language files and localization](language-files/) + +## Also in this section + +{% content-ref url="database.md" %} +[database.md](database.md) +{% endcontent-ref %} + +{% content-ref url="key-vault.md" %} +[key-vault.md](key-vault.md) +{% endcontent-ref %} + +{% content-ref url="filesystemproviders/" %} +[filesystemproviders](filesystemproviders/) +{% endcontent-ref %} diff --git a/16/umbraco-cms/extending/creating-custom-seed-key-provider.md b/16/umbraco-cms/extending/creating-custom-seed-key-provider.md new file mode 100644 index 00000000000..64fbd4ea869 --- /dev/null +++ b/16/umbraco-cms/extending/creating-custom-seed-key-provider.md @@ -0,0 +1,115 @@ +--- +description: A guide to creating a custom seed key provider for Umbraco +--- + +# Creating a Custom Seed Key Provider + +Umbraco uses a lazy loaded cache, which means that content is loaded into the cache on an as-needed basis. However, you may need specific content to always be in the cache. To achieve this you can implement your own custom seed key providers. + +There are two types of seed key providers: `IDocumentSeedKeyProvider` for documents and `IMediaSeedKeyProvider` for media. As these interfaces are identical only `IDocumentSeedKeyProvider` is demonstrated in this article. + +{% hint style="warning" %} +Seed keys are cached and calculated once. Any documents created after the site has started will not be included in the seed keys until after a server restart. +{% endhint %} + +## Implementation + +This example implements a `IDocumentSeedKeyProvider` which seeds all the children of a node, in this case blog posts. + +1. Create a new class called `BlogSeedKeyProvider` that implements `IDocumentSeedKeyProvider`. + +```csharp +using Umbraco.Cms.Infrastructure.HybridCache; + +namespace MySite.SeedKeyProviders; + +public class BlogSeedKeyProvider : IDocumentSeedKeyProvider +{ + public ISet GetSeedKeys() + { + } +} +``` + +Next we'll inject the `IDocumentNavigationQueryService` in order to get the children of the blog node. + +```csharp +using Umbraco.Cms.Core.Services.Navigation; +using Umbraco.Cms.Infrastructure.HybridCache; + +namespace MySite.SeedKeyProviders; + +public class BlogSeedKeyProvider : IDocumentSeedKeyProvider +{ + private readonly IDocumentNavigationQueryService _documentNavigationQueryService; + + public BlogSeedKeyProvider(IDocumentNavigationQueryService documentNavigationQueryService) + => _documentNavigationQueryService = documentNavigationQueryService; + +{...} +``` + +3. Parse a hardcoded string to a GUID. +4. Use the `IDocumentNavigationQueryService` to get the children of the blog node. +5. Return their keys as a `HashSet`. + +```csharp +public ISet GetSeedKeys() +{ + var blogRoot = Guid.Parse("a5fdb22d-b7f2-4a59-8c4e-46ed86bde56c"); + + if (_documentNavigationQueryService.TryGetChildrenKeys(blogRoot, out IEnumerable blogPostKeys)) + { + return new HashSet(blogPostKeys); + } + + return new HashSet(); +} +``` +Since this returns it as a set, and all the sets get unioned, we do not have to worry about duplicates. + +The final class looks like this: + +```csharp +using Umbraco.Cms.Core.Services.Navigation; +using Umbraco.Cms.Infrastructure.HybridCache; + +namespace MySite.SeedKeyProviders; + +public class BlogSeedKeyProvider : IDocumentSeedKeyProvider +{ + private readonly IDocumentNavigationQueryService _documentNavigationQueryService; + + public BlogSeedKeyProvider(IDocumentNavigationQueryService documentNavigationQueryService) + => _documentNavigationQueryService = documentNavigationQueryService; + + public ISet GetSeedKeys() + { + var blogRoot = Guid.Parse("a5fdb22d-b7f2-4a59-8c4e-46ed86bde56c"); + + if (_documentNavigationQueryService.TryGetChildrenKeys(blogRoot, out IEnumerable blogPostKeys)) + { + return new HashSet(blogPostKeys); + } + + return new HashSet(); + } +} +``` + +### Registering the Seed Key Provider + +Now that the `BlogSeedKeyProvider` is implemented, it must be registered in the `Startup` class. + +```csharp +using MySite.SeedKeyProviders; +using Umbraco.Cms.Infrastructure.DependencyInjection; +using Umbraco.Cms.Infrastructure.HybridCache; + +WebApplicationBuilder builder = WebApplication.CreateBuilder(args); + +builder.Services.AddSingleton(); +{...} +``` + +All blogpost will now be seeded into the cache on startup, and will always be present in the cache. diff --git a/16/umbraco-cms/extending/customize-backoffice/development-flow/images/vite-project-cli.jpg b/16/umbraco-cms/extending/customize-backoffice/development-flow/images/vite-project-cli.jpg new file mode 100644 index 00000000000..2258c172c82 Binary files /dev/null and b/16/umbraco-cms/extending/customize-backoffice/development-flow/images/vite-project-cli.jpg differ diff --git a/16/umbraco-cms/extending/database.md b/16/umbraco-cms/extending/database.md new file mode 100644 index 00000000000..423150c9bd5 --- /dev/null +++ b/16/umbraco-cms/extending/database.md @@ -0,0 +1,288 @@ +--- +description: A guide to creating a custom Database table in Umbraco +--- + +# Creating a Custom Database Table + +Umbraco ships with [NPoco](https://github.com/schotime/NPoco), which enables mapping the results of database queries to Common Language Runtime (CLR) objects. NPoco allows custom database tables to be added to your site to store additional data that should not be stored as normal content nodes. + +The end result looks like this: + +![Database result of a migration](<../../../10/umbraco-cms/extending/images/db-table (1) (1) (1).png>) + +## Using a Composer and Component + +The following code sample shows how this is done using a composer and component. + +When migrating from version 8 there are a few changes to be aware of. The first change is that namespace updates are dependencies that need to be passed to the `Upgrader.Execute()` method. Another is a change to the access modifier of the `Migrate()` method. + +```csharp +using Microsoft.Extensions.Logging; +using NPoco; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Core.Migrations; +using Umbraco.Cms.Core.Scoping; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Infrastructure.Migrations; +using Umbraco.Cms.Infrastructure.Migrations.Upgrade; +using Umbraco.Cms.Infrastructure.Persistence.DatabaseAnnotations; + +namespace MyNamespace; + +public class BlogCommentsComposer : ComponentComposer +{ +} + +public class BlogCommentsComponent : IComponent +{ + private readonly ICoreScopeProvider _coreScopeProvider; + private readonly IMigrationPlanExecutor _migrationPlanExecutor; + private readonly IKeyValueService _keyValueService; + private readonly IRuntimeState _runtimeState; + + public BlogCommentsComponent( + ICoreScopeProvider coreScopeProvider, + IMigrationPlanExecutor migrationPlanExecutor, + IKeyValueService keyValueService, + IRuntimeState runtimeState) + { + _coreScopeProvider = coreScopeProvider; + _migrationPlanExecutor = migrationPlanExecutor; + _keyValueService = keyValueService; + _runtimeState = runtimeState; + } + + public void Initialize() + { + if (_runtimeState.Level < RuntimeLevel.Run) + { + return; + } + + // Create a migration plan for a specific project/feature + // We can then track that latest migration state/step for this project/feature + var migrationPlan = new MigrationPlan("BlogComments"); + + // This is the steps we need to take + // Each step in the migration adds a unique value + migrationPlan.From(string.Empty) + .To("blogcomments-db"); + + // Go and upgrade our site (Will check if it needs to do the work or not) + // Based on the current/latest step + var upgrader = new Upgrader(migrationPlan); + upgrader.Execute(_migrationPlanExecutor, _coreScopeProvider, _keyValueService); + } + + public void Terminate() + { + } +} + +public class AddCommentsTable : MigrationBase +{ + public AddCommentsTable(IMigrationContext context) : base(context) + { + } + protected override void Migrate() + { + Logger.LogDebug("Running migration {MigrationStep}", "AddCommentsTable"); + + // Lots of methods available in the MigrationBase class - discover with this. + if (TableExists("BlogComments") == false) + { + Create.Table().Do(); + } + else + { + Logger.LogDebug("The database table {DbTable} already exists, skipping", "BlogComments"); + } + } + + [TableName("BlogComments")] + [PrimaryKey("Id", AutoIncrement = true)] + [ExplicitColumns] + public class BlogCommentSchema + { + [PrimaryKeyColumn(AutoIncrement = true, IdentitySeed = 1)] + [Column("Id")] + public int Id { get; set; } + + [Column("BlogPostUmbracoId")] + public int BlogPostUmbracoId { get; set; } + + [Column("Name")] + public required string Name { get; set; } + + [Column("Email")] + public required string Email { get; set; } + + [Column("Website")] + public required string Website { get; set; } + + [Column("Message")] + [SpecialDbType(SpecialDbTypes.NVARCHARMAX)] + public string Message { get; set; } + } +} +``` + +## Using a Notification Handler + +If building a new solution, you can adopt a new pattern. With this pattern you create and run a similar migration but trigger it in response to a [notification handler](../fundamentals/code/subscribing-to-notifications.md). + +The code for this approach is as follows: + +```csharp +using Microsoft.Extensions.Logging; +using NPoco; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Migrations; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Scoping; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Infrastructure.Migrations; +using Umbraco.Cms.Infrastructure.Migrations.Upgrade; +using Umbraco.Cms.Infrastructure.Persistence.DatabaseAnnotations; + +namespace MyNamespace; + +public class RunBlogCommentsMigration : INotificationHandler +{ + private readonly IMigrationPlanExecutor _migrationPlanExecutor; + private readonly ICoreScopeProvider _coreScopeProvider; + private readonly IKeyValueService _keyValueService; + private readonly IRuntimeState _runtimeState; + + public RunBlogCommentsMigration( + ICoreScopeProvider coreScopeProvider, + IMigrationPlanExecutor migrationPlanExecutor, + IKeyValueService keyValueService, + IRuntimeState runtimeState) + { + _migrationPlanExecutor = migrationPlanExecutor; + _coreScopeProvider = coreScopeProvider; + _keyValueService = keyValueService; + _runtimeState = runtimeState; + } + + public void Handle(UmbracoApplicationStartingNotification notification) + { + if (_runtimeState.Level < RuntimeLevel.Run) + { + return; + } + + // Create a migration plan for a specific project/feature + // We can then track that latest migration state/step for this project/feature + var migrationPlan = new MigrationPlan("BlogComments"); + + // This is the steps we need to take + // Each step in the migration adds a unique value + migrationPlan.From(string.Empty) + .To("blogcomments-db"); + + // Go and upgrade our site (Will check if it needs to do the work or not) + // Based on the current/latest step + var upgrader = new Upgrader(migrationPlan); + upgrader.Execute( + _migrationPlanExecutor, + _coreScopeProvider, + _keyValueService); + } +} + +// Migration and schema defined as in the previous code sample. +``` + +The notification handler can be registered in a composer: + +```csharp +using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Core.DependencyInjection; +using Umbraco.Cms.Core.Notifications; + +namespace TableMigrationTest; + +public class BlogCommentsComposer : IComposer +{ + public void Compose(IUmbracoBuilder builder) + { + builder.AddNotificationHandler(); + } +} +``` + +## Which to use? + +In short, it's up to you. If you are migrating from version 8 and want the quickest route to getting running with the latest version, then using a component makes sense. + +You will be using the notification pattern elsewhere. This could be when responding to Umbraco events that run many times in the lifetime of the application, like when content is saved. And so you may also prefer to align with that pattern for start-up events. + +It is also worth noting that components offer both `Initialize` and `Terminate` methods. With these you will need to handle two notifications to do the same with the notification handler approach (`UmbracoApplicationStartingNotification` and `UmbracoApplicationStoppingNotification`). A single handler class can be used for both notifications though. + +## Schema Class and Migrations + +**Important!** The `BlogCommentSchema` class nested inside the migration is purely used as a database schema representation class. It should not be used as a Data Transfer Object (DTO) to access the table data. Equally, you shouldn't use your DTO classes to define the schema used by your migration. Instead, you should create a duplicate snapshot for the purpose of creating or working with your database tables in the current migration. The name of the class is not important as you will be overriding it using the TableName attribute. You should choose a name that makes it clear that this class is purely for defining the schema in this migration. + +Whilst this adds a level of duplication, it is important that migrations and the code/classes within a migration remain immutable. If the DTO was to be used for both, it could cause unexpected behaviour. Should you later modify your DTO used in your application but you have previous migrations expecting the DTO to be in its unmodified state. + +Once a snapshot has been created, and once your code has been deployed, the snapshot should never be changed directly. Instead, you should use further migrations to alter the database table into the state you require. This ensures that migrations can be run in sequence and that each migration can expect the database to be in a known state before executing. + +When adding further migrations and if you need to reuse the schema class, it is a good idea to duplicate this in those particular migrations. You want the migrations to be immutable. Having separate classes in separate namespaces, reduces the risk of modifying a schema class from your initial migration. + +## Data stored in Custom Database Tables + +When storing data in custom database tables, this is by default not manageable by Umbraco at all. This can be great for many purposes such as storing massive amounts of data that you do not need to edit from the backoffice. Decoupling part of your data from being managed by Umbraco as content can be a way of achieving better performance for your site. It will no longer take up space in indexes and caches, and the Umbraco database. + +This also means that if you do need to edit or display this data, you need to implement the underlying functionality to support this. The same if the case if you need this data to be transferred or kept synchronized between multiple sites or environments. Data stored in custom tables are not supported by default by add-ons such as Umbraco Deploy and will not be deployable by default. + +Figuring out how to manage data across multiple environments can be different between individual sites and there is not one solution that fits all. Some sites may have automated database synchronization set up to ensure specific tables in multiple databases are always kept in sync. Other sites may be better off with scripts moving data around manually on demand. + +## Working with data in Custom Database Tables + +To create, read, update or delete data from your custom database tables, you can use the `IScopeProvider` to get access to the database operations. + +The following example creates a `Controller` that uses `[Route]` annotations to create API endpoints for fetching and inserting blog comments. + +{% hint style="info" %} +This example does not use the aforementioned `BlogCommentSchema` class but rather a separate (yet duplicate) class that is not part of the example. Also, be aware that things like error handling and data validation have been omitted for brevity. +{% endhint %} + +```csharp +using Microsoft.AspNetCore.Mvc; +using System.Collections.Generic; +using Umbraco.Cms.Infrastructure.Scoping; + +namespace MyNamespace; + +[ApiController] +[Route("/umbraco/api/blogcomments")] +public class BlogCommentsApiController : Controller +{ + private readonly IScopeProvider _scopeProvider; + public BlogCommentsApiController(IScopeProvider scopeProvider) + { + _scopeProvider = scopeProvider; + } + + [HttpGet("getcomments")] + public IEnumerable GetComments(int umbracoNodeId) + { + using var scope = _scopeProvider.CreateScope(); + var queryResults = scope.Database.Fetch("SELECT * FROM BlogComments WHERE BlogPostUmbracoId = @0", umbracoNodeId); + scope.Complete(); + return queryResults; + } + + [HttpPost("insertcomment")] + public void InsertComment(BlogComment comment) + { + using var scope = _scopeProvider.CreateScope(); + scope.Database.Insert(comment); + scope.Complete(); + } +} +``` diff --git a/16/umbraco-cms/extending/embedded-media-providers.md b/16/umbraco-cms/extending/embedded-media-providers.md new file mode 100644 index 00000000000..01978f63075 --- /dev/null +++ b/16/umbraco-cms/extending/embedded-media-providers.md @@ -0,0 +1,187 @@ +--- +description: A guide to creating a custom embed providers in Umbraco +--- + +# Embedded Media Providers + +The Rich Text Editor in Umbraco has an 'Embed' button, that when pressed, slides open a panel. This panel enables editors to paste the URL of a third-party media resource to embed in content. + +![The Rich Text Editor Embed Button](images/Embed-Button.png) + +For example, a YouTube Video: + +![Embedding a music video from YouTube](images/Embed-YouTube.png) + +The task of an `EmbedProvider` is to accept the pasted URL and write out the appropriate markup for the third-party provider associated with the URL. + +## Embed Provider Configuration + +Embed Providers are registered with the `EmbedProvidersCollection` during Composition when Umbraco boots. + +The list of available default Embed Providers in an Umbraco install is as follows: + +* YouTube +* YouTube Shorts +* Twitter (removed with version 14.2) +* X (available from version 14.2) +* Vimeo +* Dailymotion +* Flickr +* SlideShare +* Kickstarter +* Getty Images +* Ted +* SoundCloud +* Issuu +* Hulu +* Giphy + +You can see the details of these, and any recent editions in the C# developer reference for [Umbraco.Core.Media.EmbedProviders](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Media.EmbedProviders.html). + +## Configuring a new provider + +Create a new provider by creating a C# class that implements the `IEmbedProvider` interface. Umbraco provides a convenient `OEmbedProviderBase` class as a starting point. You can read more about this class in the [Api documentation](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Media.EmbedProviders.OEmbedProviderBase.html?q=OEmbedProviderBase). + +### Adding a new OEmbed Provider Example + +Let's allow our editors to embed artwork from the popular DeviantArt website - the world's largest online social community for artists and art enthusiasts. We can see they have information on using OEmbed: [https://www.deviantart.com/developers/oembed](https://www.deviantart.com/developers/oembed). The format of their OEmbed implementation returns a JSON format, from a URL `https://backend.deviantart.com/oembed?url=[urltoembed]`. We'll need to use the `OEmbedProviderBase` and the `base.GetJsonResponse` method. We can see 'links' to media shared on DeviantArt are in the format: `https://fav.me/[uniquemediaidentifier]`. We'll need a regex to match any URLs pasted into the embed panel that start with _fav.me_, achieved by setting the `UrlSchemeRegex` property. + +The Provider would look like this: + +{% code title="DeviantArtEmbedProvider.cs" lineNumbers="true" %} +```csharp +using Umbraco.Cms.Core.Media.EmbedProviders; +using Umbraco.Cms.Core.Serialization; + +namespace MyNamespace; + +public class DeviantArtEmbedProvider : OEmbedProviderBase +{ + public DeviantArtEmbedProvider(IJsonSerializer jsonSerializer) + : base(jsonSerializer) + { + } + + public override string ApiEndpoint => "https://backend.deviantart.com/oembed?url="; + + public override string[] UrlSchemeRegex => new[] + { + @"fav\.me/*", + @"\w+\.deviantart.com\/\w+\/art\/*", + @"\w+\.deviantart.com\/art\/*", + @"sta\.sh/*", + @"\w+\.deviantart.com\/\w+#\/d*" + }; + + public override Dictionary RequestParams => new(); + + public override string? GetMarkup(string url, int maxWidth = 0, int maxHeight = 0) + { + return GeOEmbedDataAsync(url, maxWidth, maxHeight, CancellationToken.None).GetAwaiter().GetResult(); + } + + public override async Task GeOEmbedDataAsync(string url, int? maxWidth, int? maxHeight, CancellationToken cancellationToken) + { + var requestUrl = base.GetEmbedProviderUrl(url, maxWidth, maxHeight); + OEmbedResponseWithStringDimensions? oembed = await base.GetJsonResponseAsync(requestUrl, cancellationToken); + + return oembed?.GetHtml(); + } +} +``` +{% endcode %} + +#### Register the provider with the `EmbedProvidersCollection` + +Create a new C# class that implements `IComposer` and append your new provider to the `EmbedProvidersCollection`: + +{% code title="RegisterEmbedProvidersComposer.cs" lineNumbers="true" %} +```csharp +using Umbraco.Cms.Core.Composing; + +namespace MyNamespace; + +public class RegisterEmbedProvidersComposer : IComposer +{ + public void Compose(IUmbracoBuilder builder) + => builder.EmbedProviders().Append(); +} +``` +{% endcode %} + +The new provider should be available for editors to use: + +![Embedding a Media Item from DeviantArt website](images/deviantart-embedded-media.png) + +Notice there isn't any implementation written here. The regex maps the incoming URL to the provider. The base methods handle the complication of requesting from the third-party API and turning the response into HTML. + +## Custom Embed Providers + +If your third-party media provider lacks OEmbed support or requires custom HTML due to content quirks, implement `GetMarkup()` without using base helper methods. + +### Custom Embed Provider Example + +Azure Media Services [(https://azure.microsoft.com/en-gb/services/media-services/)](https://azure.microsoft.com/en-gb/services/media-services/) provides 'broadcast-quality' video streaming services. You can embed the Azure Media Player into your site to play a video using an IFrame. + +You can create a custom `EmbedProvider` to embed an IFrame video player in your content. This can be done by taking the Media asset URL and writing out the required markup. + +{% code title="AzureVideoEmbedProvider.cs" lineNumbers="true" %} +```csharp +using System.Net; +using Umbraco.Cms.Core.Media.EmbedProviders; +using Umbraco.Cms.Core.Serialization; + +namespace MyNamespace; + +public class AzureVideoEmbedProvider : OEmbedProviderBase +{ + public AzureVideoEmbedProvider(IJsonSerializer jsonSerializer) + : base(jsonSerializer) + { + } + + // no ApiEndpoint! + public override string ApiEndpoint => string.Empty; + + public override string[] UrlSchemeRegex => new[] + { + @"windows\.net/*" + }; + + public override Dictionary RequestParams => new(); + + public override string? GetMarkup(string url, int maxWidth = 0, int maxHeight = 0) + { + // format of markup + string videoFormat = "
"; + + // pass in encoded Url, with and height, and turn off autoplay... + var videoPlayerMarkup = string.Format(videoFormat, WebUtility.UrlEncode(url) + "&autoplay=false", maxWidth, maxHeight); + + return videoPlayerMarkup; + } +} +``` +{% endcode %} + +Here the markup to embed has been manually constructed based upon the iframe video player, no request to an Api endpoint is made... + +#### Register the Azure Embed Provider with the `EmbedProvidersCollection` + +Create a new C# class that implements `IComposer` and add append your new provider to the `EmbedProvidersCollection`: + +{% code title="RegisterEmbedProvidersComposer.cs" lineNumbers="true" %} +```csharp +using Umbraco.Cms.Core.Composing; + +namespace MyNamespace; + +public class RegisterEmbedProvidersComposer : IComposer +{ + public void Compose(IUmbracoBuilder builder) + => builder.EmbedProviders().Append(); +} +``` +{% endcode %} + +Now editors can embed Azure Media video Urls in the format: `//amssamples.streaming.mediaservices.windows.net/3b970ae0-39d5-44bd-b3a3-3136143d6435/AzureMediaServicesPromo.ism/manifest`. diff --git a/16/umbraco-cms/extending/filesystemproviders/README.md b/16/umbraco-cms/extending/filesystemproviders/README.md new file mode 100644 index 00000000000..18b491480c0 --- /dev/null +++ b/16/umbraco-cms/extending/filesystemproviders/README.md @@ -0,0 +1,220 @@ +--- +description: A guide to creating custom file systems in Umbraco +--- + +# Custom File Systems (IFileSystem) + +## Media Filesystem + +{% hint style="info" %} +Before considering a custom media file system, be sure to first read about the configuration options for `UmbracoMediaPath` and `UmbracoMediaPhysicalRootPath` in the [configuration reference docs](../../reference/configuration/globalsettings.md). These configurations may save you from creating your own media file system entirely. +{% endhint %} + +By default, Umbraco uses an instance of `PhysicalFileSystem` to handle the storage location of the media archive (wwwroot/media). + +This can be configured by composition: + +```csharp +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Core.DependencyInjection; +using Umbraco.Cms.Core.Hosting; +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Infrastructure.DependencyInjection; + +namespace UmbracoExamples.Composition; + +public class SetMediaFileSystemComposer : IComposer +{ + public void Compose(IUmbracoBuilder builder) + { + builder.SetMediaFileSystem((factory) => + { + IHostingEnvironment hostingEnvironment = factory.GetRequiredService(); + var folderLocation = "~/CustomMediaFolder"; + var rootPath = hostingEnvironment.MapPathWebRoot(folderLocation); + var rootUrl = hostingEnvironment.ToAbsolute(folderLocation); + + return new PhysicalFileSystem( + factory.GetRequiredService(), + hostingEnvironment, + factory.GetRequiredService>(), + rootPath, + rootUrl); + }); + } +} +``` + +When creating a `PhysicalFileSystem` it takes some dependencies like `IIOHelper`, but the last two parameters are what we're interested in. + +The `rootPath` is where your media will be stored on the disk. Since netcore by default stores files in the `wwwroot`, we must put our desired folder somewhere within `wwwroot` to ensure that we use `hostingEnvironment.MapPathWebRoot(~/CustomMediaFolder)`. The `~` will be mapped to your `wwwroot` folder, so the final `rootPath` will be `your/project/path/wwwroot/CustomMediaFolder`. The `~` is therefore important. + +The `rootUrl` is the base URL that your media files will be served from. In this case, your image URL could look something like `mysite.com/CustomMediaFolder/MyAwesomePicture.png`. Again the `~` is important. + +In the code sample above, the `rootUrl` must map to the the same physical location as `rootPath`, which again must be placed under `wwwroot`. If you want to store the media files outside of `wwwroot` there is an extra step involved; you need to instruct netcore to include static files from a different physical location. + +The `rootUrl` is the base URL that your media files will be served from. In this case, your image URL could look something like `mysite.com/CustomMediaFolder/MyAwesomePicture.png`. Again the `~` is important. With the code sample above, the `rootUrl` must map to the same physical location as `rootPath`, otherwise, you will get 404's for your images. + +If you want to store the media files outside of `wwwroot` there is an extra step involved; you need to instruct netcore to include static files from a different physical location. + +In the `Program.cs` file, register a new static file location like so: + +```csharp +... +WebApplication app = builder.Build(); + +app.UseStaticFiles(new StaticFileOptions + { + FileProvider = new PhysicalFileProvider(Path.Combine("C:", "storage", "umbracoMedia")), + RequestPath = "/CustomPath" + }); +``` + +The PhysicalFileProvider takes a single parameter, the **`RootPath`**. This is the rooted filesystem path using directory separator chars and not ending with a directory separator, eg: `c:\storage\umbracoMedia` or `\\server\path`. The safest way to achieve this is using `Path.Combine`. + +You also have to specify the **`RequestPath`**. This is the relative URL where the media will be served using URL separator chars and not ending with a separator, eg: `/CustomPath` or `/Media`. + +Now you can use your newly registered static file location as if it was `wwwroot`. Notice how you no longer need to use `hostingEnvironment.MapPathWebRoot(folderLocation)`, since you're no longer trying to map the location to somewhere within `wwwroot`, but instead use your newly registered static file location. + +```csharp +public void Compose(IUmbracoBuilder builder) +{ + builder.SetMediaFileSystem((factory) => + { + IHostingEnvironment hostingEnvironment = factory.GetRequiredService(); + var rootPath = Path.Combine("C:", "storage", "umbracoMedia"); + var rootUrl = hostingEnvironment.ToAbsolute("/CustomPath"); + + return new PhysicalFileSystem( + factory.GetRequiredService(), + hostingEnvironment, + factory.GetRequiredService>(), + rootPath, + rootUrl); + }); +} +``` + +This is almost the same as when registering a location within the `wwwroot` folder. The only difference is that `rootPath` is now set to the path we gave the `PhysicalFileProvider` and the `rootUrl` is the same as we set as the `RequestPath` in the `StaticFileOption`. + +Our media is now stored in `C:\storage\umbracoMedia`, and is served from the base URL `/CustomPath`, so an image URL will look something like `mysite.com/CustomPath/MyAwesomePicture.png`. + +### Creating a custom file system + +You can replace `PhysicalFileSystem` with a custom file system implementation - eg. if you want your media files stored on Amazon S3 or elsewhere outside your site. + +To achieve this, you must first create your own file system by implementing the interfaces `IFileSystem` and `IFileProviderFactory` (the interfaces that are implemented by `PhysicalFileSystem`). + +You then replace the media filesystem by composition using `IUmbracoBuilder.SetMediaFileSystem(...)` (as is demonstrated in the paragraphs above), but instead of returning a `PhysicalFileSystem`, you return your own file system implementation. + +For inspiration on building a custom file system, have a look at the [Azure Blob Storage file system implementation](https://github.com/umbraco/Umbraco.StorageProviders#umbracostorageprovidersazureblob). + +### Accessing the media file system from code + +{% hint style="warning" %} +`UmbracoAuthorizedApiController` has been removed from Umbraco 14. Use`ManagementApiControllerBase` class instead. + +Read the [Creating a Backoffice API article](../../tutorials/creating-a-backoffice-api/README.md) for a comprehensive guide to writing APIs for the Management API. +{% endhint %} + +Since the default media file system can be swapped with custom implementations, you should never access the implementation directly. Umbraco uses a manager class called `MediaFileManager`. You can get a reference to this manager class via dependency injection in the constructor for your custom class or controller: + +```csharp +public class ImagesController : UmbracoAuthorizedApiController +{ + private readonly MediaFileManager _mediaFileManager; + + public ImagesController(MediaFileManager mediaFileManager) + { + _mediaFileManager = mediaFileManager; + } + +{...} +``` + +You can then access the configured file system provider through `_mediaFileManager.FileSystem`, which is the same way Umbraco will access the file system provider. + +## MediaPath Scheme + +The MediaPath Scheme defines the current set of rules that decide the format of the Media Path when it is saved into the media archive wherever it is located. + +By default the MediaPath scheme used by Umbraco is the `UniqueMediaPathScheme` this generates a unique 'folder' to place the uploaded image in eg. + +`/media/dozdrg2f/mylovelyimage.jpg` + +`/media` is defined by the PhysicalFileSystem and 'dozdrg2f' is generated by the `UniqueMediaPathScheme`. + +You can create your own logic for the path by implementing `IMediaPathScheme` and setting it during composition with: + +```csharp +builder.Services.AddUnique(); +``` + +## Other IFileSystems + +Umbraco also registers instances of `PhysicalFileSystem` for the following parts of Umbraco that persist to 'files': + +* `PartialViewsFileSystem` +* `StylesheetsFileSystem` +* `ScriptsFileSystem` +* `MvcViewsFileSystem` + +These are accessible via dependency injection. + +`IFileSystem`, `MediaFileManager`, and `FileSystems` are located in the `Umbraco.Cms.Core.IO` namespace. + +### Stylesheet Filesystem + +Like with the media file system it is also possible to replace the stylesheet filesystem with your own implementation of `IFileSystem` in a composer. It's important to note here that, unlike media file system, you cannot replace the filesystem with a `PhysicalFileSystem` using a different root path or root URL, this will not work, and will cause issues since the root path is coupled to the virtual path, given by the frontend, e.g. `/css/MyBeautifulStyle.css`. + +When replacing the stylesheet filesystem, you don't need to register it, since it's only available through Filesystems, what you need to do instead is configure the `FileSystems` to use your implementation for the `StylesheetsFileSystem`. + +The IUmbracoBuilder has an extension method for configuring the `FileSystems`, you need to invoke this method with an action that accepts an `IServiceProvider` and the `FileSystems` you will configure, configuring the `FileSystems` can look like this: + +```csharp +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.DependencyInjection; +using Umbraco.Cms.Core.Hosting; +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Infrastructure.DependencyInjection; + +namespace UmbracoExamples.Composition; + +public class FileSystemComposer : IComposer +{ + public void Compose(IUmbracoBuilder builder) + { + builder.ConfigureFileSystems((factory, systems) => + { + IIOHelper ioHelper = factory.GetRequiredService(); + IHostingEnvironment hostingEnvironment = factory.GetRequiredService(); + ILogger logger = factory.GetRequiredService>(); + GlobalSettings settings = factory.GetRequiredService>().Value; + + var path = settings.UmbracoCssPath; + var rootPath = hostingEnvironment.MapPathWebRoot(path); + var rootUrl = hostingEnvironment.ToAbsolute(path); + var fileSystem = new YourFileSystemImplementaion(ioHelper, hostingEnvironment, logger, rootPath, rootUrl); + + systems.SetStylesheetFilesystem(fileSystem); + }); + } +} + +``` + +Where `YourFileSystemImplementation` is a class that implements `IFileSystem`. This should always be done in a composer, since we do not recommend trying to change filesystems on the fly. + +After the `SetStylesheetFileSystem` method has run, `FileSystems.StylesheetsFileSystem` will return the instance that was created in the `ConfigureFileSystems` extension method. + +## Custom providers + +There is an Azure Blob Storage provider: + +* [Azure Blob Storage](azure-blob-storage.md) diff --git a/16/umbraco-cms/extending/filesystemproviders/azure-blob-storage.md b/16/umbraco-cms/extending/filesystemproviders/azure-blob-storage.md new file mode 100644 index 00000000000..b73e5367f11 --- /dev/null +++ b/16/umbraco-cms/extending/filesystemproviders/azure-blob-storage.md @@ -0,0 +1,98 @@ +--- +description: Setup your site to use Azure Blob storage for media and ImageSharp cache +--- + +# Using Azure Blob Storage for Media and ImageSharp Cache + +There are some scenarios where you may want or need to consider leveraging Azure Blob Storage. An example would be for media in Umbraco sites with substantial media libraries. + +Having your site's media in Azure Blob Storage can help your deployments complete more quickly. This has the potential to positively affect site performance, as the ImageSharp cache is moved to Azure Blob Storage. + +The setup consists of adding a package to your site, setting the correct configuration, and adding the services and middleware. Before you begin you’ll need to create an Azure Storage Account and a container for your media and ImageSharp cache. In this example, we assume your container name is "mysitestorage" and has already been created. + +See the [Microsoft documentation](https://docs.microsoft.com/en-us/azure/storage/blobs/storage-quickstart-blobs-portal) for a quickstart guide on how to create a blob storage container. + +## Installing the package + +Before you begin, you need to install the `Umbraco.StorageProviders.AzureBlob` and the `Umbraco.StorageProviders.AzureBlob.ImageSharp` NuGet packages. There are two approaches to installing the packages: + +1. Use your favorite Integrated Development Environment (IDE) and open up the NuGet Package Manager to search and install the packages +2. Use the command line to install the packages + +### Installing through command line + +Navigate to your project folder, which is the folder that contains your `.csproj` file. Now use the following `dotnet add package` commands to install the packages: + +```cmd +dotnet add package Umbraco.StorageProviders.AzureBlob +dotnet add package Umbraco.StorageProviders.AzureBlob.ImageSharp +``` + +The correct packages will have been installed in your project. + +## Configuring Blob storage + +The next step is to configure your blob storage. There are multiple approaches for this, but in this document, we're going to do it through `appsettings.json`. For more configuration options, see the [readme](https://github.com/umbraco/Umbraco.StorageProviders#umbracostorageproviders) on the GitHub repository. + +Open up your `appsettings.json` file and add the connection string and container name under `Umbraco:Storage:AzureBlob:Media`. Your Umbraco section of appsettings will look something like this: + +```json + "Umbraco": { + "Storage": { + "AzureBlob": { + "Media": { + "ConnectionString": "DefaultEndpointsProtocol=https;AccountName=;AccountKey=;EndpointSuffix=core.windows.net", + "ContainerName": "mysitestorage" + } + } + }, + "CMS": { + "Hosting": { + "Debug": false + }, + {...} + } + } +``` + +In this example, the container name is `mysitestorage`. + +{% hint style="info" %} +You can get your connection string from your Azure Portal under "Access Keys". +{% endhint %} + +## Setting the services and middleware + +You are almost there. The last step is to set up the required services and middleware. This can be done using extension methods. + +Invoke the `.AddAzureBlobMediaFileSystem()` and the `.AddAzureBlobImageSharpCache()` extension methods using the `CreateUmbracoBuilder()` builder chain in the `Program.cs` file like shown below. + +```csharp +builder.CreateUmbracoBuilder() + .AddBackOffice() + .AddWebsite() + .AddDeliveryApi() + .AddComposers() + .AddAzureBlobMediaFileSystem() // This configures the required services for Media + .AddAzureBlobImageSharpCache() // This configures the required services for the Image Sharp cache + .Build(); +``` + +Learn more about invoking and registering extension methods in the [Dependency Injection](../../reference/using-ioc.md) article. + +{% hint style="info" %} +**Upgrading from Umbraco 9/10**: + +As of [version 11.0.0](https://github.com/umbraco/Umbraco.StorageProviders/releases/tag/release-11.0.0) of `Umbraco.StorageProviders`, the ImageSharp dependency has been separated into its own package. + +Therefore, if you're planning on upgrading your site from Umbraco 9/10 to 11+, don't forget to install and setup the new `Umbraco.StorageProviders.AzureBlob.ImageSharp` package. This will ensure that your ImageSharp cache continues to be stored in your blob storage container. + +{% endhint %} + +Now when you launch your site again, the blob storage will be used to store media items as well as the ImageSharp cache. Do note though that the `/media` and `/cache` folders do not get created until a piece of media is uploaded. + +## Existing Media files + +Any media files you already have on your site will not automatically be added to the blob storage container. You will need to copy the contents on the `/wwwroot/media` folder and upload them to a new folder called `/media` in your blob storage container. Once you've done that, you can safely delete the `wwwroot/media` folder locally, as it is no longer needed. + +Any new media files you upload to the site will automatically be added to the Blob Storage. diff --git a/16/umbraco-cms/extending/filesystemproviders/images/config-from-backoffice.png b/16/umbraco-cms/extending/filesystemproviders/images/config-from-backoffice.png new file mode 100644 index 00000000000..a28c4f9bb7e Binary files /dev/null and b/16/umbraco-cms/extending/filesystemproviders/images/config-from-backoffice.png differ diff --git a/16/umbraco-cms/extending/health-check/README.md b/16/umbraco-cms/extending/health-check/README.md new file mode 100644 index 00000000000..366d89f3f90 --- /dev/null +++ b/16/umbraco-cms/extending/health-check/README.md @@ -0,0 +1,314 @@ +--- +description: "Health Checks are used to determine the state of your Umbraco project. Learn more about each of them in this section." +--- + +# Health Check + +The Settings section of the Umbraco backoffice holds a dashboard named "Health Check". It is a handy list of checks to see if your Umbraco installation is configured according to best practices. It's possible to add your custom-built health checks. + +For inspiration when building your checks you can look at the checks we've [built into Umbraco](https://github.com/umbraco/Umbraco-CMS/tree/v10/dev/src/Umbraco.Core/HealthChecks/Checks), as well as our [guides](guides/). Some examples will follow in this document. + +## Built-in checks + +Umbraco comes with the following checks by default: + +* Category **Configuration** + * **Notification Email Settings (id: `3E2F7B14-4B41-452B-9A30-E67FBC8E1206`)** - checks that the "from" email address used for email notifications has been changed from its default value +* Category **Data Integrity** + * **Database data integrity check (id: `73DD0C1C-E0CA-4C31-9564-1DCA509788AF`)** - checks for various data integrity issues in the Umbraco database +* Category **Live Environment** + * **Debug Compilation Mode (id: `61214FF3-FC57-4B31-B5CF-1D095C977D6D`)** - should be set to `debug="false"` on your live site + * **Runtime Mode (id: `8E31E5C9-7A1D-4ACB-A3A8-6495F3EDB932`)** - should be set to `Production` on your live site +* Category **Permissions** + * **Folder & File Permissions (id: `53DBA282-4A79-4B67-B958-B29EC40FCC23`)** - checks that the folders and files set with write permissions that are either required or recommended can be accessed +* Category **Security** + * **Application URL Configuration (id: `6708CA45-E96E-40B8-A40A-0607C1CA7F28`)** - checks if the Umbraco application URL is configured for your site. + * **Click-Jacking Protection (id: `ED0D7E40-971E-4BE8-AB6D-8CC5D0A6A5B0`)** - checks to see if a header or meta-tag is in place to indicate whether the site can be hosted in an IFRAME. Normally this is best set to deny permission for this to be done, to prevent what is known as [click-jacking](https://www.owasp.org/index.php/Clickjacking) attacks + * **Content/MIME Sniffing Protection (id: `1CF27DB3-EFC0-41D7-A1BB-EA912064E071`)** - checks that your site contains a header used to protect against Multipurpose Internet Mail Extensions (MIME) sniffing vulnerabilities + * **Cookie hijacking and protocol downgrade attacks Protection (HSTS) (id: `E2048C48-21C5-4BE1-A80B-8062162DF124`)** - checks if your HTTPS site contains the Strict-Transport-Security Header (HSTS). If not - adds with a default of 18 weeks + * **Cross-site scripting Protection (id: `F4D2B02E-28C5-4999-8463-05759FA15C3A`)** - checks for the presence of the X-XSS-Protection-header + * **Excessive Headers (id: `92ABBAA2-0586-4089-8AE2-9A843439D577`)** - checks to ensure that various headers that can provide details about the technology used to build and host the website have been removed + * **HTTPS Configuration (id: `EB66BB3B-1BCD-4314-9531-9DA2C1D6D9A7`)** - to determine if the current site is running on a secure connection + * **UseHttps check** - when the site is running on HTTPS, `Umbraco.Cms.Core.Configuration.Models.GlobalSettings.UseHttps` needs to be enabled to secure the backoffice. The setting can be found under `Umbraco:CMS:Global` in the `appsettings.json` file +* Category **Services** + * **SMTP Settings (id: `1B5D221B-CE99-4193-97CB-5F3261EC73DF`)** - checks that an Simple Mail Tranfer Protocol (SMTP) server is configured and is accepting requests for sending emails + +Each check returns a message indicating whether or not the issue in question has been found on the website installation. This could be an error that should be fixed, or a warning you should be aware of. + +Some of them can also be rectified via the dashboard, by clicking the **Fix** button and in some cases providing some required information. These changes usually involve writing to configuration files that will often trigger a restart of the website. + +## Configuring and scheduling checks + +As well as viewing the results of health checks via the Settings section dashboard, you can set up the checks to be run on a schedule and be notified of the results by email. It's also possible to disable certain checks if they aren't applicable in your environment. + +For more information, see the [Reference > Configuration > Health checks](../../reference/configuration/healthchecks.md) article. + +## Custom checks + +You can build your own health checks. There are two types of health checks you can build: **configuration checks** and **general checks**. + +Each health check is a class that needs to have a `HealthCheck` attribute. This attribute has a few things you need to fill in: + +* GUID - a unique ID that you've generated for this specific check +* Name - give it a short name so people know what the check is for +* Description - describes what the check does in detail +* Group - this is the category for the check if you use an existing group name (like "Configuration") the check will be added in that category, otherwise a new category will appear in the dashboard + +### Configuration checks + +These are small checks that take an [IConfiguration](https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.configuration.iconfiguration?view=dotnet-plat-ext-6.0) key and confirm that the value that's expected is there. If the value is not correct, there will be a link to a guide on how to set this value correct. + +* A configuration check needs to inherit from `Umbraco.Cms.Core.HealthChecks.Checks.AbstractSettingsCheck` +* A configuration check needs the `HealthCheck` attribute as noted at the start of this document +* `ReadMoreLink` is a link to an external guide that will help you to troubleshoot any problems +* `ValueComparisonType` can either be `ValueComparisonType.ShouldEqual` or `ValueComparisonType.ShouldNotEqual` +* `ItemPath` is the IConfiguration key path leading to the configuration value that you want to verify +* `Values` is a list of values that are available for this configuration item - in this example it can be `RemoteOnly` or `On`, they're both acceptable for a live site. + * For checks using the `ShouldEqual` comparison method, make sure to set one of these values to `IsRecommended = true`. + * Where `ShouldNotEqual` is used the fix will require the user to provide the correct setting +* `CurrentValue` is the current value from the configuration setting +* `CheckSuccessMessage` and `CheckErrorMessage` are the messages returned to the user + * It is highly recommended to use the `LocalizedTextService` so these can be localized. You can add the text in `~/Config/Lang/en-US.user.xml` (or whatever language you like) + +### General checks + +This can be anything you can think of, the results and the rectify action are completely under your control. + +* A general check needs to inherit from `Umbraco.Cms.Core.HealthChecks.HealthCheck` +* A general check needs the `HealthCheck` attribute as noted at the start of this document +* All checks run when the dashboard is loaded, this means that the `GetStatus()` method gets executed + * You can return multiple status checks from `GetStatus()` +* A status check returns a `HealthCheckStatus` + * If a `HealthCheckStatus` has a `HealthCheckAction` defined then the "Fix" button will perform that action once clicked + * Sometimes, the button to fix something should not be called "Fix", change the `Name` property of a `HealthCheckAction` to provide a better name + * `HealthCheckAction` has a `Description` property so that you can provide information on what clicking the "Rectify" button will do (or provide links to documentation, for example) + * `HealthCheckStatus` has a few result levels: + * `StatusResultType.Success` + * `StatusResultType.Error` + * `StatusResultType.Warning` + * `StatusResultType.Info` + * A `HealthCheckAction` needs to provide an alias for an action that can be picked up in the `ExecuteAction` method +* It is highly recommended to use the `LocalizedTextService` so text can be localized. You can add the text in `~/Config/Lang/en-US.user.xml` (or whatever language you like) + +An example check: + +```csharp +using Umbraco.Cms.Core.Extensions; +using Umbraco.Cms.Core.HealthChecks; +using Umbraco.Cms.Core.Services; + +namespace Umbraco.Web.HealthCheck.Checks.SEO; + +[HealthCheck("3A482719-3D90-4BC1-B9F8-910CD9CF5B32", "Robots.txt", + Description = "Create a robots.txt file to block access to system folders.", + Group = "SEO")] +public class HealthCheckNotifier : Cms.Core.HealthChecks.HealthCheck +{ + private readonly IHostEnvironment _hostEnvironment; + private readonly ILogger _logger; + private readonly ILocalizedTextService _textService; + + public HealthCheckNotifier(ILocalizedTextService textService, IHostEnvironment hostEnvironment, + ILogger logger) + { + _textService = textService; + _hostEnvironment = hostEnvironment; + _logger = logger; + } + + public override Task> GetStatus() => + Task.FromResult((IEnumerable)new[] { CheckForRobotsTxtFile() }); + + public override HealthCheckStatus ExecuteAction(HealthCheckAction action) + { + switch (action.Alias) + { + case "addDefaultRobotsTxtFile": + return AddDefaultRobotsTxtFile(); + default: + throw new InvalidOperationException("Action not supported"); + } + } + + private HealthCheckStatus CheckForRobotsTxtFile() + { + var success = File.Exists(_hostEnvironment.MapPathContentRoot("~/robots.txt")); + var message = success + ? _textService.Localize("healthcheck", "seoRobotsCheckSuccess") + : _textService.Localize("healthcheck", "seoRobotsCheckFailed"); + + var actions = new List(); + + if (success == false) + { + actions.Add(new HealthCheckAction("addDefaultRobotsTxtFile", Id) + // Override the "Rectify" button name and describe what this action will do + { + Name = _textService.Localize("healthcheck", "seoRobotsRectifyButtonName"), + Description = _textService.Localize("healthcheck", "seoRobotsRectifyDescription") + }); + } + + return + new HealthCheckStatus(message) + { + ResultType = success ? StatusResultType.Success : StatusResultType.Error, + Actions = actions + }; + } + + private HealthCheckStatus AddDefaultRobotsTxtFile() + { + var success = false; + var message = string.Empty; + const string content = @"# robots.txt for Umbraco +User-agent: * +Disallow: /umbraco/"; + + try + { + File.WriteAllText(_hostEnvironment.MapPathContentRoot("~/robots.txt"), content); + success = true; + } + catch (Exception exception) + { + _logger.LogError(exception, "Could not write robots.txt to the root of the site"); + } + + return + new HealthCheckStatus(message) + { + ResultType = success ? StatusResultType.Success : StatusResultType.Error, + Actions = new List() + }; + } +} +``` + +## Custom health check notifications + +Health check notifications can be scheduled to run periodically and notify you of the results. Included with Umbraco is a notification method to deliver the results via email. In a similar manner to how it's possible to create your health checks, you can also create custom notification methods to send the message summarising the status of the health checks via other means. Again, for further details on implementing this please refer to the [existing notification methods within the core code base](https://github.com/umbraco/Umbraco-CMS/tree/v10/dev/src/Umbraco.Core/HealthChecks/NotificationMethods). + +Each notification method needs to implement the core interface `IHealthCheckNotificationMethod` and, for ease of creation, can inherit from the base class `NotificationMethodBase`, which itself implements the `IHealthCheckNotificationMethod` interface. The class must also be decorated with an instance of the `HealthCheckNotificationMethod` attribute. There's one method to implement - `SendAsync(HealthCheckResults results)` - which is responsible for taking the results of the health checks and sending them via the mechanism of your choice. + +The following example shows how the core method for sending notification via email is implemented: + +```csharp +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Mail; +using Umbraco.Cms.Core.Models.Email; +using Umbraco.Cms.Core.Services; + +namespace Umbraco.Cms.Core.HealthChecks.NotificationMethods; + +[HealthCheckNotificationMethod("email")] +public class EmailNotificationMethod : NotificationMethodBase +{ + private readonly ILocalizedTextService? _textService; + private readonly IHostEnvironment? _hostEnvironment; + private readonly IEmailSender? _emailSender; + private readonly IMarkdownToHtmlConverter? _markdownToHtmlConverter; + private ContentSettings? _contentSettings; + + public EmailNotificationMethod( + ILocalizedTextService textService, + IHostEnvironment hostEnvironment, + IEmailSender emailSender, + IOptionsMonitor healthChecksSettings, + IOptionsMonitor contentSettings, + IMarkdownToHtmlConverter markdownToHtmlConverter) + : base(healthChecksSettings) + { + var recipientEmail = Settings?["RecipientEmail"]; + if (string.IsNullOrWhiteSpace(recipientEmail)) + { + Enabled = false; + return; + } + + RecipientEmail = recipientEmail; + + _textService = textService ?? throw new ArgumentNullException(nameof(textService)); + _hostEnvironment = hostEnvironment; + _emailSender = emailSender; + _markdownToHtmlConverter = markdownToHtmlConverter; + _contentSettings = contentSettings.CurrentValue ?? throw new ArgumentNullException(nameof(contentSettings)); + + contentSettings.OnChange(x => _contentSettings = x); + } + + public string? RecipientEmail { get; } + + public override async Task SendAsync(HealthCheckResults results) + { + if (ShouldSend(results) == false) + { + return; + } + + if (string.IsNullOrEmpty(RecipientEmail)) + { + return; + } + + var message = _textService?.Localize("healthcheck", "scheduledHealthCheckEmailBody", new[] + { + DateTime.Now.ToShortDateString(), + DateTime.Now.ToShortTimeString(), + _markdownToHtmlConverter?.ToHtml(results, Verbosity) + }); + + // Include the Umbraco Application URL host in the message subject so that + // you can identify the site that these results are for. + var host = _hostEnvironment?.ContentRootPath?.ToString(); + + var subject = _textService?.Localize("healthcheck", "scheduledHealthCheckEmailSubject", new[] { host }); + + + var mailMessage = CreateMailMessage(subject, message); + Task? task = _emailSender?.SendAsync(mailMessage, Constants.Web.EmailTypes.HealthCheck); + if (task is not null) + { + await task; + } + } + + private EmailMessage CreateMailMessage(string? subject, string? message) + { + var to = _contentSettings?.Notifications.Email; + + if (string.IsNullOrWhiteSpace(subject)) + subject = "Umbraco Health Check Status"; + + var isBodyHtml = message.IsNullOrWhiteSpace() == false && message!.Contains("<") && message.Contains(" options.SameOrigin()); +``` + +### Adding Click-Jacking Protection using manual middleware + +Avoid third-party library dependency by using custom middleware added to the request pipeline. + +```csharp +app.Use(async (context, next) => +{ + context.Response.Headers.Add("X-Frame-Options", "SAMEORIGIN"); + await next(); +}); +``` diff --git a/16/umbraco-cms/extending/health-check/guides/contentsecuritypolicy.md b/16/umbraco-cms/extending/health-check/guides/contentsecuritypolicy.md new file mode 100644 index 00000000000..055d41d1292 --- /dev/null +++ b/16/umbraco-cms/extending/health-check/guides/contentsecuritypolicy.md @@ -0,0 +1,54 @@ +--- +description: Implement a Content Security Policy (CSP) to protect your Umbraco site from XSS and data injection. +--- + +# Content Security Policy (CSP) + +_This check verifies if your site has a Content Security Policy (CSP) header to defend against Cross-Site Scripting (XSS) and data injection attacks._ + +## How to fix this health check +This health check can be fixed by adding a header before the response is started. + +Preferable you use a security library like [NWebSec](https://docs.nwebsec.com/). + +### Adding a Content Security Policy (CSP) using NWebSec + +If you take a NuGet dependency on [NWebsec.AspNetCore.Middleware/](https://www.nuget.org/packages/NWebsec.AspNetCore.Middleware/), you can use third extension methods on `IApplicationBuilder`. + +```csharp +... +WebApplication app = builder.Build(); +app.UseCsp(options => options + .ImageSources(s => s + .Self() + .CustomSources( + "our.umbraco.com data:", + "dashboard.umbraco.com")) + .DefaultSources(s => s + .Self() + .CustomSources( + "our.umbraco.com", + "marketplace.umbraco.com")) + .ScriptSources(s => s + .Self()) + .StyleSources(s => s + .Self()) + .FontSources(s => s + .Self()) + .ConnectSources(s => s + .Self()) + .FrameSources(s => s + .Self())); +``` + +### Adding a Content Security Policy (CSP) using manual middleware + +Avoid third-party library dependencies by using custom middleware added to the request pipeline as shown below. + +```csharp +app.Use(async (context, next) => +{ + context.Response.Headers.Append("Content-Security-Policy", "img-src 'self' our.umbraco.com data: dashboard.umbraco.com; default-src 'self' our.umbraco.com marketplace.umbraco.com; script-src 'self'; style-src 'unsafe-inline' 'self'; font-src 'self'; connect-src 'self'; frame-src 'self'; "); + await next(); +}); +``` \ No newline at end of file diff --git a/16/umbraco-cms/extending/health-check/guides/contentsniffingprotection.md b/16/umbraco-cms/extending/health-check/guides/contentsniffingprotection.md new file mode 100644 index 00000000000..ae71f98a164 --- /dev/null +++ b/16/umbraco-cms/extending/health-check/guides/contentsniffingprotection.md @@ -0,0 +1,68 @@ +--- +description: Protect your Umbraco site from MIME sniffing vulnerabilities using security headers like X-Content-Type-Options. +--- + +# Health check: Content/MIME Sniffing Protection + +_Checks that your site contains a header used to protect against Multipurpose Internet Mail Extensions (MIME) sniffing vulnerabilities._ + +## How to fix this health check + +This health check can be fixed by adding a header before the response is started. + +Preferable you use a security library like [NWebSec](https://docs.nwebsec.com/). + +### Adding Content/MIME Sniffing Protection using NWebSec + +If you take a NuGet dependency on [NWebsec.AspNetCore.Middleware/](https://www.nuget.org/packages/NWebsec.AspNetCore.Middleware/), you can use third extension methods on `WebApplication`. + +```csharp +... +WebApplication app = builder.Build(); + +app.UseXContentTypeOptions(); +... +``` + +### Adding Content/MIME Sniffing Protection using manual middleware + +If you do not like to have a dependency on third party libraries, you can add the following custom middleware to the request pipeline. + +First create the middleware class: + +```csharp +namespace MySite.Middleware; + +public class NoSniffMiddleware : IMiddleware +{ + public async Task InvokeAsync(HttpContext context, RequestDelegate next) + { + context.Response.Headers.Append("X-Content-Type-Options", "nosniff"); + await next(context); + } +} +``` + +Next register it in `Program.cs` + +```csharp +using MySite.Middleware; + +WebApplicationBuilder builder = WebApplication.CreateBuilder(args); + +builder.Services.AddSingleton(); + +builder.CreateUmbracoBuilder() + .AddBackOffice() + .AddWebsite() + .AddDeliveryApi() + .AddComposers() + .Build(); + +WebApplication app = builder.Build(); + +app.UseMiddleware(); + +await app.BootUmbracoAsync(); +... +``` diff --git a/16/umbraco-cms/extending/health-check/guides/crosssitescriptingprotection.md b/16/umbraco-cms/extending/health-check/guides/crosssitescriptingprotection.md new file mode 100644 index 00000000000..691135931ab --- /dev/null +++ b/16/umbraco-cms/extending/health-check/guides/crosssitescriptingprotection.md @@ -0,0 +1,11 @@ +# Health check: Cross-site scripting Protection (X-XSS-Protection header) + +{% hint style="warning" %} +This header is non-standard and should not be used. Instead, it is recommended to use a [Content Security Policy (CSP)](./contentsecuritypolicy.md) header. + +For more information about the X-XSS-Protection header, and why it should not be used, see [MDN web docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-XSS-Protection). +{% endhint %} + +## How to fix this health check + +This health check can be fixed by ensuring no middleware adds the header. diff --git a/16/umbraco-cms/extending/health-check/guides/debugcompilationmode.md b/16/umbraco-cms/extending/health-check/guides/debugcompilationmode.md new file mode 100644 index 00000000000..8395c9e09da --- /dev/null +++ b/16/umbraco-cms/extending/health-check/guides/debugcompilationmode.md @@ -0,0 +1,43 @@ +--- +description: Disable debug compilation mode in Umbraco to boost performance by updating JSON configuration. +--- + +# Health check: Debug Compilation Mode + +_Leaving debug compilation mode enabled can severely slow down a website and take up more memory on the server._ + +## How to fix this health check + +This health check can be fixed by providing configuration on the following path: `Umbraco:CMS:Hosting:Debug`. + +This configuration can be setup in a configuration source of your choice. This guide shows how to set it up in one of the JSON file sources. + +### Updating the JSON configuration + +The following JSON needs to be merged into one of your JSON sources. By default the following JSON sources are used: `appSettings.json` and `appSettings..json`, e.g. `appSettings.Development.json` or `appSettings.Production.json`. + +```json +{ + "Umbraco": { + "CMS": { + "Hosting": { + "Debug": + } + } + } +} +``` + +One example that can be used for production: + +```json +{ + "Umbraco": { + "CMS": { + "Hosting": { + "Debug": false + } + } + } +} +``` diff --git a/16/umbraco-cms/extending/health-check/guides/excessiveheaders.md b/16/umbraco-cms/extending/health-check/guides/excessiveheaders.md new file mode 100644 index 00000000000..bbab463ba86 --- /dev/null +++ b/16/umbraco-cms/extending/health-check/guides/excessiveheaders.md @@ -0,0 +1,46 @@ +# Health check: Excessive Headers + +_Checks to see if your site reveals information in its headers that gives away unnecessary details about the technology used to build and host it._ + +## How to fix this health check + +This health check can be fixed by removing headers before the response is started. + +Be aware these headers are often added by the server and not by the application. + +Unless you publicly expose the Kestrel server ([not recommended by Microsoft](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/servers/kestrel?view=aspnetcore-2.1&tabs=aspnetcore2x#when-to-use-kestrel-with-a-reverse-proxy)), you can't handle this directly in middleware. + +### Removing headers when hosted on IIS + +For IIS you will need to manipulate `web.config` (If you don't have `web.config` already in your project you will need to add it at the root). Ensure to remove the custom `X-Powered-By` and `Server` header as shown in the following example. + +{% hint style="info" %} +The `removeServerHeader` attribute is added in IIS 10.0 and does not work in versions of Windows prior to Windows Server version 1709 or Windows 10 version 1709. +{% endhint %} + +```xml + + + + + + + + + + + + + +``` + +### Removing headers when hosted on Kestrel + +By default Kestrel will only expose the `Server` header. To disable this, you have to configure Kestrel in `Program.cs`. You can use the `UseKestrel` extension method on `WebApplicationBuilder` like in the following example. + +```csharp +WebApplicationBuilder builder = WebApplication.CreateBuilder(args); + +builder.WebHost.UseKestrel(options => options.AddServerHeader = false); +... +``` diff --git a/16/umbraco-cms/extending/health-check/guides/fixedapplicationurl.md b/16/umbraco-cms/extending/health-check/guides/fixedapplicationurl.md new file mode 100644 index 00000000000..a8d9223a671 --- /dev/null +++ b/16/umbraco-cms/extending/health-check/guides/fixedapplicationurl.md @@ -0,0 +1,42 @@ +# Health check: Fixed Application URL + +_Check to make sure a fixed application URL is specified. This URL is for example used when sending emails from backoffice. +If this is not specified in configuration, Umbraco gets the application URL from last host used to request the application_ + + +## How to fix this health check + +This health check can be fixed by providing configuration on the following path: `Umbraco:CMS:WebRouting:UmbracoApplicationUrl`. + +This configuration can be setup in a configuration source of your choice. This guide shows how to set it up in one of the JSON file sources. + +### Updating the JSON configuration + +The following JSON needs to be merged into one of your JSON sources. By default the following JSON sources are used: `appSettings.json` and `appSettings..json`, e.g. `appSettings.Development.json` or `appSettings.Production.json`. + +```json +{ + "Umbraco": { + "CMS": { + "WebRouting": { + "UmbracoApplicationUrl": "string" + } + } + } +} +``` + +One example that can be used in production + +```json +{ + "Umbraco": { + "CMS": { + "WebRouting": { + "UmbracoApplicationUrl": "https://www.my-custom-domain.com/" + } + } + } +} +``` +{% hint style="info" %} If the site is hosted on Umbraco Cloud, changing the above configuration will have no effect. The site will always use the URL set in the`umbraco-cloud.json` file, which can not be changed. {% endhint %} diff --git a/16/umbraco-cms/extending/health-check/guides/folderandfilepermissions.md b/16/umbraco-cms/extending/health-check/guides/folderandfilepermissions.md new file mode 100644 index 00000000000..95dd3ded7e0 --- /dev/null +++ b/16/umbraco-cms/extending/health-check/guides/folderandfilepermissions.md @@ -0,0 +1,25 @@ +# Folder & File Permissions + +_Checks that the web server folder and file permissions are set correctly for Umbraco to run._ + +## How to fix this health check + +This health check can be fixed by ensuring that the process running Umbraco also has write access to the listed folders and files. + +### Updating the file permissions on Windows + +Here's an example of how to adjust permissions for a folder. This process works the same way for files. + +First we see an example of an error from the health check + +![Failed health check for folder creation](images/failed\_healthcheck\_folder\_permissions.png) + +To fix this, we find the specified folder, from the report and choose `Properties` and the `Security` tab. + +![Folder properties](images/folder\_properties.png) ![Folder properties - Security tab](images/folder\_properties\_security.png) + +From here you can edit the permissions for a specific user or user group. + +{% hint style="info" %} +For security reasons we recommend only giving write access to the required users or groups. +{% endhint %} diff --git a/16/umbraco-cms/extending/health-check/guides/httpsconfiguration.md b/16/umbraco-cms/extending/health-check/guides/httpsconfiguration.md new file mode 100644 index 00000000000..e96bd7b1a83 --- /dev/null +++ b/16/umbraco-cms/extending/health-check/guides/httpsconfiguration.md @@ -0,0 +1,43 @@ +# Health check: HTTPS Configuration + +_Checks if your site is configured to work over HTTPS and if the Umbraco related configuration for that is correct._ + +## How to fix this health check + +This health check checks a couple of things. + +First of all, it ensures that your website is running on HTTPS using a valid certificate. + +Furthermore, it is used to specify the configuration on the following path: `Umbraco:CMS:Global:UseHttps`. + +This configuration can be setup in a configuration source of your choice. This guide shows how to set it up in one of the JSON file sources. + +### Updating the JSON configuration + +The following JSON needs to be merged into one of your JSON sources. By default the following JSON sources are used: `appSettings.json` and `appSettings..json`, e.g. `appSettings.Development.json` or `appSettings.Production.json`. + +```json +{ + "Umbraco": { + "CMS": { + "Global": { + "UseHttps": + } + } + } +} +``` + +One example that can be used: + +```json +{ + "Umbraco": { + "CMS": { + "Global": { + "UseHttps": true + } + } + } +} +``` diff --git a/16/umbraco-cms/extending/health-check/guides/images/failed_healthcheck_folder_permissions.png b/16/umbraco-cms/extending/health-check/guides/images/failed_healthcheck_folder_permissions.png new file mode 100644 index 00000000000..6dc15b3c5af Binary files /dev/null and b/16/umbraco-cms/extending/health-check/guides/images/failed_healthcheck_folder_permissions.png differ diff --git a/16/umbraco-cms/extending/health-check/guides/images/folder_properties.png b/16/umbraco-cms/extending/health-check/guides/images/folder_properties.png new file mode 100644 index 00000000000..871cf463060 Binary files /dev/null and b/16/umbraco-cms/extending/health-check/guides/images/folder_properties.png differ diff --git a/16/umbraco-cms/extending/health-check/guides/images/folder_properties_security.png b/16/umbraco-cms/extending/health-check/guides/images/folder_properties_security.png new file mode 100644 index 00000000000..79a3fc7122e Binary files /dev/null and b/16/umbraco-cms/extending/health-check/guides/images/folder_properties_security.png differ diff --git a/16/umbraco-cms/extending/health-check/guides/notificationemail.md b/16/umbraco-cms/extending/health-check/guides/notificationemail.md new file mode 100644 index 00000000000..3ecbe7747fb --- /dev/null +++ b/16/umbraco-cms/extending/health-check/guides/notificationemail.md @@ -0,0 +1,43 @@ +# Health check: Notification Email Settings + +_If notifications are used, the 'from' email address should be specified and changed from the default value._ + +## How to fix this health check + +This health check can be fixed by providing configuration on the following path: `Umbraco:CMS:Content:Notifications:Email`. + +This configuration can be setup in a configuration source of your choice. This guide shows how to set it up in one of the JSON file sources. + +### Updating the JSON configuration + +The following JSON needs to be merged into one of your JSON sources. By default the following JSON sources are used: `appSettings.json` and `appSettings..json`, e.g. `appSettings.Development.json` or `appSettings.Production.json`. + +```json +{ + "Umbraco": { + "CMS": { + "Content": { + "Notifications": { + "Email": "" + } + } + } + } +} +``` + +One example that can be used: + +```json +{ + "Umbraco": { + "CMS": { + "Content": { + "Notifications": { + "Email": "no-reply@domain.com" + } + } + } + } +} +``` diff --git a/16/umbraco-cms/extending/health-check/guides/smtp.md b/16/umbraco-cms/extending/health-check/guides/smtp.md new file mode 100644 index 00000000000..8fce916f7b5 --- /dev/null +++ b/16/umbraco-cms/extending/health-check/guides/smtp.md @@ -0,0 +1,52 @@ +# Health check: SMTP + +_Checks that valid settings for sending emails are in place._ + +## How to fix this health check + +This health check can be fixed by providing configuration on the following path: `Umbraco:CMS:Global:Smtp` + +This configuration can be setup in a configuration source of your choice. This guide shows how to set it up in one of the JSON file sources. + +### Updating the JSON configuration + +The following JSON needs to be merged into one of your JSON sources. By default the following JSON sources are used: `appSettings.json` and `appSettings..json`, e.g. `appSettings.Development.json` or `appSettings.Production.json`. + +```json +{ + "Umbraco": { + "CMS": { + "Global": { + "Smtp": { + "From": "", + "Host": "", + "Port": , + "PickupDirectoryLocation": "", + "Username": "", + "Password": "", + "DeliveryMethod": "", + "SecureSocketOptions": "" + } + } + } + } +} +``` + +An example that can be used on localhost, is if you have a local Simple Mail Transfer Protocol (SMTP) server running during development. This could be a tool like [Smtp4dev](https://github.com/rnwood/smtp4dev). + +```json +{ + "Umbraco": { + "CMS": { + "Global": { + "Smtp": { + "From": "my@email.com", + "Host": "localhost", + "Port": 25 + } + } + } + } +} +``` diff --git a/16/umbraco-cms/extending/health-check/guides/stricttransportsecurityheader.md b/16/umbraco-cms/extending/health-check/guides/stricttransportsecurityheader.md new file mode 100644 index 00000000000..72cea315e11 --- /dev/null +++ b/16/umbraco-cms/extending/health-check/guides/stricttransportsecurityheader.md @@ -0,0 +1,40 @@ +--- +description: "Learn about the health checks that check for cookie hijacking and protocol downgrade attacks protection." +--- + +# Strict-Transport-Security Header + +Checks if your site, when running with HTTPS, contains the Strict-Transport-Security Header (HSTS). + +## How to fix this health check + +This health check can be fixed by adding the `Strict-Transport-Security` header to responses. The header tells browsers that future requests should be made over HTTPS only. + +{% hint style="warning" %} +Enabling HSTS on a domain will cause browsers to only use HTTPS (not HTTP) to communicate with your site. Only enable HSTS on domains that can, and should, use HTTPS exclusively. +{% endhint %} + +### Using the UseHsts extension method + +ASP.NET Core implements HSTS with the `UseHsts` extension method. + +You can add `UseHsts` after the `env.IsDevelopment()` check-in `Program.cs`. + +```csharp +if (builder.Environment.IsDevelopment()) +{ + app.UseDeveloperExceptionPage(); +} +else +{ + app.UseHsts(); +} + //... +} +``` + +This example only enables HSTS if the app is not running in development mode. `UseHsts` isn't recommended in development because the HSTS settings are highly cacheable by browsers. + +It is possible to configure a timespan for the HSTS, preferably six months. This can be done by adding a new builder to the `Program.cs` file. Learn more in the [official Microsoft Documentation](https://learn.microsoft.com/en-us/aspnet/core/security/enforcing-ssl?view=aspnetcore-8.0&tabs=visual-studio%2Clinux-ubuntu#http-strict-transport-security-protocol-hsts). + +Full details of `UseHsts`, and additional configuration, can be found in the [ASP.NET Core documentation](https://learn.microsoft.com/en-us/aspnet/core/security/enforcing-ssl?view=aspnetcore-8.0&tabs=visual-studio%2Clinux-ubuntu#http-strict-transport-security-protocol-hsts). diff --git a/16/umbraco-cms/extending/images/Canvas_tab (1).png b/16/umbraco-cms/extending/images/Canvas_tab (1).png new file mode 100644 index 00000000000..1e6b0c0730a Binary files /dev/null and b/16/umbraco-cms/extending/images/Canvas_tab (1).png differ diff --git a/16/umbraco-cms/extending/images/Canvas_tab (2).png b/16/umbraco-cms/extending/images/Canvas_tab (2).png new file mode 100644 index 00000000000..1e6b0c0730a Binary files /dev/null and b/16/umbraco-cms/extending/images/Canvas_tab (2).png differ diff --git a/16/umbraco-cms/extending/images/Canvas_tab.png b/16/umbraco-cms/extending/images/Canvas_tab.png new file mode 100644 index 00000000000..1e6b0c0730a Binary files /dev/null and b/16/umbraco-cms/extending/images/Canvas_tab.png differ diff --git a/16/umbraco-cms/extending/images/Docs_tab (1).png b/16/umbraco-cms/extending/images/Docs_tab (1).png new file mode 100644 index 00000000000..ac21325f172 Binary files /dev/null and b/16/umbraco-cms/extending/images/Docs_tab (1).png differ diff --git a/16/umbraco-cms/extending/images/Docs_tab (2).png b/16/umbraco-cms/extending/images/Docs_tab (2).png new file mode 100644 index 00000000000..ac21325f172 Binary files /dev/null and b/16/umbraco-cms/extending/images/Docs_tab (2).png differ diff --git a/16/umbraco-cms/extending/images/Docs_tab.png b/16/umbraco-cms/extending/images/Docs_tab.png new file mode 100644 index 00000000000..ac21325f172 Binary files /dev/null and b/16/umbraco-cms/extending/images/Docs_tab.png differ diff --git a/16/umbraco-cms/extending/images/Embed-Button.png b/16/umbraco-cms/extending/images/Embed-Button.png new file mode 100644 index 00000000000..c52341893ad Binary files /dev/null and b/16/umbraco-cms/extending/images/Embed-Button.png differ diff --git a/16/umbraco-cms/extending/images/Embed-YouTube.png b/16/umbraco-cms/extending/images/Embed-YouTube.png new file mode 100644 index 00000000000..ae6456c438c Binary files /dev/null and b/16/umbraco-cms/extending/images/Embed-YouTube.png differ diff --git a/16/umbraco-cms/extending/images/content-app-1.png b/16/umbraco-cms/extending/images/content-app-1.png new file mode 100644 index 00000000000..ffae7300253 Binary files /dev/null and b/16/umbraco-cms/extending/images/content-app-1.png differ diff --git a/16/umbraco-cms/extending/images/content-app-2.png b/16/umbraco-cms/extending/images/content-app-2.png new file mode 100644 index 00000000000..b3b479c6c3c Binary files /dev/null and b/16/umbraco-cms/extending/images/content-app-2.png differ diff --git a/16/umbraco-cms/extending/images/content-app-badge-v9.png b/16/umbraco-cms/extending/images/content-app-badge-v9.png new file mode 100644 index 00000000000..7afd0e57dc3 Binary files /dev/null and b/16/umbraco-cms/extending/images/content-app-badge-v9.png differ diff --git a/16/umbraco-cms/extending/images/content-app-badge.png b/16/umbraco-cms/extending/images/content-app-badge.png new file mode 100644 index 00000000000..80d3b00631e Binary files /dev/null and b/16/umbraco-cms/extending/images/content-app-badge.png differ diff --git a/16/umbraco-cms/extending/images/content-apps-location.png b/16/umbraco-cms/extending/images/content-apps-location.png new file mode 100644 index 00000000000..7192d228876 Binary files /dev/null and b/16/umbraco-cms/extending/images/content-apps-location.png differ diff --git a/16/umbraco-cms/extending/images/content-dashboards.png b/16/umbraco-cms/extending/images/content-dashboards.png new file mode 100644 index 00000000000..f2ef027c2e8 Binary files /dev/null and b/16/umbraco-cms/extending/images/content-dashboards.png differ diff --git a/16/umbraco-cms/extending/images/contentTypespecific-v8 (1) (1) (1).png b/16/umbraco-cms/extending/images/contentTypespecific-v8 (1) (1) (1).png new file mode 100644 index 00000000000..06cfabb8035 Binary files /dev/null and b/16/umbraco-cms/extending/images/contentTypespecific-v8 (1) (1) (1).png differ diff --git a/16/umbraco-cms/extending/images/contentTypespecific-v8 (1) (1) (2).png b/16/umbraco-cms/extending/images/contentTypespecific-v8 (1) (1) (2).png new file mode 100644 index 00000000000..06cfabb8035 Binary files /dev/null and b/16/umbraco-cms/extending/images/contentTypespecific-v8 (1) (1) (2).png differ diff --git a/16/umbraco-cms/extending/images/contentTypespecific-v8 (1) (1) (3).png b/16/umbraco-cms/extending/images/contentTypespecific-v8 (1) (1) (3).png new file mode 100644 index 00000000000..06cfabb8035 Binary files /dev/null and b/16/umbraco-cms/extending/images/contentTypespecific-v8 (1) (1) (3).png differ diff --git a/16/umbraco-cms/extending/images/contentTypespecific-v8 (1) (1).png b/16/umbraco-cms/extending/images/contentTypespecific-v8 (1) (1).png new file mode 100644 index 00000000000..06cfabb8035 Binary files /dev/null and b/16/umbraco-cms/extending/images/contentTypespecific-v8 (1) (1).png differ diff --git a/16/umbraco-cms/extending/images/contentTypespecific-v8 (1) (2).png b/16/umbraco-cms/extending/images/contentTypespecific-v8 (1) (2).png new file mode 100644 index 00000000000..06cfabb8035 Binary files /dev/null and b/16/umbraco-cms/extending/images/contentTypespecific-v8 (1) (2).png differ diff --git a/16/umbraco-cms/extending/images/contentTypespecific-v8 (1).png b/16/umbraco-cms/extending/images/contentTypespecific-v8 (1).png new file mode 100644 index 00000000000..06cfabb8035 Binary files /dev/null and b/16/umbraco-cms/extending/images/contentTypespecific-v8 (1).png differ diff --git a/16/umbraco-cms/extending/images/contentTypespecific-v8.png b/16/umbraco-cms/extending/images/contentTypespecific-v8.png new file mode 100644 index 00000000000..06cfabb8035 Binary files /dev/null and b/16/umbraco-cms/extending/images/contentTypespecific-v8.png differ diff --git a/16/umbraco-cms/extending/images/contentTypespecific.png b/16/umbraco-cms/extending/images/contentTypespecific.png new file mode 100644 index 00000000000..06cfabb8035 Binary files /dev/null and b/16/umbraco-cms/extending/images/contentTypespecific.png differ diff --git a/16/umbraco-cms/extending/images/db-table (1).png b/16/umbraco-cms/extending/images/db-table (1).png new file mode 100644 index 00000000000..f5bae665216 Binary files /dev/null and b/16/umbraco-cms/extending/images/db-table (1).png differ diff --git a/16/umbraco-cms/extending/images/db-table (2).png b/16/umbraco-cms/extending/images/db-table (2).png new file mode 100644 index 00000000000..f5bae665216 Binary files /dev/null and b/16/umbraco-cms/extending/images/db-table (2).png differ diff --git a/16/umbraco-cms/extending/images/db-table.png b/16/umbraco-cms/extending/images/db-table.png new file mode 100644 index 00000000000..f5bae665216 Binary files /dev/null and b/16/umbraco-cms/extending/images/db-table.png differ diff --git a/16/umbraco-cms/extending/images/developer-dashboards.png b/16/umbraco-cms/extending/images/developer-dashboards.png new file mode 100644 index 00000000000..1304c5c1805 Binary files /dev/null and b/16/umbraco-cms/extending/images/developer-dashboards.png differ diff --git a/16/umbraco-cms/extending/images/deviantart-embedded-media.png b/16/umbraco-cms/extending/images/deviantart-embedded-media.png new file mode 100644 index 00000000000..ca8a8902ac1 Binary files /dev/null and b/16/umbraco-cms/extending/images/deviantart-embedded-media.png differ diff --git a/16/umbraco-cms/extending/images/element-v8.png b/16/umbraco-cms/extending/images/element-v8.png new file mode 100644 index 00000000000..878ce70c25b Binary files /dev/null and b/16/umbraco-cms/extending/images/element-v8.png differ diff --git a/16/umbraco-cms/extending/images/element.png b/16/umbraco-cms/extending/images/element.png new file mode 100644 index 00000000000..75bdeea01cc Binary files /dev/null and b/16/umbraco-cms/extending/images/element.png differ diff --git a/16/umbraco-cms/extending/images/getting-started-dashboard.jpg b/16/umbraco-cms/extending/images/getting-started-dashboard.jpg new file mode 100644 index 00000000000..3af5f1808e3 Binary files /dev/null and b/16/umbraco-cms/extending/images/getting-started-dashboard.jpg differ diff --git a/16/umbraco-cms/extending/images/image-position-v8.png b/16/umbraco-cms/extending/images/image-position-v8.png new file mode 100644 index 00000000000..7a9995081f5 Binary files /dev/null and b/16/umbraco-cms/extending/images/image-position-v8.png differ diff --git a/16/umbraco-cms/extending/images/image-position.png b/16/umbraco-cms/extending/images/image-position.png new file mode 100644 index 00000000000..f6b86300305 Binary files /dev/null and b/16/umbraco-cms/extending/images/image-position.png differ diff --git a/16/umbraco-cms/extending/images/introstep.png b/16/umbraco-cms/extending/images/introstep.png new file mode 100644 index 00000000000..ca631766a82 Binary files /dev/null and b/16/umbraco-cms/extending/images/introstep.png differ diff --git a/16/umbraco-cms/extending/images/step-event-element-v8.png b/16/umbraco-cms/extending/images/step-event-element-v8.png new file mode 100644 index 00000000000..185991d1530 Binary files /dev/null and b/16/umbraco-cms/extending/images/step-event-element-v8.png differ diff --git a/16/umbraco-cms/extending/images/step-event-element.png b/16/umbraco-cms/extending/images/step-event-element.png new file mode 100644 index 00000000000..fb12fee4e39 Binary files /dev/null and b/16/umbraco-cms/extending/images/step-event-element.png differ diff --git a/16/umbraco-cms/extending/images/stepcontent-v8.png b/16/umbraco-cms/extending/images/stepcontent-v8.png new file mode 100644 index 00000000000..cf0776b57d0 Binary files /dev/null and b/16/umbraco-cms/extending/images/stepcontent-v8.png differ diff --git a/16/umbraco-cms/extending/images/stepcontent.png b/16/umbraco-cms/extending/images/stepcontent.png new file mode 100644 index 00000000000..adbe84d994a Binary files /dev/null and b/16/umbraco-cms/extending/images/stepcontent.png differ diff --git a/16/umbraco-cms/extending/images/steptitle-v8.png b/16/umbraco-cms/extending/images/steptitle-v8.png new file mode 100644 index 00000000000..522e3008c1a Binary files /dev/null and b/16/umbraco-cms/extending/images/steptitle-v8.png differ diff --git a/16/umbraco-cms/extending/images/steptitle.png b/16/umbraco-cms/extending/images/steptitle.png new file mode 100644 index 00000000000..c3ea636c953 Binary files /dev/null and b/16/umbraco-cms/extending/images/steptitle.png differ diff --git a/16/umbraco-cms/extending/images/the-dashboard-package.png b/16/umbraco-cms/extending/images/the-dashboard-package.png new file mode 100644 index 00000000000..e565eea3cf4 Binary files /dev/null and b/16/umbraco-cms/extending/images/the-dashboard-package.png differ diff --git a/16/umbraco-cms/extending/images/tourallowdisable-v8.png b/16/umbraco-cms/extending/images/tourallowdisable-v8.png new file mode 100644 index 00000000000..ba501f4fd87 Binary files /dev/null and b/16/umbraco-cms/extending/images/tourallowdisable-v8.png differ diff --git a/16/umbraco-cms/extending/images/tourallowdisable.png b/16/umbraco-cms/extending/images/tourallowdisable.png new file mode 100644 index 00000000000..7d24eae70e6 Binary files /dev/null and b/16/umbraco-cms/extending/images/tourallowdisable.png differ diff --git a/16/umbraco-cms/extending/images/tourgroup-v8.png b/16/umbraco-cms/extending/images/tourgroup-v8.png new file mode 100644 index 00000000000..b2a4a7efb38 Binary files /dev/null and b/16/umbraco-cms/extending/images/tourgroup-v8.png differ diff --git a/16/umbraco-cms/extending/images/tourgroup.png b/16/umbraco-cms/extending/images/tourgroup.png new file mode 100644 index 00000000000..2509c199dba Binary files /dev/null and b/16/umbraco-cms/extending/images/tourgroup.png differ diff --git a/16/umbraco-cms/extending/images/tourname-v8.png b/16/umbraco-cms/extending/images/tourname-v8.png new file mode 100644 index 00000000000..e247687d244 Binary files /dev/null and b/16/umbraco-cms/extending/images/tourname-v8.png differ diff --git a/16/umbraco-cms/extending/images/tourname.png b/16/umbraco-cms/extending/images/tourname.png new file mode 100644 index 00000000000..91466067eb7 Binary files /dev/null and b/16/umbraco-cms/extending/images/tourname.png differ diff --git a/16/umbraco-cms/extending/images/welcome-dashboard.jpg b/16/umbraco-cms/extending/images/welcome-dashboard.jpg new file mode 100644 index 00000000000..60c31089586 Binary files /dev/null and b/16/umbraco-cms/extending/images/welcome-dashboard.jpg differ diff --git a/16/umbraco-cms/extending/key-vault.md b/16/umbraco-cms/extending/key-vault.md new file mode 100644 index 00000000000..d0e00cc0780 --- /dev/null +++ b/16/umbraco-cms/extending/key-vault.md @@ -0,0 +1,260 @@ +--- +description: A guide for configuring Azure Key Vault +--- + +# Configuring Azure Key Vault + +From a security perspective, storing your application secrets in Azure Key Vault is always a good solution. This could be a connection string or other keys. + +This article tells you how to configure your application so it is ready to use a Key Vault. + +Depending on your hosting situation there are a few approaches to incorporating Azure Key Vault into your application. + +1. [Install Key Vault via Nuget (for most Hosting scenarios)](key-vault.md#install-key-vault-via-nuget) +2. [Use Key Vault references for Azure App Service (For Azure Web App Hosting)](key-vault.md#use-key-vault-references-for-azure-app-service) + +## Install Key Vault via Nuget + +Before you begin, you need to install the `Azure.Extensions.AspNetCore.Configuration.Secrets` and the `Azure.Identity` NuGet packages. There are two approaches to installing the packages: + +1. Use your favorite Integrated Development Environment (IDE) and open up the NuGet Package Manager to search and install the packages +2. Use the command line to install the package + +### Installing through command line + +Navigate to your project folder, which is the folder that contains your `.csproj` file. Now use the following `dotnet add package` command to install the packages: + +``` +dotnet add package Azure.Extensions.AspNetCore.Configuration.Secrets +dotnet add package Azure.Identity +``` + +### Configuration + +{% hint style="info" %} +You can find the database connection string under the `Umbraco:CMS:ConnectionStrings` section in the `appsettings.json` file. For more information, see the [Connection strings settings](../reference/configuration/connectionstringssettings.md) article. +{% endhint %} + +The next step is to add the Azure Key Vault endpoint to the `appsettings.json` file (or create as an Environment Variable). You can add this endpoint in the root or anywhere in the `appsettings.json` as long as it is resolved in the `ConfigureAppConfiguration` method. + +```json +{ + "AzureKeyVaultEndpoint": "https://{your-key-vault-name}.vault.azure.net", +} +``` + +After adding the endpoint in the appsettings, it's time to add configuration so that the KeyVault is used. One way to achieve this is to write an extension method for the `WebApplicationBuilder`: + +```csharp +using System; +using Azure.Identity; +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.Configuration; +using Umbraco.Cms.Core.DependencyInjection; +using Umbraco.Extensions; + +namespace My.Website; + +public static class WebApplicationBuilderExtensions +{ + public static WebApplicationBuilder ConfigureKeyVault(this WebApplicationBuilder builder) + { + var keyVaultEndpoint = builder.Configuration["AzureKeyVaultEndpoint"]; + if (!string.IsNullOrWhiteSpace(keyVaultEndpoint) && Uri.TryCreate(keyVaultEndpoint, UriKind.Absolute, out var validUri)) + { + builder.Configuration.AddAzureKeyVault(validUri, new DefaultAzureCredential()); + } + + return builder; + } +} +``` + +After creating the extension method, it's possible to call it from the `Program.cs` class, like so: + +```csharp +using Microsoft.AspNetCore.Builder; +using My.Project; +using Umbraco.Cms.Core.DependencyInjection; +using Umbraco.Extensions; + +WebApplicationBuilder builder = WebApplication.CreateBuilder(args); + +builder.ConfigureKeyVault(); + +builder.CreateUmbracoBuilder() + .AddBackOffice() + .AddWebsite() + .AddDeliveryApi() + .AddComposers() + .Build(); + +WebApplication app = builder.Build(); + +await app.BootUmbracoAsync(); + +app.UseUmbraco() + .WithMiddleware(u => + { + u.UseBackOffice(); + u.UseWebsite(); + }) + .WithEndpoints(u => + { + u.UseBackOfficeEndpoints(); + u.UseWebsiteEndpoints(); + }); + +await app.RunAsync(); +``` + +### Authentication + +There are different ways to access the Azure Key Vault. It is important that the user you are logging in with has access to the Key Vault. You can assign roles using the Azure Portal. + +1. Navigate to your Key Vault. +2. Select Access Control. +3. Select Add -> Add role assignment. +4. Select the preferred role. +5. Search for the user. +6. Click review + assign + +## Use Key Vault references for Azure App Service + +Azure Web Apps offers the ability to directly reference Key Vault secrets as App Settings. The benefit of this is you can securely store your secrets in Key Vault without any code changes required in your application. + +### Create a System Assigned Managed Identity + +To begin we first need to create a **Managed Identity** for the Azure Web App. This enables us to grant granular permissions to an identity representing the Web App. + +Head over to your Azure Web App and find **Identity** under **Settings**: + +![image](https://user-images.githubusercontent.com/11179749/196052374-cebcfbc3-848f-4866-8e0f-70a57e776f60.png) + +Under **System assigned** change the Status from Off to **On**. + +![image](https://user-images.githubusercontent.com/11179749/196052406-2205c1bc-504a-41be-86bf-81b1cabbc17f.png) + +A GUID will then be generated called **Object (principal) ID**. Take note of this ID as we will need it further on. + +### Update your Key Vault Access Policy + +{% hint style="info" %} +Alternatively, you can use Role-Based Access Control on your Azure Key Vault. + +Learn more about the difference between the two approaches and how to migrate between them on the [Azure Documentation platform](https://learn.microsoft.com/en-us/azure/key-vault/general/rbac-access-policy). +{% endhint %} + +It is assumed you already have a Key Vault set up with a few Umbraco secrets inside. In your Key Vault head to **Access Policies**. + +![image](https://user-images.githubusercontent.com/11179749/196052540-e1368016-ad7a-4b69-b05c-2875a4f11998.png) + +At the top select **+ Create**. We are now going to add the **System Managed Identity** for the Web App to Key Vault. + +![image](https://user-images.githubusercontent.com/11179749/196052612-e3b2041c-785f-46f5-b9b5-d8ad33b893ac.png) + +You will now be presented with different permissions to set for your Web App. You only need **Get** and **List** for **Secret Permissions** only. Click **Next** to continue: + +![image](https://user-images.githubusercontent.com/11179749/196052668-124d1496-4486-4098-9198-eff809876c80.png) + +Enter the GUID you took note of earlier, into the **Search Box**. You will see your Web App listed. + +![image](https://user-images.githubusercontent.com/11179749/196052706-15431bf4-80ea-4bb7-b40e-ebda45264fb7.png) + +Click your Web App to Select and click Next and then Create: + +![image](https://user-images.githubusercontent.com/11179749/196052849-970a97c5-e945-415a-9469-a67f485424ea.png) + +If you visit the **Access Policies** section again you should now see your web app in the list and its permissions: + +![image](https://user-images.githubusercontent.com/11179749/196052924-0d0559c0-a414-4bbd-ab91-94fc25dc720f.png) + +### Link our Key Vault Secret to an Azure Web App + +In your Azure Web App head to **Configuration** under **Settings**. + +![image](https://user-images.githubusercontent.com/11179749/196053006-3a95fc5f-1038-4228-9ae4-467050ea5759.png) + +Here we can add **App Settings** and **Connection Strings** to the environment. + +1. Let us start off with the **Umbraco Database Connection String**. + +Under Connection Strings, select **Advanced Edit**. + +![image](https://user-images.githubusercontent.com/11179749/196053130-8fb6c2b9-61c7-4c02-a419-8570174c6646.png) + +Once you click on "**Advanced Edit"** a new window will open up. There you will need to paste in the following JSON Object inside the square brackets. Ensure you update `{keyvault-name}`, `{secret-name}` and `{version-id}`. + +```json +{ + "name": "umbracoDbDSN", + "value": "@Microsoft.KeyVault(SecretUri=https://{keyvault-name}.vault.azure.net/secrets/{secret-name}/{version-id}/)", + "type": "Custom", + "slotSetting": false +} +``` + +{% hint style="info" %} +You can obtain the Secret Uri by visiting the specific version of your secret and copying the Url: +{% endhint %} + +![image](https://user-images.githubusercontent.com/11179749/196054001-cc215c04-d29c-435a-ae7b-6e8efb7f3faa.png) + +The ID is optional but recommended as it enables you to control which version of the secret is used at your discretion. Leave it out if you always want the Web App to pull the latest version of the secret. + +Wait a moment and refresh the screen. You should see a Green tick. If you do not have a Green tick you need to review your Access Policies in the previous step. + +![image](https://user-images.githubusercontent.com/11179749/196053419-f53feba2-b8ed-4b98-99f0-ee68f58ac8e4.png) + +2. We will perform the same approach for our **App Settings**. We will be updating the following App Settings for Azure Blob Storage. + +```json +"Umbraco": { + "Storage": { + "AzureBlob": { + "Media": { + "ConnectionString": "", + "ContainerName": "" + } + } + } +``` + +Due to the secrets being nested we need to use double underscore `__` to correctly reference the value on our Web App. + +On the Web App select **Advanced Edit** for Application Settings: + +
+ +When clicking on "Advanced Edit", a new window will open up. There you will need to paste in the following JSON Objects inside the square brackets. Ensure you update `{keyvault-name}`, `{secret-name}` and `{version-id}`. + +```json +{ + "name": "Umbraco__Storage__AzureBlob__Media__ConnectionString", + "value": "@Microsoft.KeyVault(SecretUri=https://{keyvault-name}.vault.azure.net/secrets/{secret-name}/{version-id}/)", + "slotSetting": false +}, +{ + "name": "Umbraco__Storage__AzureBlob__Media__ContainerName", + "value": "@Microsoft.KeyVault(SecretUri=https://{keyvault-name}.vault.azure.net/secrets/{secret-name}/{version-id}/)", + "slotSetting": false +} +``` + +The ID is optional but recommended as it enables you to control which version of the secret is used at your discretion. Leave it out if you always want the Web App to pull the latest version of the secret. + +Wait a moment and refresh the screen. You should see Green ticks for both values. If you do not have a Green tick you need to review your Access Policies in the previous step. + +
+ +### Local Development + +1. [Sign in to Visual Studio using the credentials that can access the Key Vault.](https://docs.microsoft.com/en-us/visualstudio/ide/signing-in-to-visual-studio) +2. [Use Azure CLI to store your preferred account into the credential cache.](https://docs.microsoft.com/en-us/cli/azure/authenticate-azure-cli) +3. [An example of extending and referencing secrets in `appsettings.json` in your local development environment.](https://gist.github.com/tgreensill/26659111871fdc54d0ac20cc21e602e1) + +### Staging/Production + +1. [Managed identities for Azure resources](https://docs.microsoft.com/en-us/aspnet/core/security/key-vault-configuration?view=aspnetcore-6.0#use-managed-identities-for-azure-resources) +2. [X.509 certificate for non-Azure-hosted apps](https://docs.microsoft.com/en-us/aspnet/core/security/key-vault-configuration?view=aspnetcore-6.0#use-application-id-and-x509-certificate-for-non-azure-hosted-apps) +3. [Use Key Vault references for App Service and Azure Functions](https://learn.microsoft.com/en-us/azure/app-service/app-service-key-vault-references) diff --git a/16/umbraco-cms/extending/language-files/README.md b/16/umbraco-cms/extending/language-files/README.md new file mode 100644 index 00000000000..9770bcec6fa --- /dev/null +++ b/16/umbraco-cms/extending/language-files/README.md @@ -0,0 +1,58 @@ +--- +description: >- + This article overviews how an Umbraco CMS website uses and manages + localization with language files. +--- + +# Language Files & Localization + +## Language Files & Localization + +Language files are used to localise the Umbraco backoffice, so Users can use Umbraco in their native language. This is particularly important for content editors who do not speak English. + +With language files, you can also: + +* Override existing (core) localizations. +* Define localization for your own package. + +### [UI Localization](../../customizing/foundation/localization.md) + +Defines how to use the UI Umbraco Localization. This is the primary source of localization for the backoffice. + +### [.NET Localization](net-localization.md) + +Defines how to use the .NET Core Umbraco Localization. This is only relevant for localization that happens server-side - for example, for sending emails. + +{% hint style="info" %} +You can use localization files for Document and Media Types as well. You can find more information about this in the [Document Type Localization](../../fundamentals/data/defining-content/document-type-localization.md) article. +{% endhint %} + +## Supported Languages + +Current [languages](https://github.com/umbraco/Umbraco-CMS/tree/contrib/src/Umbraco.Core/EmbeddedResources/Lang) with their ISO codes that are included in new Umbraco installations are: + +* `bs-BS` - Bosnian (Bosnia and Herzegovina) +* `cs-CZ` - Czech (Czech Republic) +* `cy-GB` - Welsh (United Kingdom) +* `da-DK` - Danish (Denmark) +* `de-DE` - German (Germany) +* `en` - **English (United Kingdom)** (fallback language) +* `en-US` - English (United States) +* `es-ES` - Spanish (Spain) +* `fr-FR` - French (France) +* `he-IL` - Hebrew (Israel) +* `hr-HR` - Croatian (Croatia) +* `it-IT` - Italian (Italy) +* `ja-JP` - Japanese (Japan) +* `ko-KR` - Korean (Korea) +* `nb-NO` - Norwegian Bokmål (Norway) +* `nl-NL` - Dutch (Netherlands) +* `pl-PL` - Polish (Poland) +* `pt-BR` - Portuguese (Brazil) +* `ro-RO` - Romanian (Romania) +* `ru-RU` - Russian (Russia) +* `sv-SE` - Swedish (Sweden) +* `tr-TR` - Turkish (Turkey) +* `ua-UA` - Ukrainian (Ukraine) +* `zh-CN` - Chinese (China) +* `zh-TW` - Chinese (Taiwan) diff --git a/16/umbraco-cms/extending/language-files/net-localization.md b/16/umbraco-cms/extending/language-files/net-localization.md new file mode 100644 index 00000000000..d84a86072ac --- /dev/null +++ b/16/umbraco-cms/extending/language-files/net-localization.md @@ -0,0 +1,116 @@ +--- +description: NET Umbraco Core Localization files. +--- + +# .NET Localization + +In this article, you will find information about the Core Localization files. You can also find information about where to find and use them, and how to keep them up-to-date. + +## Use cases + +.NET localization has limited use cases in Umbraco, as all backoffice localization is performed with [UI Localization](../../customizing/foundation/localization.md). + +In other words, .NET localization is only applied server-side with no accompanying UI - for example: + +* Sending emails. +* User login error handling. +* Health checks. + +## Where to find the core localization files + +The core Umbraco localization files are found at the following location within the [Umbraco source](https://github.com/umbraco/Umbraco-CMS/tree/contrib/src/Umbraco.Core/EmbeddedResources/Lang): + +```xml +Umbraco-CMS/src/Umbraco.Core/EmbeddedResources/Lang/ +``` + +These localization files are shipped with Umbraco and should not be modified. + +### User localization files + +If you want to override Umbraco Core .NET localization, create new files in the following location and format: + +```xml +/config/lang/{language}.user.xml +``` + +{% hint style="info" %} +The `/config/lang/` folders do not exist on a clean installation of the CMS. You will need to create them at the root of your project. In an Umbraco Cloud project this will need to be in the `src` project. +{% endhint %} + +In order for these files to deploy when you do a `dotnet publish`, you need to add the following to your `.csproj` file: + +```xml + + + +``` + +## Using the localizations + +`ILocalizedTextService` is used to localize strings, and is available through dependency injection. You can use the `Localize()` method available in the namespace `Umbraco.Extensions` to localize the string by `area` and `key`: + +```csharp +using Umbraco.Cms.Core.Services; + +namespace UmbracoDocs.Samples; + +public class LocalizationSample +{ + private readonly ILocalizedTextService _localizedTextService; + + public LocalizationSample(ILocalizedTextService localizedTextService) + => _localizedTextService = localizedTextService; + + public string LocalizeMyText(string area, string key) + => _localizedTextService.Localize(area, key); +} +``` + +## Help keep the language files up to date + +As Umbraco is a continually evolving product it is inevitable that new text is added regularly to the English language version of these files. This may mean that some of the above languages are no longer up to date. + +If a translation is missing, the key "**alias**" used will be shown within the user interface, as an example: + +```xml +[assignDomain] +``` + +The language files are XML files with a straight-forward layout as seen below. + +```xml + + + + The Umbraco community + https://community.umbraco.com + + + Culture and Hostnames + Audit Trail + ... + + ... + +``` + +In the above example of a missing translation for "**assignDomain**", locate this string in the en.xml file. Then copy the whole "**Key**" element into the relevant language file. Afterwards you can translate the text, as an example here is the Spanish version of the above snippet: + +```xml + + + + The Umbraco community + https://community.umbraco.com + + + Administrar hostnames + Auditoría + ... + + ... + +``` + +If you modify core language files or introduce a new language, you can assist the community by sharing your updates. This can be done by [submitting a pull request](https://github.com/umbraco/Umbraco-CMS/blob/contrib/.github/CONTRIBUTING.md) so that your changes are merged into the core. diff --git a/16/umbraco-cms/extending/packages/README.md b/16/umbraco-cms/extending/packages/README.md new file mode 100644 index 00000000000..bb9c6ac7fff --- /dev/null +++ b/16/umbraco-cms/extending/packages/README.md @@ -0,0 +1,72 @@ +--- +description: "A package extends the functionality of Umbraco to provide additional functionality to editors, developers, site visitors, and all other types of users of Umbraco." +--- + +# Packages + +## What is a Package? + +A package extends Umbraco to provide additional functionality to editors, developers, site visitors, and all other types of users of Umbraco. It can impact one or more of these groups of people depending on the type of package. + +An Umbraco Package can be many things, but is generally characterized by: + +- Adding or extending functionality in the Umbraco CMS +- Empowering people to do more and/or do things more efficiently +- Engaging community members in collaboration and sharing +- Solving real life problems +- Inspiring people on what Umbraco can be made capable of + +### Categories of packages + +Packages provide a wide variety of functionality, and can often span multiple categories. In general, though, the functionality they provide fall into these main groups: + +- [Schema Extensions](#schema-extensions) +- [Management Extensions](#management-extensions) +- [Starter Kits](#starter-kits) +- [Integration Extensions](#integration-extensions) + +#### Schema Extensions + +A package that can be categorized as a Schema Extension will extend the default Umbraco Schema. Schema in this sense refers to things like Data Types, Property Editors, Document Types and Media Types. By extending Umbraco with packages such as [Our.Umbraco.GMaps](https://marketplace.umbraco.com/package/our.umbraco.gmaps) editors are given greater capabilities when they are populating their content pages. + +#### Management Extensions + +A Management Extension package helps you manage your site, and provides information to the users. Management extensions typically contain custom sections or dashboards to facilitate site management. [Diplo God Mode](https://marketplace.umbraco.com/package/diplo.godmode) is an example of a comprehensive management extension package with additional tools and information. + +#### Starter Kits + +Starter kits are, as the name suggests, a package that helps you set up a starter version of whatever you want to build. Most starter kit packages are for starting a website, and include schema like Document Types and Templates as well as content nodes and media. There are also some specialized starter kits, for example for creating a blog. Umbraco HQ has released their [own starter kit](https://www.nuget.org/packages/Umbraco.TheStarterKit), that creates a small site with the most commonly used features. + +#### Workspace Views + +Workspace Views are almost like dashboards for content nodes that are intended to display node specific information. A good example of this is the [Preflight](https://marketplace.umbraco.com/package/preflight.umbraco) content app. It shows you readability scores for your written content, directly on each content node. + +#### Integration extensions + +This type of package can be a lot of things, and can include a number of the other package types. They are generally integrating a larger system into Umbraco. A good example could be an e-commerce package such as [Umbraco Commerce](https://docs.umbraco.com/umbraco-commerce), that includes an entire webshop module for Umbraco. + +## [Creating a Package](creating-a-package.md) + +This short tutorial will teach you how to create a package in the Umbraco backoffice. It will also give a quick overview of what a generated package will contain. + +## [Language file for packages](language-files-for-packages.md) + +Package authors who would like their UI to be multi-lingual can include their own set of language files as part of their package distribution. + +## [Listing a Package on the Umbraco Marketplace](listing-on-marketplace.md) + +Once you've created a package make it available on the Umbraco Marketplace to share it with the community. + +## [Packages on Umbraco Cloud](packages-on-umbraco-cloud.md) + +Things you should know if you are developing for Umbraco Cloud. + +## [Maintaining Packages](maintaining-packages.md) + +Some guidance on how to maintain your package after release. + +## [An Example Package Repository](example-package-repository.md) + +There are many ways to build and deploy your package to NuGet. You will likely have your own approach for organizing a solution and preferred tools for build and deployment. + +If you are looking for inspiration to follow form some tried and tested packages, read more here. diff --git a/16/umbraco-cms/extending/packages/accessibility.md b/16/umbraco-cms/extending/packages/accessibility.md new file mode 100644 index 00000000000..733e096667f --- /dev/null +++ b/16/umbraco-cms/extending/packages/accessibility.md @@ -0,0 +1,22 @@ +# Create accessible Umbraco packages + +Creating accessible packages extends on accessibility in an [Umbraco context](https://www.skrift.io/issues/accessibility-in-an-umbraco-context/). + +The Umbraco UI components have been built to be accessible and have accessibility tests built within them. Building the user interface (UI) using these [Umbraco UI components](https://uui.umbraco.com/) ensures that the package is as accessible as the Umbraco backoffice. + +In addition, any fixes and updates to the UI components will be pushed through to the packages when you rebuild them with the updates. + +## Testing + +Accessibility testing is more a specialist skillset than it is automated testing. The purpose of this document is to outline what can be done to help build accessible packages. It is not a complete list of accessibility tests that can be performed. + +- Build the components using the [Umbraco UI components](https://uui.umbraco.com/) as these have accessibility tests built within them. +- Use the keyboard to tab through the elements on the page checking: + - Does the element tabbed to have a **focus state**? + - Does the **tab order** make sense? + - More on focus, tab orders, other common interactions and techniques for keyboard testing can be found at [WebAIM: Keyboard Accessibility](https://webaim.org/techniques/keyboard/) +- Check the UI with a screen reader. + - [Non Visual Desktop Access (NVDA) is a free Windows screen reader](https://www.nvaccess.org/download/) and some guidelines on screen reader testing are available from [WebAIM: Web Accessibility In Mind](https://webaim.org/articles/screenreader_testing/) +- Install an accessibility testing tool as a plugin into your browser to run automated tests: + - Tools like [axe DevTools](https://chrome.google.com/webstore/detail/axe-devtools-web-accessib/lhdoppojpmngadmnindnejefpokejbdd) are built to reduce the number of false positives in a test. +- If the UI does not follow the Umbraco Style, then check the contrast with a tool like the [Web Content Accessibility Guidelines (WCAG) Contrast Checker](https://chrome.google.com/webstore/detail/wcag-color-contrast-check/plnahcmalebffmaghcpcmpaciebdhgdf). This will help ensure contrast. diff --git a/16/umbraco-cms/extending/packages/creating-a-package.md b/16/umbraco-cms/extending/packages/creating-a-package.md new file mode 100644 index 00000000000..c11745c70ac --- /dev/null +++ b/16/umbraco-cms/extending/packages/creating-a-package.md @@ -0,0 +1,336 @@ +--- +description: Tutorial to create a package in Umbraco +--- + +# Creating a Package + +This tutorial demonstrates how to create a package in Umbraco. The process described is based on creating a package from the dashboard in the [Creating a Custom Dashboard Tutorial](../../tutorials/creating-a-custom-dashboard/). The same approach can be applied to other packages as well. + +## Creating a Package Schema in the Backoffice + +To begin creating a package, start by setting up a package schema in the Umbraco backoffice: + +1. Navigate to the `Packages` section. +2. Select `Created` in the top-right corner of the screen. +3. Click the `Create package` button. + +![Creating a package schema in the Backoffice](images/create-package.png) + +On the `Create package` page, there are different fields that allow you to define the contents of the package based on backoffice items. + +4. Enter the Package Name at the top. For this tutorial, name the package `Custom Welcome Dashboard` matching the name used in the [Creating a Custom Dashboard Tutorial](../../tutorials/creating-a-custom-dashboard/). +5. Fill in the required fields. For more information, see the [Package Content Section](#package-content-section). +6. Click `Create` to generate the package schema. +7. Click `Download` to download the package and inspect its contents. + +### Package Content section + +These fields determine which backoffice items will be included in the package. For this example, the following settings are used: + +| Property | Value | Note | +| -------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| Content | _Empty_ | Here, you can include content. For example, if you want to create a starter kit. Not relevant for this package though. | +| Media | _Empty_ | Here, you can include media. For example, if you want to add media to the starter kit. Not relevant for this package though. | +| Document Types | _Empty_ | Similar to the Content picker above. If you include content, you will also need to pick all its dependencies in this and the next steps for them to be packaged together. | +| Media Types | _Empty_ | Similar to the Media picker above. If you include media, you will also need to pick all its dependencies in this and the next steps for them to be packaged together. | +| Languages | _Empty_ | See `Document Types` above. All text is hardcoded or within the lang folder in this package, so this is not needed. | +| Dictionary | _Empty_ | See `Document Types` above | +| Data Types | _Empty_ | See `Document Types` above | +| Templates | _Empty_ | See `Document Types` above | +| Stylesheets | _Empty_ | These will come from the **wwwroot/css** folder. If you have stylesheets you want to include from other locations (_like App_Plugins folder_) you can do so at a later step. | +| Scripts | _Empty_ | These will come from the **wwwroot/scripts** folder. If you have scripts you want to include from other locations (_like App_Plugins folder_) you can do so at a later step. | +| Partial Views | _Empty_ | See `Document Types` above | + +## Inspecting the Package ZIP + +If the package includes backoffice-specific items, the downloaded ZIP will contain the `package.xml` along with a folder for any media items included. The contents of the ZIP package might look like this: + +![Contents of the ZIP package](images/zip-package-contents.png) + +The `package.xml` file contains the metadata for the package, while additional files (if any) are referenced within the XML. + +Below is an example of the `package.xml` file: + +```xml + + + + + Custom Welcome Dashboard + + + + + + + + + + + + + +``` + +This XML file contains package metadata, and references where Umbraco should place the files upon installation. + +Since this example package does not include backoffice items, only the package name appears, with other tags left empty. + +## Creating a NuGet package + +NuGet packages are the preferred method for distributing Umbraco extensions. NuGet enables better practices for source control and deployment. This section outlines how to create a NuGet package for the custom dashboard that extends Umbraco’s functionality. + +NuGet is the standard package manager for .NET. More details on NuGet can be found in the [Microsoft Documentation](https://docs.microsoft.com/en-us/nuget/what-is-nuget). + +### Generate an Empty Package Using a Template + +1. Install the latest [.NET SDK](https://dotnet.microsoft.com/download). +2. Run `dotnet new install Umbraco.Templates` to install the project templates. +3. Run `dotnet new umbraco-extension -n CustomWelcomeDashboard` to create a new package project. + +{% hint style="info" %} +If the post-installation script fails due to PowerShell restrictions, manually run `npm install` and `npm run build` in the `Client` folder. +{% endhint %} + +This command will generate the following files: + +![Content of an empty package](images/empty-package-from-template-v15.png) + +Apart from the project file, the generated structure includes key folders and files that are essential for building an Umbraco extension. + +- **Client** - where you can place any front-end assets, such as JavaScript, CSS, and Angular views, that will be used in the backoffice. +- **Composers** - intended for C# composer classes, which are used to register dependencies and configure services in Umbraco. +- **Controllers** - where you can add API controllers if your package requires custom endpoints. +- **Constants.cs** - serves as a placeholder for any global constants your package may need. + +Unlike previous versions, the `umbraco-extension` template does not generate a `package.manifest` file or an `App_Plugins` folder by default. If your package includes client-side assets for the backoffice, you will need to manually create an `App_Plugins/{YourPackageName}` folder. + +Additionally, the `.csproj` file is configured to support NuGet packaging, allowing you to distribute your extension. If you plan to include custom C# logic, the files you place in the root folder will be compiled into the package DLL. + +### Transfer Files + +Since the `umbraco-extension` template does not generate an `App_Plugins` folder by default, you will need to manually create it. + +1. Create an `App_Plugins` folder in the downloaded package folder. +2. Go to the `welcome-dashboard` folder created in the [Creating a Custom Dashboard Tutorial](../../tutorials/creating-a-custom-dashboard/README.md#setting-up-a-package). +3. Transfer or copy the `welcome-dashboard` folder in the `App_Plugins` folder. + +![App_Plugins with dashboard files](images/app-plugins-content.png) + +### Specify Package Properties + +You can specify package metadata directly in the `csproj` file. Here, is an example of some package properties: + +```xml + + + . . . + CustomWelcomeDashboard + Custom welcome dashboard for Umbraco. + umbraco plugin package + 1.0.0 + Your Name + https://umbraco.com + MIT + + . . . + +``` + +Alternatively, right-click the `csproj` file in Visual Studio > **Properties** > **Package** and add the required information: + +![Adding Package properties in Visual Studio](images/Package-properties-Visual-Studio.png) + +The properties that can be specified include: + +| Property | Value | Note | +| ------------------------ | ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Version | 1.0.0 | This is automatically set to 1.0.0 but can be changed as appropriate. | +| Authors | Your name | Here you get to take credit for your awesome work! | +| PackageProjectUrl | https://umbraco.com | This URL will be shown as the package's URL when others install it. It will likely be a GitHub repository, or similar. | +| PackageLicenseExpression | MIT | The license is set to MIT. Please consider how you want your package licensed. If in doubt when deciding an open-source license there are [good resources available](https://choosealicense.com/licenses/). | + +### Pack the Package + +To create the actual NuGet package, use the `dotnet pack` command. You can either output the package to the default `bin` folder or specify a custom location. + +#### Default Output + +Run the command in the package directory to generate the package in the `bin` folder: + +```sh +dotnet pack +``` + +![Package output in `bin` folder](images/package-default-location.png) + +#### Custom Output Location + +To specify a different output location, use the following command: + +```sh +dotnet pack --output MyNugetPackages +``` + +![Package output in `MyNugetPackages` folder](images/package-custom-folder.png) + +It will pack the project in the current directory and place the resulting package into the `MyNugetPackages` folder. + +### Publish the Package + +To share the package with others, publish it to a public NuGet repository, such as [https://nuget.org](https://nuget.org). + +The official [NuGet Documentation](https://docs.microsoft.com/en-us/nuget/nuget-org/publish-a-package) provides a detailed guide on how to publish a package to NuGet.org. + +To release packages to only a limited audience, see the [Hosting your own NuGet feeds](https://docs.microsoft.com/en-us/nuget/hosting-packages/overview) documentation. + +For Umbraco-specific packages, refer to the [Listing Your Package](https://docs.umbraco.com/umbraco-dxp/marketplace/listing-your-package) guide to feature your package on the Umbraco Marketplace. + +## Installing a NuGet Package + +To install the NuGet package, you can use Visual Studio, Rider, or the CLI. + +In the CLI, create a new Umbraco project and add the package reference: + +```cs +dotnet new umbraco -n CustomWelcomeDashboardProject +cd CustomWelcomeDashboardProject +dotnet add package CustomWelcomeDashboard.1.0.0 +dotnet run +``` + +You can check that the NuGet package was referenced in your solution and that the **App_Plugins** assets were restored successfully. The custom dashboard should now be available in the Umbraco backoffice. + +For testing the package locally without publishing, use the `-p` flag to create a project that depends on the package. So when you build the new project, it will copy the **App_Plugins** folder from the package project into the test project. + +```cs +dotnet new umbraco -n CustomWelcomeDashboardProject -p CustomWelcomeDashboard +``` + +Go to the `CustomWelcomeDashboardProject` directory, build your Umbraco website using the `dotnet build` command, and then run the application. + +### Package Migration + +Umbraco supports automatic and custom package migrations to handle content updates when a package is installed. + +#### Automatic Package Migration + +For schema and content packages, inherit from the `AutomaticPackageMigrationPlan` and specify the package name that is displayed under the packages _Installed_ tab in the backoffice. You will also need to embed the schema file in the same namespace. + +```csharp +using Umbraco.Cms.Infrastructure.Packaging; + +namespace CustomWelcomeDashboardProject.Migrations; + +public class PackageMigrationPlan : AutomaticPackageMigrationPlan +{ + public PackageMigrationPlan() : base("Custom Welcome Dashboard") + { + } +} +``` + +![Automatic package migration](../../../../10/umbraco-cms/extending/packages/images/embeded-resource.png) + +{% hint style="info" %} +Whenever the embedded package.xml file changes, the automatic package migration plan is executed again. This is due to the fact that the migration state is based on the file hash. Existing schema or content will not be overwritten in this process. +{% endhint %} + +#### Custom Package Migration + +Instead of creating an automatic package migration plan, we will inherit from the `PackageMigrationPlan` and again specify the name of the package in the base constructor. Further on, we will define the plan using a unique GUID - in the example below we have a single migration called `MyCustomMigration`. + +```csharp +using Umbraco.Cms.Core.Packaging; + +namespace CustomWelcomeDashboardProject.Migrations; + +public class CustomPackageMigrationPlan : PackageMigrationPlan +{ + public CustomPackageMigrationPlan() : base("Custom Welcome Dashboard") + { + } + + protected override void DefinePlan() + { + To(new Guid("4FD681BE-E27E-4688-922B-29EDCDCB8A49")); + } +} +``` + +The custom migrations can inherit from `PackageMigrationBase` where we can use helper methods to pick up the schema. But we can also use the regular `MigrationBase` class. + +```csharp +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Strings; +using Umbraco.Cms.Infrastructure.Migrations; +using Umbraco.Cms.Infrastructure.Packaging; + +namespace CustomWelcomeDashboardProject.Migrations; + +public class CustomPackageMigration : PackageMigrationBase +{ + public CustomPackageMigration( + IPackagingService packagingService, + IMediaService mediaService, + MediaFileManager mediaFileManager, + MediaUrlGeneratorCollection mediaUrlGenerators, + IShortStringHelper shortStringHelper, + IContentTypeBaseServiceProvider contentTypeBaseServiceProvider, + IMigrationContext context, + IOptions packageMigrationsSettings) + : base( + packagingService, + mediaService, + mediaFileManager, + mediaUrlGenerators, + shortStringHelper, + contentTypeBaseServiceProvider, + context, + packageMigrationsSettings) + { + } + + protected override void Migrate() + { + ImportPackage.FromEmbeddedResource().Do(); + } +} +``` + +Here we also added the ZIP file as an embedded resource to the package project. + +![ZIP as an embedded resource](<../../../../10/umbraco-cms/extending/packages/images/embeded-resource-props (1).png>) + +![Automatic package migration](<../../../../10/umbraco-cms/extending/packages/images/embeded-zip-resource (1).png>) + +Whichever migration plan you choose to create, you will be able to see that your package has been installed after the migration is completed. + +![Installed package](../../../../10/umbraco-cms/extending/packages/images/installed-package.png) + +When using a custom package migration plan, the current state is ignored by default. This causes it to execute all migrations again whenever this isn't the same as the final state of the plan (e.g. if you added a new migration). This is due to the `IgnoreCurrentState` being set to `true` in the `PackageMigrationPlan` base class. You can override this property and set it to `false` again to make it behave like regular migration plans and only run the migrations that have not yet been executed on the current environment. + +### Attended/Unattended migration execution + +After creating a migration plan, the content and schema will automatically be imported either during unattended package migration or from the Packages section in the backoffice. + +By default, all these package migrations are executed unattended during startup but the solution owners can disable this in the configuration. IntelliSense can help, as well as provide further information about the `PackageMigrationsUnattended` setting. Then in the Packages section, there will be an option to run the package migration for each package individually when the `PackageMigrationsUnattended` is set to `false`. + +```json + "Umbraco": { + "CMS": { + . . . + "Unattended": { + "PackageMigrationsUnattended": false + } + } + } +``` + +![Attended package install](../../../../10/umbraco-cms/extending/packages/images/package-install-attended.png) + +The configuration of package migrations can be different for each environment and makes it possible to have the migration executed unattended on the development environment, but leave them out or manually execute them on other environments. This is useful when you use a tool like Umbraco Deploy or USync as these will migrate the content. diff --git a/16/umbraco-cms/extending/packages/example-package-repository.md b/16/umbraco-cms/extending/packages/example-package-repository.md new file mode 100644 index 00000000000..2d861a71710 --- /dev/null +++ b/16/umbraco-cms/extending/packages/example-package-repository.md @@ -0,0 +1,83 @@ +--- +meta.Title: An Example Package Repository +description: >- + Suggestions for organizing and Umbraco package source code repository. +--- + +# An Example Package Repository + +There are many ways to build and deploy your package to NuGet. You will likely have your own approach for organizing a solution and preferred tools for build and deployment. + +It may be useful though to review some practices we share here, of how we build packages at Umbraco. + +Some add-ons to the CMS created by Umbraco are closed-source, but we have some we make freely available with open-source repositories. An example is [Umbraco.AuthorizedServices](../../../../marketplace-and-integrations/packages/authorized-services.md), that has a source code repository [here on GitHub](https://github.com/umbraco/Umbraco.AuthorizedServices). + +## Solution Organization + +The solution consists of three projects. + +### Package Project + +The [main package project](https://github.com/umbraco/Umbraco.AuthorizedServices/tree/main/src/Umbraco.AuthorizedServices) lives in `src/`. It contains in the project file a dependency on Umbraco CMS: + +```xml + +``` + +Here we provide an upper bound on the package. This ensures that developers can only install it into projects that are using versions of Umbraco that we have tested the package with. + +When the next major version of Umbraco is released, we'll test and either extend the range or release a new version, as appropriate. + +### Tests Project + +We have a [project for unit tests](https://github.com/umbraco/Umbraco.AuthorizedServices/tree/main/tests/Umbraco.AuthorizedServices.Tests) in `tests/.Tests`. It contains references to `Umbraco.Cms.Tets` and a project reference to the package: + +```xml + +``` + +### Example Website + +Finally there's an [example Umbraco website](https://github.com/umbraco/Umbraco.AuthorizedServices/tree/main/examples/Umbraco.AuthorizedServices.TestSite) that we use for manual testing of the package. It also has a project reference to the package project, allowing us to test updates as they are compiled. + +### Solution Items + +As well as the projects, the following files are added to the solution: + +- [.artifactignore](https://github.com/umbraco/Umbraco.AuthorizedServices/blob/main/.artifactignore) - used by Azure DevOps services to [control which files are uploaded when you publish](https://learn.microsoft.com/en-us/azure/devops/artifacts/reference/artifactignore?view=azure-devops). This helps to reduce pipeline execution time. +- [.editorconfig](https://github.com/umbraco/Umbraco.AuthorizedServices/blob/main/.editorconfig) - used to [enforce consistent coding styles](https://editorconfig.org/) for multiple developers working on the same project across editors and IDEs. +- [.gitignore](https://github.com/umbraco/Umbraco.AuthorizedServices/blob/main/.gitignore) - controls which files are added to source control. +- [.globalconfig](https://github.com/umbraco/Umbraco.AuthorizedServices/blob/main/.globalconfig) - provides [further styling rules for the project files, even if stored outside of the project directory](https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/configuration-files#global-analyzerconfig). +- [Directory.Build.props](https://github.com/umbraco/Umbraco.AuthorizedServices/blob/main/Directory.Build.props) - used to provide common setting across all projects in the solution. +- global.json - ensures that the solution is always [built with a consistent version of .NET](https://learn.microsoft.com/en-us/dotnet/core/tools/global-json). We add this when we have a solution that targets a single Umbraco major version. +- [LICENSE.md](https://github.com/umbraco/Umbraco.AuthorizedServices/blob/main/LICENSE.md) - indicates the license through which the code is available. +- [README.md](https://github.com/umbraco/Umbraco.AuthorizedServices/blob/main/README.md) - a top-level documentation page for the source code repository. +- [icon.png](https://github.com/umbraco/Umbraco.AuthorizedServices/blob/main/icon.png) - an icon used for the package on NuGet and the Umbraco Marketplace. +- [umbraco-marketplace.json](https://github.com/umbraco/Umbraco.AuthorizedServices/blob/main/umbraco-marketplace.json) - provides [additional details about the package when listed on the Umbraco Marketplace](https://docs.umbraco.com/umbraco-dxp/marketplace/listing-your-package). +- [version.json](https://github.com/umbraco/Umbraco.AuthorizedServices/blob/main/version.json) - provides package versioning information for use by [Nerdbank.GitVersioning](https://github.com/dotnet/Nerdbank.GitVersioning). We use this tool for generating version numbers. + +## Build and Deployment + +We use Azure DevOps pipelines for continuous integration and releasing new versions of the package. The definition of how the project is built is defined in a `.yaml` file that's part of the source code repository. + +The file can be found [here](https://github.com/umbraco/Umbraco.AuthorizedServices/blob/main/azure-pipeline%20-%20Umbraco.AuthorizedServices.yml). + +Even if using another tool it may be worth reviewing how we have setup our pipeline. It may be you can setup something similar with your own provider. + +### Building the Package + +The build consists of two stages: building the solution and running unit tests. Only if both succeed is the build as a whole considered successful. + +![Azure DevOps build pipeline](../../../../10/umbraco-cms/extending/packages/images/azuredevops-build.png) + +### Releasing the Package + +We release the package manually in Azure DevOps, with a two stage process. Firstly we release to a "pre-releases" feed, and then after manual approval, to NuGet. + +![Azure DevOps release pipeline](../../../../10/umbraco-cms/extending/packages/images/azuredevops-release.png) + + + + + + diff --git a/16/umbraco-cms/extending/packages/good-practice-and-defaults.md b/16/umbraco-cms/extending/packages/good-practice-and-defaults.md new file mode 100644 index 00000000000..d692b3ca0a1 --- /dev/null +++ b/16/umbraco-cms/extending/packages/good-practice-and-defaults.md @@ -0,0 +1,208 @@ +--- +description: >- + Information on good practices and common defaults for Umbraco package + development. +--- + +# Good practice and defaults + +This document provides guides and notes on package development. It includes good practice guidelines that will help you maintain and support your package through multiple releases and versions of Umbraco. These good practices are not prescriptive, but offer a guide as to what often works well, and not-so-well, when developing packages for Umbraco. + +## Backoffice assets (JS/CSS/HTML) + +To extend the Umbraco backoffice, a package can provide files such as `umbraco-package.json` and TypeScript/JavaScript files that should be stored within the `App_Plugins` folder. It's recommended to put all files in a subfolder with a unique name, preferably using the package name, like `App_Plugins\MyPackage`. + +For more information on how to extend the Umbraco backoffice, have a look at the [customizing the backoffice documentation](../../customizing/overview.md) + +Files in the `App_Plugins` folder will be publicly available on the website even though they are not in the `wwwroot` folder. You should not store sensitive information in the `App_Plugins` folder. + +Files in the `App_Plugins` folder should be considered immutable. This means that they are not something a user of your package is expected to change on their site. + +The default delivery method for files to the `App_Plugins` folder is via a `.targets` file within a package. This means when a website is built, the files in this folder are copied over from the NuGet cache. When this happens, any changes a user might have made to these files will be lost. Equally, if the user performs a `dotnet clean` on a solution, all files in the `App_Plugins` folder will be deleted. + +{% hint style="info" %} +If you have files that you expect users of your package to alter you should not place them in the `App_Plugins` folder. +{% endhint %} + +## Views + +Views are used to render content on the front end of a website. If your package provides a way for the user to present the content publicly, you should copy these files to the views folder. + +As the files will still be copied during build you should ensure your target file does not overwrite newer or altered files. You should also ensure that it doesn't delete files on clean. + +{% hint style="info" %} +When you have a package that contains many views you might consider building a dotnet template or Razor Class Library (RCL) instead. By doing this, the files will not pollute your user's solutions. +{% endhint %} + +## License files + +Umbraco products store their licenses in `/umbraco/Licences`. It is recommended for third-party packages that require license files to also store their license files in this location. + +The default `.gitignore` for Umbraco templates will include any files in the `/Licenses` folder while ignoring most of the rest of the Umbraco folder. + +{% hint style="info" %} +The `/umbraco/Licenses` folder does not exist on a fresh installation of Umbraco. You need to create it manually before you save your license file to this folder. +{% endhint %} + +## Operating system considerations + +Umbraco a .NET application and can be run on multiple operating systems (Windows, Linux and macOS). When developing packages there are a few things you should be aware of for your package to run on all possible operating systems. + +### Case sensitivity + +The Linux and macOS file systems are case-sensitive by default. This means that `App_Plugins/myPackage` is a different location from `app_plugins/MyPACKAGE`. When building your package you should ensure that you always refer to folders and paths in a consistent way. + +A good way to ensure consistency is to use constants in your code to define file or folder locations. + +{% hint style="info" %} +You can adjust the case sensitivity of a Windows folder by running a command against a newly created/empty folder: + +```bash +fsutil.exe file queryCaseSensitiveInfo +``` +{% endhint %} + +#### Case sensitivity in default files and folders + +Some folders within Umbraco will already exist for all installations. If you access these folders, you need to be aware of the case used to ensure you end up in the correct place: + +| Folder | Note | +| ---------------------- | ------------------------------------------------------------------------------------- | +| /App\_Plugins | Uppercase `A` and `P` | +| /App\_Plugins/\[Ll]ang | Uppercase `L` | +| /Views | Uppercase `V` | +| /umbraco/Licenses | Lowercase `u` and uppercase `L` | +| /config | Lowercase `c` | + +{% hint style="info" %} +If you create a custom section/tree, Umbraco will build paths based on the name of that section or tree. These folder paths will be case-sensitive. + +For example: if you have a custom tree with the `treeAlias` of `MyCustomTree` Umbraco will look for files in `App_Plugins\MyPackage\backoffice\MyCustomTree\`. +{% endhint %} + +### File/folder locations + +You should never hardwire a file or folder location into code. Instead, it is recommended to follow either of the options below: + +* Access files using the ASP.NET Core file providers from `IHostingEnvironment`. +* Use the built-in methods to access well-known locations (see below). + +#### Temp folder + +The location of the Umbraco temp folder can be controlled via configuration and cannot be assumed. Use the `IHostingEnvironment.LocalTempPath` variable to locate the temp folder. + +```csharp +var localTempRoot = Path.GetFullPath(hostingEnvironment.LocalTempPath); +``` + +#### Relative paths + +If you require the path of a folder relative to the site root, you can use the `IHostingEnvironment` method to map a path: + +```csharp +// Full physical path to the content/project root +var contentRootPath = hostingEnvironment.MapPathContentRoot("~/"); +// Full physical path to the web root (served publicly) +var webRootPath = hostEnvironment.MapPathWebRoot("~/"); +// Absolute path to use as URL +var absolutePath = hostEnvironment.ToAbsolute("~/"); +``` + +### Folder/file system access + +It is not recommended to assume things about the folder structure of a site or use direct I/O commands to access the file system. Access to the disk within an ASP.NET Core site is usually managed with File Providers. You can access the file providers from the `IWebHostEnvironment` class. + +Example: If you want to read `robots.txt` from the `wwwroot` folder, use `WebRootFileProvider` in a controller to get to the root of the site and read the file: + +```csharp +public class MyController: UmbracoAuthorizedJsonController +{ + public MyController(IWebHostEnvironment webHostEnvironment) + { + var webRootProvider = webHostEnvironment.WebRootFileProvider; + + var myFileInfo = webRootProvider.GetFileInfo("robots.txt"); + if (myFileInfo.Exists) + { + using (var stream = myFileInfo.CreateReadStream()) + { + using(var streamReader = new StreamReader(stream)) + { + var text = streamReader.ReadToEnd(); + } + } + } + } +} +``` + +{% hint style="info" %} +This is the preferred method for file I/O. Not all files served up by a site are placed in the `wwwroot` folder when you expect them to be. This is especially true if the site is using Razor Class Library projects to insert static files. +{% endhint %} + +### Path manipulation + +Building folder path strings manually can cause problems when swapping between file systems. Windows uses the backslash character ('\\') to separate folders and files while Linux uses the forward slash ('/'). + +#### Example + +On Windows, a file might be located at `d:\website\robots.txt` while on Linux this might look like `/home/website/robots.txt` instead. + +You should use the .NET `Path` methods wherever possible when building paths to ensure that the correct path is built: + +```csharp +// WRONG: Don't build paths manually +var myPath = webrootPath + "\robots.txt"; + +// CORRECT: Build the path using the correct separator for the current file system +var myPath = Path.Combine(webrootPath, "robots.txt"); +``` + +If you need to build a path manually, use `Path.DirectorySeparatorChar` instead to get the correct separator for the file system. + +## Settings + +Most packages will require some settings to be stored for the users to control in order to change the behavior of the package. Where you store these settings will depend a lot on the nature of the package. + +### Property Editors + +Property Editors should store their settings as part of their Data Type in Umbraco. This is the standard way property editor behavior is controlled while it is familiar to users and supported by deployment tools. + +### Do not save to appsettings.json + +You should not alter `appsettings.json` via code. + +Settings in ASP.NET Core are merged from a number of different locations at runtime. You cannot guarantee that `appsettings.json` is the location that a setting is read from and your users may not want certain settings in that file. You can read settings from the configuration, but you cannot assume they have come from `appsettings.json`. + +### Settings locations + +There are many options for where you might save your settings and a lot will depend on the nature of your package. + +Below you can find pros and cons for different places where you might save the settings for your package. + +#### Save to the database + +Settings can be saved to the database. Settings can be stored in the database using the Umbraco `IKeyValueService`, and for more complex settings you can use a custom database table. + +* Pros: + * Settings will be accessible directly from the database, and not dependent on deployed files on disk. +* Cons: + * Setup is required to create the database tables for the settings to live in. + * The settings will only be available to the specific instance of the site, and any settings will not be deployed between a local, development, or staging site. + +#### Save to disk + +You can choose to save the settings to disk. As an example, the settings can be saved in the `/config` folder at the root of the site. + +* Pros: + * Settings will be accessible to the site and can be included in deployments between sites. +* Cons: + * You cannot guarantee that the folder or files will be present on a site or that they will be writable. + * Using your own config means your users cannot harness the power of the .NET Core configuration system and move settings to environment variables or other key/value stores. This means that sensitive information may end up on disk. + +#### Provide the users with appsettings.json snippets + +You could choose to provide your users with a snippet they can copy into their `appsettings.json` file. This will ensure that the settings are stored in the correct location. + +* Pro: Allows your users to fully control how and where the settings are stored (eg. secure key/value stores). +* Con: Requires the user to edit files on disk to get the settings in place. diff --git a/16/umbraco-cms/extending/packages/images/Package-properties-Visual-Studio.png b/16/umbraco-cms/extending/packages/images/Package-properties-Visual-Studio.png new file mode 100644 index 00000000000..8312d1e8cf7 Binary files /dev/null and b/16/umbraco-cms/extending/packages/images/Package-properties-Visual-Studio.png differ diff --git a/16/umbraco-cms/extending/packages/images/PackagesPage.png b/16/umbraco-cms/extending/packages/images/PackagesPage.png new file mode 100644 index 00000000000..cff25527501 Binary files /dev/null and b/16/umbraco-cms/extending/packages/images/PackagesPage.png differ diff --git a/16/umbraco-cms/extending/packages/images/app-pligins-contents.png b/16/umbraco-cms/extending/packages/images/app-pligins-contents.png new file mode 100644 index 00000000000..c0350372ad7 Binary files /dev/null and b/16/umbraco-cms/extending/packages/images/app-pligins-contents.png differ diff --git a/16/umbraco-cms/extending/packages/images/app-plugins-content.png b/16/umbraco-cms/extending/packages/images/app-plugins-content.png new file mode 100644 index 00000000000..69803716887 Binary files /dev/null and b/16/umbraco-cms/extending/packages/images/app-plugins-content.png differ diff --git a/16/umbraco-cms/extending/packages/images/app-plugins-starterkit.png b/16/umbraco-cms/extending/packages/images/app-plugins-starterkit.png new file mode 100644 index 00000000000..f69d24e28df Binary files /dev/null and b/16/umbraco-cms/extending/packages/images/app-plugins-starterkit.png differ diff --git a/16/umbraco-cms/extending/packages/images/backoffice-installed-packages.png b/16/umbraco-cms/extending/packages/images/backoffice-installed-packages.png new file mode 100644 index 00000000000..74651ae2ada Binary files /dev/null and b/16/umbraco-cms/extending/packages/images/backoffice-installed-packages.png differ diff --git a/16/umbraco-cms/extending/packages/images/backoffice-package-section.png b/16/umbraco-cms/extending/packages/images/backoffice-package-section.png new file mode 100644 index 00000000000..dca87d21e51 Binary files /dev/null and b/16/umbraco-cms/extending/packages/images/backoffice-package-section.png differ diff --git a/16/umbraco-cms/extending/packages/images/backoffice-packages-section-package.png b/16/umbraco-cms/extending/packages/images/backoffice-packages-section-package.png new file mode 100644 index 00000000000..c31ad803478 Binary files /dev/null and b/16/umbraco-cms/extending/packages/images/backoffice-packages-section-package.png differ diff --git a/16/umbraco-cms/extending/packages/images/backoffice-packages-section.png b/16/umbraco-cms/extending/packages/images/backoffice-packages-section.png new file mode 100644 index 00000000000..f14549ef229 Binary files /dev/null and b/16/umbraco-cms/extending/packages/images/backoffice-packages-section.png differ diff --git a/16/umbraco-cms/extending/packages/images/cloud-flow.png b/16/umbraco-cms/extending/packages/images/cloud-flow.png new file mode 100644 index 00000000000..1880ca8d129 Binary files /dev/null and b/16/umbraco-cms/extending/packages/images/cloud-flow.png differ diff --git a/16/umbraco-cms/extending/packages/images/create-package.png b/16/umbraco-cms/extending/packages/images/create-package.png new file mode 100644 index 00000000000..ec89f4f933f Binary files /dev/null and b/16/umbraco-cms/extending/packages/images/create-package.png differ diff --git a/16/umbraco-cms/extending/packages/images/creating-package-menu-v9.png b/16/umbraco-cms/extending/packages/images/creating-package-menu-v9.png new file mode 100644 index 00000000000..da73453c0a6 Binary files /dev/null and b/16/umbraco-cms/extending/packages/images/creating-package-menu-v9.png differ diff --git a/16/umbraco-cms/extending/packages/images/creating-package-menu.png b/16/umbraco-cms/extending/packages/images/creating-package-menu.png new file mode 100644 index 00000000000..2bbadf1c1ba Binary files /dev/null and b/16/umbraco-cms/extending/packages/images/creating-package-menu.png differ diff --git a/16/umbraco-cms/extending/packages/images/display-retired.png b/16/umbraco-cms/extending/packages/images/display-retired.png new file mode 100644 index 00000000000..9584bcae232 Binary files /dev/null and b/16/umbraco-cms/extending/packages/images/display-retired.png differ diff --git a/16/umbraco-cms/extending/packages/images/download-package-button.png b/16/umbraco-cms/extending/packages/images/download-package-button.png new file mode 100644 index 00000000000..ed8d1705221 Binary files /dev/null and b/16/umbraco-cms/extending/packages/images/download-package-button.png differ diff --git a/16/umbraco-cms/extending/packages/images/embeded-resource-props.png b/16/umbraco-cms/extending/packages/images/embeded-resource-props.png new file mode 100644 index 00000000000..ee8e0b6a49e Binary files /dev/null and b/16/umbraco-cms/extending/packages/images/embeded-resource-props.png differ diff --git a/16/umbraco-cms/extending/packages/images/embeded-resource.png b/16/umbraco-cms/extending/packages/images/embeded-resource.png new file mode 100644 index 00000000000..f3ac2e59a1a Binary files /dev/null and b/16/umbraco-cms/extending/packages/images/embeded-resource.png differ diff --git a/16/umbraco-cms/extending/packages/images/embeded-zip-resource.png b/16/umbraco-cms/extending/packages/images/embeded-zip-resource.png new file mode 100644 index 00000000000..4b85628f1a0 Binary files /dev/null and b/16/umbraco-cms/extending/packages/images/embeded-zip-resource.png differ diff --git a/16/umbraco-cms/extending/packages/images/empty-package-from-template-v15.png b/16/umbraco-cms/extending/packages/images/empty-package-from-template-v15.png new file mode 100644 index 00000000000..80e276ce065 Binary files /dev/null and b/16/umbraco-cms/extending/packages/images/empty-package-from-template-v15.png differ diff --git a/16/umbraco-cms/extending/packages/images/empty-package-from-template.png b/16/umbraco-cms/extending/packages/images/empty-package-from-template.png new file mode 100644 index 00000000000..0a692a5c54e Binary files /dev/null and b/16/umbraco-cms/extending/packages/images/empty-package-from-template.png differ diff --git a/16/umbraco-cms/extending/packages/images/flag-as-retired.png b/16/umbraco-cms/extending/packages/images/flag-as-retired.png new file mode 100644 index 00000000000..bf4104ef5d4 Binary files /dev/null and b/16/umbraco-cms/extending/packages/images/flag-as-retired.png differ diff --git a/16/umbraco-cms/extending/packages/images/forum-create.png b/16/umbraco-cms/extending/packages/images/forum-create.png new file mode 100644 index 00000000000..7df1e4bd4ca Binary files /dev/null and b/16/umbraco-cms/extending/packages/images/forum-create.png differ diff --git a/16/umbraco-cms/extending/packages/images/forums-display.png b/16/umbraco-cms/extending/packages/images/forums-display.png new file mode 100644 index 00000000000..accb6880390 Binary files /dev/null and b/16/umbraco-cms/extending/packages/images/forums-display.png differ diff --git a/16/umbraco-cms/extending/packages/images/forums-link.png b/16/umbraco-cms/extending/packages/images/forums-link.png new file mode 100644 index 00000000000..3c7540216a6 Binary files /dev/null and b/16/umbraco-cms/extending/packages/images/forums-link.png differ diff --git a/16/umbraco-cms/extending/packages/images/fromArtifact.png b/16/umbraco-cms/extending/packages/images/fromArtifact.png new file mode 100644 index 00000000000..22d50e7f5f1 Binary files /dev/null and b/16/umbraco-cms/extending/packages/images/fromArtifact.png differ diff --git a/16/umbraco-cms/extending/packages/images/generate-package=schema.png b/16/umbraco-cms/extending/packages/images/generate-package=schema.png new file mode 100644 index 00000000000..5df310e995d Binary files /dev/null and b/16/umbraco-cms/extending/packages/images/generate-package=schema.png differ diff --git a/16/umbraco-cms/extending/packages/images/hitting-breakpoints.png b/16/umbraco-cms/extending/packages/images/hitting-breakpoints.png new file mode 100644 index 00000000000..2fbe4666e6e Binary files /dev/null and b/16/umbraco-cms/extending/packages/images/hitting-breakpoints.png differ diff --git a/16/umbraco-cms/extending/packages/images/installed-package-leftovers-backoffice.png b/16/umbraco-cms/extending/packages/images/installed-package-leftovers-backoffice.png new file mode 100644 index 00000000000..48ee6810320 Binary files /dev/null and b/16/umbraco-cms/extending/packages/images/installed-package-leftovers-backoffice.png differ diff --git a/16/umbraco-cms/extending/packages/images/installed-package.png b/16/umbraco-cms/extending/packages/images/installed-package.png new file mode 100644 index 00000000000..715cf1f9bcb Binary files /dev/null and b/16/umbraco-cms/extending/packages/images/installed-package.png differ diff --git a/16/umbraco-cms/extending/packages/images/nuget-installing-options.png b/16/umbraco-cms/extending/packages/images/nuget-installing-options.png new file mode 100644 index 00000000000..95d91944f82 Binary files /dev/null and b/16/umbraco-cms/extending/packages/images/nuget-installing-options.png differ diff --git a/16/umbraco-cms/extending/packages/images/nuget-package-in-manager.png b/16/umbraco-cms/extending/packages/images/nuget-package-in-manager.png new file mode 100644 index 00000000000..d5e1b3b80a5 Binary files /dev/null and b/16/umbraco-cms/extending/packages/images/nuget-package-in-manager.png differ diff --git a/16/umbraco-cms/extending/packages/images/package-cli-command.png b/16/umbraco-cms/extending/packages/images/package-cli-command.png new file mode 100644 index 00000000000..e763915d62b Binary files /dev/null and b/16/umbraco-cms/extending/packages/images/package-cli-command.png differ diff --git a/16/umbraco-cms/extending/packages/images/package-custom-folder.png b/16/umbraco-cms/extending/packages/images/package-custom-folder.png new file mode 100644 index 00000000000..6d6d28f8af8 Binary files /dev/null and b/16/umbraco-cms/extending/packages/images/package-custom-folder.png differ diff --git a/16/umbraco-cms/extending/packages/images/package-default-location.png b/16/umbraco-cms/extending/packages/images/package-default-location.png new file mode 100644 index 00000000000..0e53c020cae Binary files /dev/null and b/16/umbraco-cms/extending/packages/images/package-default-location.png differ diff --git a/16/umbraco-cms/extending/packages/images/package-files-list.png b/16/umbraco-cms/extending/packages/images/package-files-list.png new file mode 100644 index 00000000000..b3d5b4843ed Binary files /dev/null and b/16/umbraco-cms/extending/packages/images/package-files-list.png differ diff --git a/16/umbraco-cms/extending/packages/images/package-install-attended.png b/16/umbraco-cms/extending/packages/images/package-install-attended.png new file mode 100644 index 00000000000..c6f31569b02 Binary files /dev/null and b/16/umbraco-cms/extending/packages/images/package-install-attended.png differ diff --git a/16/umbraco-cms/extending/packages/images/package-options.gif b/16/umbraco-cms/extending/packages/images/package-options.gif new file mode 100644 index 00000000000..0bd1a81695f Binary files /dev/null and b/16/umbraco-cms/extending/packages/images/package-options.gif differ diff --git a/16/umbraco-cms/extending/packages/images/package-properties.png b/16/umbraco-cms/extending/packages/images/package-properties.png new file mode 100644 index 00000000000..b7fb1b0d7f5 Binary files /dev/null and b/16/umbraco-cms/extending/packages/images/package-properties.png differ diff --git a/16/umbraco-cms/extending/packages/images/property-editor.png b/16/umbraco-cms/extending/packages/images/property-editor.png new file mode 100644 index 00000000000..7b049f578ca Binary files /dev/null and b/16/umbraco-cms/extending/packages/images/property-editor.png differ diff --git a/16/umbraco-cms/extending/packages/images/removing-content.png b/16/umbraco-cms/extending/packages/images/removing-content.png new file mode 100644 index 00000000000..3fa0db3b6ad Binary files /dev/null and b/16/umbraco-cms/extending/packages/images/removing-content.png differ diff --git a/16/umbraco-cms/extending/packages/images/removing-datatypes.png b/16/umbraco-cms/extending/packages/images/removing-datatypes.png new file mode 100644 index 00000000000..7cd36ad62eb Binary files /dev/null and b/16/umbraco-cms/extending/packages/images/removing-datatypes.png differ diff --git a/16/umbraco-cms/extending/packages/images/removing-document-types.png b/16/umbraco-cms/extending/packages/images/removing-document-types.png new file mode 100644 index 00000000000..40d0ad2a49d Binary files /dev/null and b/16/umbraco-cms/extending/packages/images/removing-document-types.png differ diff --git a/16/umbraco-cms/extending/packages/images/removing-media.png b/16/umbraco-cms/extending/packages/images/removing-media.png new file mode 100644 index 00000000000..160ccfac9f6 Binary files /dev/null and b/16/umbraco-cms/extending/packages/images/removing-media.png differ diff --git a/16/umbraco-cms/extending/packages/images/removing-partials.png b/16/umbraco-cms/extending/packages/images/removing-partials.png new file mode 100644 index 00000000000..1fc93d8ccaa Binary files /dev/null and b/16/umbraco-cms/extending/packages/images/removing-partials.png differ diff --git a/16/umbraco-cms/extending/packages/images/removing-templates.png b/16/umbraco-cms/extending/packages/images/removing-templates.png new file mode 100644 index 00000000000..08beb8e2c15 Binary files /dev/null and b/16/umbraco-cms/extending/packages/images/removing-templates.png differ diff --git a/16/umbraco-cms/extending/packages/images/select-files-for-package.png b/16/umbraco-cms/extending/packages/images/select-files-for-package.png new file mode 100644 index 00000000000..d059e30fe7d Binary files /dev/null and b/16/umbraco-cms/extending/packages/images/select-files-for-package.png differ diff --git a/16/umbraco-cms/extending/packages/images/seochecker-after-removal.png b/16/umbraco-cms/extending/packages/images/seochecker-after-removal.png new file mode 100644 index 00000000000..9eb77e23309 Binary files /dev/null and b/16/umbraco-cms/extending/packages/images/seochecker-after-removal.png differ diff --git a/16/umbraco-cms/extending/packages/images/seochecker-app-plugins.png b/16/umbraco-cms/extending/packages/images/seochecker-app-plugins.png new file mode 100644 index 00000000000..b2b2ec0b92c Binary files /dev/null and b/16/umbraco-cms/extending/packages/images/seochecker-app-plugins.png differ diff --git a/16/umbraco-cms/extending/packages/images/seochecker-content-section.png b/16/umbraco-cms/extending/packages/images/seochecker-content-section.png new file mode 100644 index 00000000000..5c83b0d11e6 Binary files /dev/null and b/16/umbraco-cms/extending/packages/images/seochecker-content-section.png differ diff --git a/16/umbraco-cms/extending/packages/images/specify-version-info.png b/16/umbraco-cms/extending/packages/images/specify-version-info.png new file mode 100644 index 00000000000..ece295fecdb Binary files /dev/null and b/16/umbraco-cms/extending/packages/images/specify-version-info.png differ diff --git a/16/umbraco-cms/extending/packages/images/steppingThroughCode.png b/16/umbraco-cms/extending/packages/images/steppingThroughCode.png new file mode 100644 index 00000000000..2edb08c90e1 Binary files /dev/null and b/16/umbraco-cms/extending/packages/images/steppingThroughCode.png differ diff --git a/16/umbraco-cms/extending/packages/images/team-link.png b/16/umbraco-cms/extending/packages/images/team-link.png new file mode 100644 index 00000000000..61008aef4e3 Binary files /dev/null and b/16/umbraco-cms/extending/packages/images/team-link.png differ diff --git a/16/umbraco-cms/extending/packages/images/uninstalling-via-nuget-package-manager.png b/16/umbraco-cms/extending/packages/images/uninstalling-via-nuget-package-manager.png new file mode 100644 index 00000000000..b63cc056930 Binary files /dev/null and b/16/umbraco-cms/extending/packages/images/uninstalling-via-nuget-package-manager.png differ diff --git a/16/umbraco-cms/extending/packages/images/valueconnector.gif b/16/umbraco-cms/extending/packages/images/valueconnector.gif new file mode 100644 index 00000000000..40948d682f1 Binary files /dev/null and b/16/umbraco-cms/extending/packages/images/valueconnector.gif differ diff --git a/16/umbraco-cms/extending/packages/images/vs-cleaning-solution.png b/16/umbraco-cms/extending/packages/images/vs-cleaning-solution.png new file mode 100644 index 00000000000..435e2505b06 Binary files /dev/null and b/16/umbraco-cms/extending/packages/images/vs-cleaning-solution.png differ diff --git a/16/umbraco-cms/extending/packages/images/zip-package-contents.png b/16/umbraco-cms/extending/packages/images/zip-package-contents.png new file mode 100644 index 00000000000..9350caff0ed Binary files /dev/null and b/16/umbraco-cms/extending/packages/images/zip-package-contents.png differ diff --git a/16/umbraco-cms/extending/packages/images/zip-package-v9.png b/16/umbraco-cms/extending/packages/images/zip-package-v9.png new file mode 100644 index 00000000000..169bbfb1591 Binary files /dev/null and b/16/umbraco-cms/extending/packages/images/zip-package-v9.png differ diff --git a/16/umbraco-cms/extending/packages/images/zip-package.png b/16/umbraco-cms/extending/packages/images/zip-package.png new file mode 100644 index 00000000000..0ec18b86ceb Binary files /dev/null and b/16/umbraco-cms/extending/packages/images/zip-package.png differ diff --git a/16/umbraco-cms/extending/packages/installing-and-uninstalling-packages.md b/16/umbraco-cms/extending/packages/installing-and-uninstalling-packages.md new file mode 100644 index 00000000000..e132651cd2e --- /dev/null +++ b/16/umbraco-cms/extending/packages/installing-and-uninstalling-packages.md @@ -0,0 +1,189 @@ +--- +description: >- + The process of installing and, in turn, uninstalling packages in your Umbraco + CMS website. +--- + +# Installing and Uninstalling Packages + +This article will cover the process of installing as well as uninstalling packages from your Umbraco CMS website. + +## Installing packages + +In the Umbraco Backoffice, you will find a **Packages** section that displays the [Umbraco Marketplace](https://marketplace.umbraco.com/). From here you can browse all community-made as well as official Umbraco packages for the Umbraco CMS. + +![Backoffice - Packages section](images/backoffice-packages-section.png) + +Navigating to a specific package in the section will present you with an overview of the package, as well as an install snippet for NuGet CLI. + +![Backoffice - Starter Kit package](images/backoffice-packages-section-package.png) + +The packages can be installed by using: + +* **NuGet Package Manager** in Visual Studio +* **Package Manager Console** in Visual Studio +* .NET CLI (usually accessible from the terminal/command prompt of your system) + +For example, to install the StarterKit package for the Umbraco CMS the command would be: + +`dotnet add package Umbraco.TheStarterKit` + +Navigating to the NuGet Package Manager in Visual Studio is more visual, and gives you an overview of already installed packages. + +![Visual Studio - nuget packages manager](images/nuget-installing-options.png) + +The Package Manager has an integrated search function that allows you to find any public NuGet package and install it on the project. + +![Visual Studio - finding The Starter Kit](images/nuget-package-in-manager.png) + +Once the package has been installed, it will show up under the **Packages** section in the backoffice, under **Installed** tab. + +![Backoffice - installed packages](../../../../10/umbraco-cms/extending/packages/images/backoffice-installed-packages.png) + +## Uninstalling packages + +Uninstalling packages is not always as straightforward as installing them. + +In this section, we will provide two examples of uninstalling a package - the StarterKit package and the SEOChecker package. + +### Uninstalling packages like the StarterKit + +{% hint style="info" %} +Keep in mind that this particular guide targets a specific package. There are many packages out there, and each one is different. The exact steps presented here might not work the exact same way for all the packages, though the general approach should still apply. +{% endhint %} + +The Starter Kit provides you with a boilerplate website solution to build upon. The package installs Document Types, Templates, media, content, and everything else needed to set up a small website. There is little custom code/functionality involved which is usually the case for such starter kit or sample-site packages. + +To uninstall a package, either run a command or use the NuGet Package Manager in Visual Studio. + +`dotnet remove package Umbraco.TheStarterKit` + +![Visual Studio - uninstalling via Package Manager](images/uninstalling-via-nuget-package-manager.png) + +It is recommended to clean the solution after removing any package. This can be done by right-clicking the project in Visual Studio and choosing the _Clean_ option, or using the `dotnet clean` command. + +![Visual Studio - clean solution](images/vs-cleaning-solution.png) + +#### Removing package leftovers from the backoffice + +With packages like the StarterKit, the process does not end there. While the package is gone, content - and everything else needed for the website - is still available in the backoffice. To fully remove this kind of package, additional steps are needed. + +
+ +Remove content provided by the package + +There is no universal way to tell what content comes from a package, and what content is custom-made. In the Content section, delete individual nodes accordingly. If the goal is to fully remove the package and clean the site, all the content can be removed (and the recycle bin emptied). + +Backoffice - removing content + +
+ +
+ +Remove media provided by the package + +Similar to content, media also might have to be removed. + +Backoffice - removing media + +
+ +
+ +Remove Document Types + +Document Types can be removed from the **Settings** section. If fully removing the package, all Document Types can be deleted, as there are no default Document Types in a clean-slate Umbraco installation. + +Backoffice - removing document types + +
+ +
+ +Removing Data Types + +As opposed to Document Types, there are some Data Types that are available out of the box when Umbraco is installed. It is not recommended to remove them. The safe approach is to delete any item that starts with a Document Type prefix and includes multiple dashes. That is the default naming convention for new configurations of Data Types (Example: "Blog - How many posts should be shown - Slider") + +Backoffice - removing data types + +
+ +
+ +Removing Templates + +No Templates are available out of the box in a new installation. If cleaning up after a package, it would be okay to delete all that are present + +Backoffice - removing templates + +
+ +
+ +Removing Partial Views + +Out of the box, there are a few views available in the `blocklist` and `grid` folders. Everything else can theoretically be removed. + +Backoffice - removing partial views + +
+ +
+ +Cleaning leftover files on disk + +Some packages might reference other items. For example, installing the StarterKit also adds `Bergmania.OpenStreetMap` to your project. That component will show up as installed in the backoffice even after uninstalling the NuGet package. + +Backoffice - Packages section - leftover dependency + +In many cases, custom dashboards, editors, and scripts are left in the `App_Plugins` folder after a package has been uninstalled via NuGet. These files also have to be deleted manually. + +Visual Studio - App Plugins leftover files + +
+ +### Uninstalling packages like the SEOChecker + +{% hint style="info" %} +Keep in mind that this particular guide targets a specific package. There are many packages out there, and each one is different. The exact steps presented here might not work the exact same way for all the packages, though the general approach should still apply. +{% endhint %} + +More advanced packages that add functionality on top of Umbraco, usually rely on providing custom, compiled code. That being said, many of such packages also implement custom Sections, Dashboards, editors, and views. + +In this example, we will be using the SEOChecker package. This package allows developers of the site to add custom properties to Document Types used to track search engine optimization practices. + +An example use case of the SEOChecker property on a Document Type, as presented in the Content section: + +![SEOChecker in content](../../../../10/umbraco-cms/extending/packages/images/seochecker-content-section.png) + +To uninstall the SEOChecker from a website, the first step is to remove the package via a `dotnet` command or use the NuGet Package Manager. + +The following command can be used for uninstalling the package: + +`dotnet remove package SEOChecker` + +After that, cleaning the solution is recommended. + +![Visual Studio - clean solution](images/vs-cleaning-solution.png) + +
+ +Cleaning leftover files on disk + +While uninstalling the package would remove most of the custom code, the `App_Plugins` folder has to be cleaned manually. + +SEOChecker files in App Plugins + +Removing _seochecker_ folder from `App_Plugins` will clean up the leftover backoffice section and dashboards. + +
+ +## Consequences of removing packages + +If content on the website relies on having a custom Property Editor or a data source installed, those properties will default to a `label` Data Type. All previously saved content in the property will in turn be converted to a string. + +In the case of the SEOChecker, the custom property added from the package would look like this after all the package files have been removed: + +![The SEOChecker in Content after removing the package](../../../../10/umbraco-cms/extending/packages/images/seochecker-after-removal.png) + +Depending on the packages and the implementation, rendering of content from custom editors, or any frontend functionality dependent on external code, might not work correctly. It is always recommended to inspect the frontend of the site after removing any packages. diff --git a/16/umbraco-cms/extending/packages/language-files-for-packages.md b/16/umbraco-cms/extending/packages/language-files-for-packages.md new file mode 100644 index 00000000000..e5c50881d19 --- /dev/null +++ b/16/umbraco-cms/extending/packages/language-files-for-packages.md @@ -0,0 +1,36 @@ +--- +description: >- + Information on how to use language files to make your Umbraco package UI + support multiple languages +--- + +# Language file for packages + +If you want your package to be available in different languages, you can use the existing localizations from Umbraco or register your own localizations. The localizations are written as a key-value pair pattern. + +To register localizations to a language, you must add a new manifest to the Extension API. The manifest can be added through the `umbraco-package.json` file like this: + +{% code title="umbraco-package.json" lineNumbers="true" %} +``` +``` +{% endcode %} + +```json +{ + ... + "name": "MyPackage", + "extensions": [ + { + "type": "localization", + "alias": "MyPackage.Localize.EnUS", + "name": "English (United States)", + "meta": { + "culture": "en-us" + }, + "js": "/App_Plugins/MyPackage/Localization/en-us.js" + } + ] +} +``` + +Read the [UI Localization documentation](../../customizing/foundation/localization.md) to learn in-depth on how you can use languages in your packages and Umbraco in general. diff --git a/16/umbraco-cms/extending/packages/listing-on-marketplace.md b/16/umbraco-cms/extending/packages/listing-on-marketplace.md new file mode 100644 index 00000000000..2cc27f0408c --- /dev/null +++ b/16/umbraco-cms/extending/packages/listing-on-marketplace.md @@ -0,0 +1,11 @@ +--- +description: Information on how to list your package on the Umbraco Marketplace. +--- + +# Listing on the Umbraco Marketplace + +The [Umbraco Marketplace](https://marketplace.umbraco.com) is a website built and maintained by Umbraco HQ to support searching and reviewing packages. + +It lists all commercial and open-source packages that the community has made available on NuGet. + +More information, including details of the steps for listing, are available at the dedicated [documentation space for the Marketplace](https://docs.umbraco.com/umbraco-dxp/marketplace/introduction). diff --git a/16/umbraco-cms/extending/packages/maintaining-packages.md b/16/umbraco-cms/extending/packages/maintaining-packages.md new file mode 100644 index 00000000000..562e0edf678 --- /dev/null +++ b/16/umbraco-cms/extending/packages/maintaining-packages.md @@ -0,0 +1,50 @@ +--- +description: >- + Once you've created and published your package, here is what's involved in + it's ongoing maintenance +--- + +# Maintaining packages + +Once you've created and published your package, what's involved in its ongoing maintenance? + +## Updating with new Umbraco major versions + +Umbraco will regular release update to the CMS as patch or minor versions. These are verified to be backwards compatible. As such there's no expectation that a package may break when a new version of this type comes out. + +When a new major version of Umbraco is released, there will be breaking changes. You should test your package on this latest version to confirm it still works. Unless there's been a significant change to the CMS, many packages will continue to work with the new major version without any update. However you may be using a service or API that has undergone a breaking change. + +If this happens, the changes will be documented and you should be able to update your code, recompile and test. After that you can release a new major version of your own package. + +Even if there are no breaking changes that affect you, it's worth also looking for any code you using that is marked as obsolete. Umbraco will obsolete public methods or constructors that are expected to be removed in a future major version. + +### Referencing Umbraco dependencies + +When creating a package with Umbraco you will be taking a dependency on at least one Umbraco package. You will do this in the `.csproj` file for your package: + +```xml + +``` + +As indicated, this states the package is compatible with Umbraco 10 and any future version. This would allow the developer to install the package into an Umbraco 12 or 13 solution for example. + +If you want to maintain tighter control over this, you can specify an upper bound, like this: + +```xml + +``` + +This states that the package is compatible with Umbraco 10, 11 and 12, but not 13. Thus it prevents anyone installing your package into an Umbraco solution that you haven't verified compatibility with. Once you have, you can increase the bound or otherwise update the dependent Umbraco version as appropriate. + +## Manage feature requests and issues + +If you want to encourage feedback, feature requests, and issue reports then you should make available an issue tracker. + +This can be [provided as information about your package](https://docs.umbraco.com/umbraco-dxp/marketplace/listing-your-package) and will be linked from your package's page on the Umbraco Marketplace. Specifically you should populate the `IssueTrackerUrl` field. + +## Package no longer required? + +After some time it could be that your package should no longer be used. Perhaps it is now too old, or it has been superseded by another one that you recommend instead. + +You can indicate this by [deprecating your package on NuGet](https://learn.microsoft.com/en-us/nuget/nuget-org/deprecate-packages). + diff --git a/16/umbraco-cms/extending/packages/packages-on-umbraco-cloud.md b/16/umbraco-cms/extending/packages/packages-on-umbraco-cloud.md new file mode 100644 index 00000000000..ec2f5295f35 --- /dev/null +++ b/16/umbraco-cms/extending/packages/packages-on-umbraco-cloud.md @@ -0,0 +1,44 @@ +--- +description: Things to consider for package development and usage in Umbraco Cloud +--- + +# Packages on Umbraco Cloud + +If you want to use or develop packages for Umbraco Cloud there are a few things to consider and be aware of. The two most important things to know about are + +* [How you should store data on Cloud](packages-on-umbraco-cloud.md#storing-data) +* [Using custom property editors with Deploy](packages-on-umbraco-cloud.md#valueconnectors) + +## Storing data + +When developing a package you will sometimes store data, this can be data in many forms - Umbraco schema / content, package settings, etc. + +When you develop a package for Umbraco Cloud there are a few things to be aware of when storing data, mainly whether you want that data to be specific to 1 environment or more. + +Let's take a look at the most common ways of storing data in packages - and what to watch out for on Cloud. + +### Migrations + +A [migration](../database.md) is some code that you run as part of a migration plan. That migration plan has an ID that is stored in the database (in the KeyValue table). This means that when you add new migrations Umbraco will only execute the ones that came after the one with the stored ID. The most important difference between a migration and a package action is when they are initialized. A package action runs on package install and uninstall, whereas a migration will run whenever you want it to run, see below for common examples. + +As migration runs are stored in the database of the site it also means that they will run on each environment you trigger them on. The most common way to trigger a migration is to include them in a [composer](../../implementation/composing.md), which will ensure they run on site startup. This means any commands you have in your migration will automatically run when the site starts up. When your package code is pushed to a new environment it will run them from the beginning on that environment as no ID is saved in the database. + +This is normally a good thing. However if you generate any Umbraco schema then Umbraco Deploy will automatically create [UDA files](https://docs.umbraco.com/umbraco-cloud/set-up/power-tools/generating-uda-files) based on that schema, and commit them to source control. This means that when you deploy all your files to the next environment the migration will run again, create duplicates and generate duplicate UDA files, which could end up causing a lot of issues. + +You could consider creating Umbraco schema only during a package action, and then running things like creating database tables in migrations. Another good workaround could be to not run the migrations in a composer, but rather create a dashboard for the package where the user can choose which migrations to run themselves. The [Articulate package](https://github.com/Shazwazza/Articulate/blob/master/build/packageManifest.xml#L613) has an example of this. + +### Creating files + +You may sometimes choose to save data in a file. Could be a separate config file for your package or a [config transform file](https://docs.umbraco.com/umbraco-cloud/set-up/config-transforms) to add an app setting to the web.config. If you do this be aware of two things: + +1. If these files are generated on a Cloud environment they will not be stored in source control, and will be overwritten on next deployment. They need to be installed locally, committed to source control and then pushed up to the Cloud environments. We have an [existing feature request](../../reference/mapping.md) on allowing package creators to commit their files directly on Cloud, and it is possible to do so currently but not in a supported way, and it may change suddenly. +2. If you need the content of the files to be different on the different environments you will need to use environment specific [config transforms](https://docs.umbraco.com/umbraco-cloud/set-up/config-transforms). + +## Value connectors + +A value connector is an extension to Umbraco Deploy that allows you to transform data when you deploy content of any kind between environments. When it comes to packages, one reason you need to consider these if you are supporting deploying content properties that rely on integer IDs. Content and other Umbraco data has two identifiers - an integer and a GUID. The GUID is consistent between environments but the integer ID is not. As such, if transferring content between environments and relying on integer IDs, you'll need to include a value connector to transform the value. + +They also manage dependencies for property data. If you save an ID of an image in your property editor, you can make sure the related image media item is transferred too. + +You can read more about value connectors and other extensions to Umbraco Deploy [here](https://docs.umbraco.com/umbraco-deploy/getting-started/extending). + diff --git a/16/umbraco-cms/extending/section-trees/images/add-custom-section-v8 (1).png b/16/umbraco-cms/extending/section-trees/images/add-custom-section-v8 (1).png new file mode 100644 index 00000000000..d5465050e54 Binary files /dev/null and b/16/umbraco-cms/extending/section-trees/images/add-custom-section-v8 (1).png differ diff --git a/16/umbraco-cms/extending/section-trees/images/add-custom-section-v8.png b/16/umbraco-cms/extending/section-trees/images/add-custom-section-v8.png new file mode 100644 index 00000000000..d5465050e54 Binary files /dev/null and b/16/umbraco-cms/extending/section-trees/images/add-custom-section-v8.png differ diff --git a/16/umbraco-cms/extending/section-trees/images/add-custom-section.png b/16/umbraco-cms/extending/section-trees/images/add-custom-section.png new file mode 100644 index 00000000000..56ab633a134 Binary files /dev/null and b/16/umbraco-cms/extending/section-trees/images/add-custom-section.png differ diff --git a/16/umbraco-cms/extending/section-trees/images/backoffice-search-v8 (1).png b/16/umbraco-cms/extending/section-trees/images/backoffice-search-v8 (1).png new file mode 100644 index 00000000000..1ab9820f3bc Binary files /dev/null and b/16/umbraco-cms/extending/section-trees/images/backoffice-search-v8 (1).png differ diff --git a/16/umbraco-cms/extending/section-trees/images/backoffice-search-v8.png b/16/umbraco-cms/extending/section-trees/images/backoffice-search-v8.png new file mode 100644 index 00000000000..1ab9820f3bc Binary files /dev/null and b/16/umbraco-cms/extending/section-trees/images/backoffice-search-v8.png differ diff --git a/16/umbraco-cms/extending/section-trees/images/backoffice-search.png b/16/umbraco-cms/extending/section-trees/images/backoffice-search.png new file mode 100644 index 00000000000..31c5f536067 Binary files /dev/null and b/16/umbraco-cms/extending/section-trees/images/backoffice-search.png differ diff --git a/16/umbraco-cms/extending/section-trees/images/custom-section-alias-v8 (1).png b/16/umbraco-cms/extending/section-trees/images/custom-section-alias-v8 (1).png new file mode 100644 index 00000000000..6a8e9633af9 Binary files /dev/null and b/16/umbraco-cms/extending/section-trees/images/custom-section-alias-v8 (1).png differ diff --git a/16/umbraco-cms/extending/section-trees/images/custom-section-alias-v8.png b/16/umbraco-cms/extending/section-trees/images/custom-section-alias-v8.png new file mode 100644 index 00000000000..6a8e9633af9 Binary files /dev/null and b/16/umbraco-cms/extending/section-trees/images/custom-section-alias-v8.png differ diff --git a/16/umbraco-cms/extending/section-trees/images/custom-section-alias.png b/16/umbraco-cms/extending/section-trees/images/custom-section-alias.png new file mode 100644 index 00000000000..9cac915a1cb Binary files /dev/null and b/16/umbraco-cms/extending/section-trees/images/custom-section-alias.png differ diff --git a/16/umbraco-cms/extending/section-trees/images/favouritethings-search-v8 (1).png b/16/umbraco-cms/extending/section-trees/images/favouritethings-search-v8 (1).png new file mode 100644 index 00000000000..c1010a8dda1 Binary files /dev/null and b/16/umbraco-cms/extending/section-trees/images/favouritethings-search-v8 (1).png differ diff --git a/16/umbraco-cms/extending/section-trees/images/favouritethings-search-v8.png b/16/umbraco-cms/extending/section-trees/images/favouritethings-search-v8.png new file mode 100644 index 00000000000..c1010a8dda1 Binary files /dev/null and b/16/umbraco-cms/extending/section-trees/images/favouritethings-search-v8.png differ diff --git a/16/umbraco-cms/extending/section-trees/images/favouritethings-search.png b/16/umbraco-cms/extending/section-trees/images/favouritethings-search.png new file mode 100644 index 00000000000..9b297a84ac5 Binary files /dev/null and b/16/umbraco-cms/extending/section-trees/images/favouritethings-search.png differ diff --git a/16/umbraco-cms/extending/section-trees/trees/images/delete-raindrops-on-roses-v8.png b/16/umbraco-cms/extending/section-trees/trees/images/delete-raindrops-on-roses-v8.png new file mode 100644 index 00000000000..ac562758a4b Binary files /dev/null and b/16/umbraco-cms/extending/section-trees/trees/images/delete-raindrops-on-roses-v8.png differ diff --git a/16/umbraco-cms/extending/section-trees/trees/images/favourite-thing-custom-single-node-tree.png b/16/umbraco-cms/extending/section-trees/trees/images/favourite-thing-custom-single-node-tree.png new file mode 100644 index 00000000000..c40ceb25a75 Binary files /dev/null and b/16/umbraco-cms/extending/section-trees/trees/images/favourite-thing-custom-single-node-tree.png differ diff --git a/16/umbraco-cms/extending/section-trees/trees/images/favourite-thing-custom-tree-v8.png b/16/umbraco-cms/extending/section-trees/trees/images/favourite-thing-custom-tree-v8.png new file mode 100644 index 00000000000..9dcef2ad50c Binary files /dev/null and b/16/umbraco-cms/extending/section-trees/trees/images/favourite-thing-custom-tree-v8.png differ diff --git a/16/umbraco-cms/extending/section-trees/trees/images/favourite-things-custom-tree-v8.png b/16/umbraco-cms/extending/section-trees/trees/images/favourite-things-custom-tree-v8.png new file mode 100644 index 00000000000..c6e99e95dc7 Binary files /dev/null and b/16/umbraco-cms/extending/section-trees/trees/images/favourite-things-custom-tree-v8.png differ diff --git a/16/umbraco-cms/extending/section-trees/trees/images/favourite-things-custom-tree.png b/16/umbraco-cms/extending/section-trees/trees/images/favourite-things-custom-tree.png new file mode 100644 index 00000000000..ada029e7947 Binary files /dev/null and b/16/umbraco-cms/extending/section-trees/trees/images/favourite-things-custom-tree.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/README.md b/16/umbraco-cms/fundamentals/backoffice/README.md new file mode 100644 index 00000000000..cf1224b1a9c --- /dev/null +++ b/16/umbraco-cms/fundamentals/backoffice/README.md @@ -0,0 +1,147 @@ +--- +description: >- + Learn more about the Umbraco backoffice which is the admin side of your + Umbraco website +--- + +# Backoffice + +In this article you can learn more about the common terms and concepts that are used throughout the Umbraco backoffice. + +## [Login screen](login.md) + +When you go to the backoffice for the first time, you're presented with the login screen. + +![Login screen](images/login-backoffice-login-v14.png) + +[Read more about the login screen](login.md). + +## [Section](sections.md) + +A section in Umbraco is where you do specific tasks related to that section. For example Content, Settings and Users. You can navigate between the different sections of the backoffice by clicking the corresponding icon in the section menu. + +_The **Section menu** is the horizontal menu located on the top of the backoffice._ + +![Section](images/highlight-sections-v14.png) + +[Read more about the section menu](sections.md). + +## [Tree](../../customizing/section-trees.md) + +A tree is a hierarchical list of items related (and usually restricted) to a specific concept, like for example content or media. + +You can expand trees by clicking the side arrow ![Expand Node](images/expand-node-v14.png) to the left of the node. + +![Tree](images/highlight-tree-v14.png) + +[Read more about the Tree](../../customizing/section-trees.md) + +## Node + +A node is an item in a tree. Media section items appear as nodes in the Media tree, while pages and content are displayed in the Content tree, and so on. + +![Node](images/highlight-content-node-v14.png) + +## [Dashboards](../../customizing/extending-overview/extension-types/dashboard.md) + +A dashboard is the main view you are presented with when entering a section within the backoffice. It can be used to show valuable information to the users of the system. + +![Default dashboard in the Content section](images/highlight-dashboard-v14.png) + +[Read more about Dashboards](../../customizing/extending-overview/extension-types/dashboard.md) + +## Editor + +An editor is what you use to edit different items within the backoffice. There are editors specific to editing stylesheets, there are editors for editing Partial Views, and so forth. + +## [Content](../data/defining-content/) + +Content is what you find in the Content section. Each item in the tree is called a **content node**. Each content node in the content tree consists of different fields, and each of them is defined by a Document Type. + +![Content](images/highlight-content-v14.png) + +[Read more about Content](../data/defining-content/) + +## Document Type + +Document Types define the types of content nodes that backoffice users can create in the content tree. Each Document Type contains different properties. Each property has a specific Data Type for example text or number. + +![Document Types](images/document-types-v14.png) + +### Properties + +Every Document Type has properties. These are the fields that the content editor is allowed to edit for the content node. + +![Document Type Properties](images/document-type-properties-v14.png) + +### [Data Type](../data/data-types/) + +Each Document Type property has a Data Type that defines the type of input of that property. Data Types reference a Property Editor and are configured in the Umbraco backoffice in the Settings section. A Data Type can be something basic (text string, number, true/false) or more complex (multi-node tree picker, image cropper, etc). + +![Data Types](images/data-types-v14.png) + +[Read more about Data Types](../data/data-types/) + +### [Property Editors](property-editors/) + +A property editor is a view used by Data Types to insert content into Umbraco. An example of a property editor is the _Textarea_. It's possible to have many Textarea Data Types with different settings that all use the Textarea property editor. + +![Property Editor](images/property-editor-v14.png) + +[Read more about Property Editors](property-editors/) + +## [Media](../data/creating-media/) + +Media items are used to store assets like images and video within the Media section and can be referenced from your content. + +![Media](images/Media-v14.png) + +[Read more about Media](../data/creating-media/) + +### Media Types + +Media Types are similar to Document Types in Umbraco, except they are specifically for media items in the Media section. + +Umbraco includes the following default Media Types - **Article**, **Audio**, **File**, **Folder**, **Image**, **Vector Graphics (SVG)**, and **Video**. + +![Media Types](images/Media-Type-v14.png) + +## [Members](../data/members.md) + +A member is someone who has access to signup, register, and login into your **public website** and is not to be confused with Users. + +![Members](images/Members-v14.png) + +[Read more about Members](../data/members.md) + +### Member Types + +Similar to a Document Type and a Media Type. You are able to define custom properties to store on a member such as Twitter username or website URL. + +![Member Types](images/Member-Types-v14.png) + +## [Templates](../design/templates/) + +A Template is where you define the HTML markup of your website and also where you output the data from your content nodes. + +![Templates](images/template-v14.png) + +[Read more about Templates](../design/templates/) + +## Packages + +A package is the Umbraco term for an add-on or plugin used to extend the core functionalities in Umbraco. The packages can be found on the [Umbraco Marketplace](https://marketplace.umbraco.com/), and the can also be browsed directly in the backoffice of the Umbraco CMS. + +![Packages](images/packages-v14.png) + +## Users + +A user is someone who has access to the **Umbraco backoffice** and is not to be confused with Members. When Umbraco has been installed a user will automatically be generated with the login (email) and password entered during installation. Users can be created, edited, and managed in the User section. + +![Users](images/Users-v14.png) + +## [Document Blueprints](document-blueprints.md) + +Document Blueprint provide a blueprint for content nodes based on an existing node. + +![Document Blueprint](images/document-blueprint-v14.png) diff --git a/16/umbraco-cms/fundamentals/backoffice/document-blueprints.md b/16/umbraco-cms/fundamentals/backoffice/document-blueprints.md new file mode 100644 index 00000000000..66e2b9ee188 --- /dev/null +++ b/16/umbraco-cms/fundamentals/backoffice/document-blueprints.md @@ -0,0 +1,90 @@ +--- +description: >- + In this article you can learn about how to create and use Document Blueprints + in Umbraco. +--- + +# Document Blueprints + +{% hint style="info" %} +Document Blueprints was previously called Content Templates. +{% endhint %} + +## Document Blueprints Overview + +Document Blueprints allows a content editor to create a blueprint for new content nodes based on an existing node. + +### Create - Method 1 + +{% hint style="warning" %} +Before following this method you should have some [content](../data/defining-content/#3.-creating-the-content) created beforehand. +{% endhint %} + +Select a **Content node** from the **Content** menu: + +![Content-Menu](images/content-menu.png) + +Click **...** next to the Content node and select the **Create Document Blueprint** option. + +![Action Button](images/action-menu.png) + +Give your document blueprint a **Name**: + +![Document Blueprint Name Field](images/Name-Content-Template.png) + +Click the **Save** button and if the creation was successful, you will see a success notification: + +![Create Button](images/Save-Content-Template.png) + +The new document blueprint will be created in **Document Blueprints** node of the **Settings** tree: + +![New Document Blueprint](images/Find-Content-Template.png) + +{% hint style="info" %} +Refresh your browser, if you do not see the new document blueprint in the **Document Blueprints** folder. +{% endhint %} + +### Create - Method 2 + +Go to the **Settings** section: + +![Settings Menu](images/Settings-Menu.png) + +Click **...** next to the **Document Blueprints** tree and select the **Create Document Blueprint** menu item: + +![Create Document Blueprint](images/Create-Content-Template.png) + +Select the Document Type you want to create a document blueprint for: + +![Select Content Type](images/Content-Type.png) + +{% hint style="warning" %} +You can create document blueprints only from **Document Types** or **Document Types with Templates** +{% endhint %} + +Give your document blueprint a **Name** and click the **Save** button: + +![Document Blueprint Name Field](images/Save-Template.png) + +The new document blueprint will be created in **Document Blueprints** folder of the **Settings** tree: + +![New Document Blueprint](images/Find-Template.png) + +### Edit + +To edit an existing document blueprint, select a document blueprint from the **Document Blueprints** folder of the **Settings** tree. When you have finished editing click the **Save** button: + +![Edit Document Blueprint](images/Edit-Content-Template.png) + +### Use + +Once you have created a document blueprint, you can use the template to create new content nodes. To use a document blueprint, Click **...** next to the **Content** tree and select **Create**: + +![Create From Template](images/Create-From-Template.png) + +When you click on a Document Type that has a document blueprint, you will see two options: + +* Create a new node based on a document blueprint +* Create a blank one + +![Select Template](images/Select-Template.png) diff --git a/16/umbraco-cms/fundamentals/backoffice/images/01-Content-Menu.png b/16/umbraco-cms/fundamentals/backoffice/images/01-Content-Menu.png new file mode 100644 index 00000000000..fd1aa38c909 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/images/01-Content-Menu.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/images/02-Actions-Menu.png b/16/umbraco-cms/fundamentals/backoffice/images/02-Actions-Menu.png new file mode 100644 index 00000000000..9a4c63c5b7a Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/images/02-Actions-Menu.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/images/03-Name-Content-Template.png b/16/umbraco-cms/fundamentals/backoffice/images/03-Name-Content-Template.png new file mode 100644 index 00000000000..30bd53bd31c Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/images/03-Name-Content-Template.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/images/04-Save-Content-Template.png b/16/umbraco-cms/fundamentals/backoffice/images/04-Save-Content-Template.png new file mode 100644 index 00000000000..92bda986ff2 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/images/04-Save-Content-Template.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/images/05-Find-Content-Template.png b/16/umbraco-cms/fundamentals/backoffice/images/05-Find-Content-Template.png new file mode 100644 index 00000000000..a27e7fccade Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/images/05-Find-Content-Template.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/images/06-Edit-Content-Template.png b/16/umbraco-cms/fundamentals/backoffice/images/06-Edit-Content-Template.png new file mode 100644 index 00000000000..28fc44c2d47 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/images/06-Edit-Content-Template.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/images/07-Settings-Menu.png b/16/umbraco-cms/fundamentals/backoffice/images/07-Settings-Menu.png new file mode 100644 index 00000000000..b337fcbdd64 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/images/07-Settings-Menu.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/images/08-Create-Content-Template.png b/16/umbraco-cms/fundamentals/backoffice/images/08-Create-Content-Template.png new file mode 100644 index 00000000000..1e02b91ac87 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/images/08-Create-Content-Template.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/images/09-Select-Content-Type.png b/16/umbraco-cms/fundamentals/backoffice/images/09-Select-Content-Type.png new file mode 100644 index 00000000000..1667a1004e1 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/images/09-Select-Content-Type.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/images/10-Save-Template.png b/16/umbraco-cms/fundamentals/backoffice/images/10-Save-Template.png new file mode 100644 index 00000000000..3eacba37b32 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/images/10-Save-Template.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/images/11-Find-Template.png b/16/umbraco-cms/fundamentals/backoffice/images/11-Find-Template.png new file mode 100644 index 00000000000..ed641e4ff90 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/images/11-Find-Template.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/images/12-Create-From-Template.png b/16/umbraco-cms/fundamentals/backoffice/images/12-Create-From-Template.png new file mode 100644 index 00000000000..147b376c44c Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/images/12-Create-From-Template.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/images/13-Select-Template.png b/16/umbraco-cms/fundamentals/backoffice/images/13-Select-Template.png new file mode 100644 index 00000000000..904351ebe5e Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/images/13-Select-Template.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/images/Add-ons.png b/16/umbraco-cms/fundamentals/backoffice/images/Add-ons.png new file mode 100644 index 00000000000..82c43f933e1 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/images/Add-ons.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/images/Allowing-Variance-on-properties.png b/16/umbraco-cms/fundamentals/backoffice/images/Allowing-Variance-on-properties.png new file mode 100644 index 00000000000..b9cb76264f6 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/images/Allowing-Variance-on-properties.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/images/Assign-Access-Languages.png b/16/umbraco-cms/fundamentals/backoffice/images/Assign-Access-Languages.png new file mode 100644 index 00000000000..e03a4c893e8 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/images/Assign-Access-Languages.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/images/BlockEditorConfigurationProgramatically.png b/16/umbraco-cms/fundamentals/backoffice/images/BlockEditorConfigurationProgramatically.png new file mode 100644 index 00000000000..949ae88fff5 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/images/BlockEditorConfigurationProgramatically.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/images/BlockEditorContentCreated.png b/16/umbraco-cms/fundamentals/backoffice/images/BlockEditorContentCreated.png new file mode 100644 index 00000000000..4edfd5aba87 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/images/BlockEditorContentCreated.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/images/Content-Type.png b/16/umbraco-cms/fundamentals/backoffice/images/Content-Type.png new file mode 100644 index 00000000000..149c0b225f6 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/images/Content-Type.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/images/Content.png b/16/umbraco-cms/fundamentals/backoffice/images/Content.png new file mode 100644 index 00000000000..9ab10d1fe04 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/images/Content.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/images/Create-Content-Template.png b/16/umbraco-cms/fundamentals/backoffice/images/Create-Content-Template.png new file mode 100644 index 00000000000..08207f2a985 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/images/Create-Content-Template.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/images/Create-From-Template.png b/16/umbraco-cms/fundamentals/backoffice/images/Create-From-Template.png new file mode 100644 index 00000000000..c250d8ca953 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/images/Create-From-Template.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/images/Developer.png b/16/umbraco-cms/fundamentals/backoffice/images/Developer.png new file mode 100644 index 00000000000..ce155b66cc5 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/images/Developer.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/images/Dropdown-DataType.png b/16/umbraco-cms/fundamentals/backoffice/images/Dropdown-DataType.png new file mode 100644 index 00000000000..6785734474b Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/images/Dropdown-DataType.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/images/Edit-Content-Template.png b/16/umbraco-cms/fundamentals/backoffice/images/Edit-Content-Template.png new file mode 100644 index 00000000000..37e7d82f950 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/images/Edit-Content-Template.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/images/English_get.png b/16/umbraco-cms/fundamentals/backoffice/images/English_get.png new file mode 100644 index 00000000000..70e3950dff0 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/images/English_get.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/images/Find-Content-Template.png b/16/umbraco-cms/fundamentals/backoffice/images/Find-Content-Template.png new file mode 100644 index 00000000000..8020ec1ba39 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/images/Find-Content-Template.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/images/Find-Template.png b/16/umbraco-cms/fundamentals/backoffice/images/Find-Template.png new file mode 100644 index 00000000000..32ec568f82b Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/images/Find-Template.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/images/Forms.png b/16/umbraco-cms/fundamentals/backoffice/images/Forms.png new file mode 100644 index 00000000000..6dfdf03de1b Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/images/Forms.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/images/Help.png b/16/umbraco-cms/fundamentals/backoffice/images/Help.png new file mode 100644 index 00000000000..bf96869ef08 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/images/Help.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/images/Infinite-editing.gif b/16/umbraco-cms/fundamentals/backoffice/images/Infinite-editing.gif new file mode 100644 index 00000000000..b24121d92ba Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/images/Infinite-editing.gif differ diff --git a/16/umbraco-cms/fundamentals/backoffice/images/Media-Type-v14.png b/16/umbraco-cms/fundamentals/backoffice/images/Media-Type-v14.png new file mode 100644 index 00000000000..bd5e047e924 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/images/Media-Type-v14.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/images/Media-v14.png b/16/umbraco-cms/fundamentals/backoffice/images/Media-v14.png new file mode 100644 index 00000000000..a65bda6939d Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/images/Media-v14.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/images/Media.png b/16/umbraco-cms/fundamentals/backoffice/images/Media.png new file mode 100644 index 00000000000..77f0746531b Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/images/Media.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/images/Member-Types-v14.png b/16/umbraco-cms/fundamentals/backoffice/images/Member-Types-v14.png new file mode 100644 index 00000000000..0a7147a93d6 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/images/Member-Types-v14.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/images/Members-v14.png b/16/umbraco-cms/fundamentals/backoffice/images/Members-v14.png new file mode 100644 index 00000000000..9ee60709619 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/images/Members-v14.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/images/Members.png b/16/umbraco-cms/fundamentals/backoffice/images/Members.png new file mode 100644 index 00000000000..d805f89a211 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/images/Members.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/images/Name-Content-Template.png b/16/umbraco-cms/fundamentals/backoffice/images/Name-Content-Template.png new file mode 100644 index 00000000000..d04b58c18e7 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/images/Name-Content-Template.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/images/Save-Content-Template.png b/16/umbraco-cms/fundamentals/backoffice/images/Save-Content-Template.png new file mode 100644 index 00000000000..84b849c80e3 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/images/Save-Content-Template.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/images/Save-Template.png b/16/umbraco-cms/fundamentals/backoffice/images/Save-Template.png new file mode 100644 index 00000000000..3d3efe9bdc8 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/images/Save-Template.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/images/Select-Template.png b/16/umbraco-cms/fundamentals/backoffice/images/Select-Template.png new file mode 100644 index 00000000000..ffce97142d4 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/images/Select-Template.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/images/Settings-Menu.png b/16/umbraco-cms/fundamentals/backoffice/images/Settings-Menu.png new file mode 100644 index 00000000000..fe0112dcef3 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/images/Settings-Menu.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/images/Settings.png b/16/umbraco-cms/fundamentals/backoffice/images/Settings.png new file mode 100644 index 00000000000..3c4a206e0a2 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/images/Settings.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/images/Users-v14.png b/16/umbraco-cms/fundamentals/backoffice/images/Users-v14.png new file mode 100644 index 00000000000..7e73da5e53b Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/images/Users-v14.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/images/Users.png b/16/umbraco-cms/fundamentals/backoffice/images/Users.png new file mode 100644 index 00000000000..d589c44c76f Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/images/Users.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/images/action-menu.png b/16/umbraco-cms/fundamentals/backoffice/images/action-menu.png new file mode 100644 index 00000000000..a33cfb09ec6 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/images/action-menu.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/images/adding-a-language.png b/16/umbraco-cms/fundamentals/backoffice/images/adding-a-language.png new file mode 100644 index 00000000000..e5bb43dc9c0 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/images/adding-a-language.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/images/allow-variance.png b/16/umbraco-cms/fundamentals/backoffice/images/allow-variance.png new file mode 100644 index 00000000000..5fed09efd7c Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/images/allow-variance.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/images/allow-variance2.png b/16/umbraco-cms/fundamentals/backoffice/images/allow-variance2.png new file mode 100644 index 00000000000..65f9a34c499 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/images/allow-variance2.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/images/allow-variance_v10.png b/16/umbraco-cms/fundamentals/backoffice/images/allow-variance_v10.png new file mode 100644 index 00000000000..828f73cdceb Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/images/allow-variance_v10.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/images/backoffice-login.png b/16/umbraco-cms/fundamentals/backoffice/images/backoffice-login.png new file mode 100644 index 00000000000..736a44a2f3e Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/images/backoffice-login.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/images/content-menu.png b/16/umbraco-cms/fundamentals/backoffice/images/content-menu.png new file mode 100644 index 00000000000..29e78f3e879 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/images/content-menu.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/images/data-types-v14.png b/16/umbraco-cms/fundamentals/backoffice/images/data-types-v14.png new file mode 100644 index 00000000000..bc854bff954 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/images/data-types-v14.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/images/document-blueprint-v14.png b/16/umbraco-cms/fundamentals/backoffice/images/document-blueprint-v14.png new file mode 100644 index 00000000000..1e5e38f5fe2 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/images/document-blueprint-v14.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/images/document-type-properties-v14.png b/16/umbraco-cms/fundamentals/backoffice/images/document-type-properties-v14.png new file mode 100644 index 00000000000..1a3a99baaca Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/images/document-type-properties-v14.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/images/document-types-v14.png b/16/umbraco-cms/fundamentals/backoffice/images/document-types-v14.png new file mode 100644 index 00000000000..f0f76e63f64 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/images/document-types-v14.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/images/expand-node-v14.png b/16/umbraco-cms/fundamentals/backoffice/images/expand-node-v14.png new file mode 100644 index 00000000000..de3b284929b Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/images/expand-node-v14.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/images/has-unpublished-version.svg b/16/umbraco-cms/fundamentals/backoffice/images/has-unpublished-version.svg new file mode 100644 index 00000000000..fd3509507a6 --- /dev/null +++ b/16/umbraco-cms/fundamentals/backoffice/images/has-unpublished-version.svg @@ -0,0 +1 @@ + diff --git a/16/umbraco-cms/fundamentals/backoffice/images/highlight-content-node-v14.png b/16/umbraco-cms/fundamentals/backoffice/images/highlight-content-node-v14.png new file mode 100644 index 00000000000..45db35b3703 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/images/highlight-content-node-v14.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/images/highlight-content-v14.png b/16/umbraco-cms/fundamentals/backoffice/images/highlight-content-v14.png new file mode 100644 index 00000000000..8646513184d Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/images/highlight-content-v14.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/images/highlight-dashboard-v14.png b/16/umbraco-cms/fundamentals/backoffice/images/highlight-dashboard-v14.png new file mode 100644 index 00000000000..39adf28fa20 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/images/highlight-dashboard-v14.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/images/highlight-sections-v14.png b/16/umbraco-cms/fundamentals/backoffice/images/highlight-sections-v14.png new file mode 100644 index 00000000000..52eb170b799 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/images/highlight-sections-v14.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/images/highlight-tree-v14.png b/16/umbraco-cms/fundamentals/backoffice/images/highlight-tree-v14.png new file mode 100644 index 00000000000..aa086ceb376 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/images/highlight-tree-v14.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/images/invariant-property-locked.png b/16/umbraco-cms/fundamentals/backoffice/images/invariant-property-locked.png new file mode 100644 index 00000000000..7159ba8b58a Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/images/invariant-property-locked.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/images/is-container.svg b/16/umbraco-cms/fundamentals/backoffice/images/is-container.svg new file mode 100644 index 00000000000..b43024c3d0e --- /dev/null +++ b/16/umbraco-cms/fundamentals/backoffice/images/is-container.svg @@ -0,0 +1 @@ + diff --git a/16/umbraco-cms/fundamentals/backoffice/images/languages.png b/16/umbraco-cms/fundamentals/backoffice/images/languages.png new file mode 100644 index 00000000000..3820e0cae49 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/images/languages.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/images/languages_v10.png b/16/umbraco-cms/fundamentals/backoffice/images/languages_v10.png new file mode 100644 index 00000000000..d8e66c08b24 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/images/languages_v10.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/images/locked-v14.png b/16/umbraco-cms/fundamentals/backoffice/images/locked-v14.png new file mode 100644 index 00000000000..22f7bee6520 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/images/locked-v14.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/images/locked.svg b/16/umbraco-cms/fundamentals/backoffice/images/locked.svg new file mode 100644 index 00000000000..782eb1006fb --- /dev/null +++ b/16/umbraco-cms/fundamentals/backoffice/images/locked.svg @@ -0,0 +1 @@ + diff --git a/16/umbraco-cms/fundamentals/backoffice/images/login-backoffice-login-v14.png b/16/umbraco-cms/fundamentals/backoffice/images/login-backoffice-login-v14.png new file mode 100644 index 00000000000..dc92982be28 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/images/login-backoffice-login-v14.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/images/mntp.png b/16/umbraco-cms/fundamentals/backoffice/images/mntp.png new file mode 100644 index 00000000000..47be9e4ce2d Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/images/mntp.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/images/mntp_node_type.png b/16/umbraco-cms/fundamentals/backoffice/images/mntp_node_type.png new file mode 100644 index 00000000000..82e3a4f40e9 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/images/mntp_node_type.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/images/mntp_node_type_dynamic_root.png b/16/umbraco-cms/fundamentals/backoffice/images/mntp_node_type_dynamic_root.png new file mode 100644 index 00000000000..c9cdfa8254e Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/images/mntp_node_type_dynamic_root.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/images/mntp_node_type_dynamic_root_origin.png b/16/umbraco-cms/fundamentals/backoffice/images/mntp_node_type_dynamic_root_origin.png new file mode 100644 index 00000000000..401c38157a5 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/images/mntp_node_type_dynamic_root_origin.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/images/mntp_node_type_dynamic_root_overview.png b/16/umbraco-cms/fundamentals/backoffice/images/mntp_node_type_dynamic_root_overview.png new file mode 100644 index 00000000000..d482460dd58 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/images/mntp_node_type_dynamic_root_overview.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/images/mntp_node_type_dynamic_root_steps.png b/16/umbraco-cms/fundamentals/backoffice/images/mntp_node_type_dynamic_root_steps.png new file mode 100644 index 00000000000..e1e0ad3db83 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/images/mntp_node_type_dynamic_root_steps.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/images/packages-v14.png b/16/umbraco-cms/fundamentals/backoffice/images/packages-v14.png new file mode 100644 index 00000000000..e1ab01a2da0 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/images/packages-v14.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/images/property-editor-v14.png b/16/umbraco-cms/fundamentals/backoffice/images/property-editor-v14.png new file mode 100644 index 00000000000..6553f609e08 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/images/property-editor-v14.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/images/protected.svg b/16/umbraco-cms/fundamentals/backoffice/images/protected.svg new file mode 100644 index 00000000000..3aab0c725f5 --- /dev/null +++ b/16/umbraco-cms/fundamentals/backoffice/images/protected.svg @@ -0,0 +1 @@ + diff --git a/16/umbraco-cms/fundamentals/backoffice/images/sections-highlight-sections.png b/16/umbraco-cms/fundamentals/backoffice/images/sections-highlight-sections.png new file mode 100644 index 00000000000..246bb98a467 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/images/sections-highlight-sections.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/images/sections-highlight-sections2.png b/16/umbraco-cms/fundamentals/backoffice/images/sections-highlight-sections2.png new file mode 100644 index 00000000000..3244d145125 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/images/sections-highlight-sections2.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/images/template-v14.png b/16/umbraco-cms/fundamentals/backoffice/images/template-v14.png new file mode 100644 index 00000000000..7330f070a60 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/images/template-v14.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/images/timeout-screen.jpg b/16/umbraco-cms/fundamentals/backoffice/images/timeout-screen.jpg new file mode 100644 index 00000000000..c57857c7a3d Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/images/timeout-screen.jpg differ diff --git a/16/umbraco-cms/fundamentals/backoffice/images/umbraco7-6_dashboard.jpg b/16/umbraco-cms/fundamentals/backoffice/images/umbraco7-6_dashboard.jpg new file mode 100644 index 00000000000..fdac204aafe Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/images/umbraco7-6_dashboard.jpg differ diff --git a/16/umbraco-cms/fundamentals/backoffice/images/umbraco7-6_login.jpg b/16/umbraco-cms/fundamentals/backoffice/images/umbraco7-6_login.jpg new file mode 100644 index 00000000000..6627a757b7c Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/images/umbraco7-6_login.jpg differ diff --git a/16/umbraco-cms/fundamentals/backoffice/images/umbraco7-6_sections.jpg b/16/umbraco-cms/fundamentals/backoffice/images/umbraco7-6_sections.jpg new file mode 100644 index 00000000000..b1dc1cd708e Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/images/umbraco7-6_sections.jpg differ diff --git a/16/umbraco-cms/fundamentals/backoffice/images/umbraco7-6_tree.jpg b/16/umbraco-cms/fundamentals/backoffice/images/umbraco7-6_tree.jpg new file mode 100644 index 00000000000..6de9e5a00d6 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/images/umbraco7-6_tree.jpg differ diff --git a/16/umbraco-cms/fundamentals/backoffice/images/update-dropdown-options.gif b/16/umbraco-cms/fundamentals/backoffice/images/update-dropdown-options.gif new file mode 100644 index 00000000000..475d8384b68 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/images/update-dropdown-options.gif differ diff --git a/16/umbraco-cms/fundamentals/backoffice/images/v8-01-Content-Menu.png b/16/umbraco-cms/fundamentals/backoffice/images/v8-01-Content-Menu.png new file mode 100644 index 00000000000..dd375cca662 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/images/v8-01-Content-Menu.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/images/v8-02-Actions-Menu.png b/16/umbraco-cms/fundamentals/backoffice/images/v8-02-Actions-Menu.png new file mode 100644 index 00000000000..4f038a5eb3f Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/images/v8-02-Actions-Menu.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/images/v8-03-Name-Content-Template.png b/16/umbraco-cms/fundamentals/backoffice/images/v8-03-Name-Content-Template.png new file mode 100644 index 00000000000..6a41f9aa5d6 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/images/v8-03-Name-Content-Template.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/images/v8-04-Save-Content-Template.png b/16/umbraco-cms/fundamentals/backoffice/images/v8-04-Save-Content-Template.png new file mode 100644 index 00000000000..f7a15cb548e Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/images/v8-04-Save-Content-Template.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/images/v8-05-Find-Content-Template.png b/16/umbraco-cms/fundamentals/backoffice/images/v8-05-Find-Content-Template.png new file mode 100644 index 00000000000..244f5d796b7 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/images/v8-05-Find-Content-Template.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/images/v8-06-Edit-Content-Template.png b/16/umbraco-cms/fundamentals/backoffice/images/v8-06-Edit-Content-Template.png new file mode 100644 index 00000000000..5cdb17c7422 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/images/v8-06-Edit-Content-Template.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/images/v8-07-Settings-Menu.png b/16/umbraco-cms/fundamentals/backoffice/images/v8-07-Settings-Menu.png new file mode 100644 index 00000000000..e1f6910d596 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/images/v8-07-Settings-Menu.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/images/v8-08-Create-Content-Template.png b/16/umbraco-cms/fundamentals/backoffice/images/v8-08-Create-Content-Template.png new file mode 100644 index 00000000000..2c610e04b97 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/images/v8-08-Create-Content-Template.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/images/v8-09-Select-Content-Type.png b/16/umbraco-cms/fundamentals/backoffice/images/v8-09-Select-Content-Type.png new file mode 100644 index 00000000000..ca0480f2704 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/images/v8-09-Select-Content-Type.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/images/v8-10-Save-Template.png b/16/umbraco-cms/fundamentals/backoffice/images/v8-10-Save-Template.png new file mode 100644 index 00000000000..1d323d33295 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/images/v8-10-Save-Template.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/images/v8-11-Find-Template.png b/16/umbraco-cms/fundamentals/backoffice/images/v8-11-Find-Template.png new file mode 100644 index 00000000000..a7cde6aabef Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/images/v8-11-Find-Template.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/images/v8-12-Create-From-Template.png b/16/umbraco-cms/fundamentals/backoffice/images/v8-12-Create-From-Template.png new file mode 100644 index 00000000000..3b8be3c5dca Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/images/v8-12-Create-From-Template.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/images/v8-13-Select-Template.png b/16/umbraco-cms/fundamentals/backoffice/images/v8-13-Select-Template.png new file mode 100644 index 00000000000..251ae8eded3 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/images/v8-13-Select-Template.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/images/varying-content.png b/16/umbraco-cms/fundamentals/backoffice/images/varying-content.png new file mode 100644 index 00000000000..41247e7065e Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/images/varying-content.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/images/varying-content_v10.png b/16/umbraco-cms/fundamentals/backoffice/images/varying-content_v10.png new file mode 100644 index 00000000000..79efa211c19 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/images/varying-content_v10.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/images/varying-properties.png b/16/umbraco-cms/fundamentals/backoffice/images/varying-properties.png new file mode 100644 index 00000000000..8c01350fe7c Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/images/varying-properties.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/images/varying-properties_v10.png b/16/umbraco-cms/fundamentals/backoffice/images/varying-properties_v10.png new file mode 100644 index 00000000000..fd949f8dad9 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/images/varying-properties_v10.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/login.md b/16/umbraco-cms/fundamentals/backoffice/login.md new file mode 100644 index 00000000000..287dd04a98a --- /dev/null +++ b/16/umbraco-cms/fundamentals/backoffice/login.md @@ -0,0 +1,280 @@ +--- +description: >- + In this article you can learn the various ways of customizing the Umbraco + backoffice login screen and form. +--- + +# Login + +To access the backoffice, you will need to login. You can do this by adding `/umbraco` at the end of your website URL, for example `http://mywebsite.com/umbraco`. + +You will be presented with a login form similar to this: + +![Login screen](images/login-backoffice-login-v14.png) + +The **login** screen contains a short greeting, a **login form** and an optional **Forgotten password** link. + +Below, you will find instructions on how to customize the login screen. + +## Greeting + +The login screen features a greeting text: The "Welcome" headline. This can be personalized by overriding the existing language translation keys. + +To do this follow the steps below: + +1. Register a 'localization' manifest for the default language of your Umbraco site, (usually en-US) to override the greetings. + +{% code title="App_Plugins/Login/umbraco-package.json" lineNumbers="true" %} +```json +{ + "alias": "login.extensions", + "name": "Login extensions", + "version": "1.0.0", + "allowPublicAccess": true, + "extensions": [ + { + "type": "localization", + "alias": "Login.Localize.EnUS", + "name": "English", + "js": "/App_Plugins/Login/en-us.js", + "meta": { + "culture": "en-US" + } + } + ] +} +``` +{% endcode %} + +2. Add an `en-us.js` file containing the following: + +{% code title="App_Plugins/Login/en-us.js" %} +```javascript +export default { + auth: { + instruction: "Log in again to continue", + greeting0: "Is is Sunday", + greeting1: "Is is Monday", + greeting2: "Is is Tuesday", + greeting3: "Is is Wednesday", + greeting4: "Is is Thursday", + greeting5: "Is is Friday", + greeting6: "Is is Saturday", + } +} +``` +{% endcode %} + +This will override the default greetings with the ones you provide. The login screen will now display "It is Sunday" instead of "Welcome" for example. + +{% hint style="info" %} +The login screen has its own set of localization files independent of the rest of the Backoffice. You can read more about Backoffice localization in the [UI Localization](../../customizing/foundation/localization.md) article. +{% endhint %} + +You can customize other text on the login screen as well. First, grab the default values and keys from the [en.ts](https://github.com/umbraco/Umbraco-CMS/blob/contrib/src/Umbraco.Web.UI.Login/src/localization/lang/en.ts) in the Umbraco CMS GitHub repository. Thereafter copy the ones you want to translate into `~/App_Plugins/Login/umbraco-package.json` file. + +## Password reset + +The **Forgotten password?** link allows your backoffice users to reset their password. To use this feature, you will need to add the following key to the `Umbraco.Cms.Security` section in the `appsettings.json` file: + +```json +"Umbraco": { + "CMS": { + "Security": { + "AllowPasswordReset": true + } + } +} +``` + +Set it to `true` to enable the password reset feature, and `false` to disable the feature. + +You will also need to configure a Simple Mail Transfer Protocol (SMTP) server in your `appsettings.json` file. When you get a successful result on the SMTP configuration when running a health check in the backoffice, you are good to go! + +An example: + +```json +"Umbraco": { + "CMS": { + "Global": { + "Id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", + "Smtp": { + "From": "noreply@test.com", + "Host": "127.0.0.1", + "Username": "username", + "Password": "password" + } + } + } +} +``` + +## Custom background image and logo + +It is possible to customize the background image and the logo for the backoffice login screen by adding the `"Content"` section in the `appsettings.json` file: + +```json +"Umbraco": { + "CMS": { + "Content": { + "LoginBackgroundImage": "../myImagesFolder/myLogin.jpg", + "LoginLogoImage": "../myImagesFolder/myLogo.svg", + "LoginLogoImageAlternative": "../myImagesFolder/myLogo.svg" + } + } +} +``` + +The `LoginBackgroundImage`, `LoginLogoImage`, and `LoginLogoImageAlternative` are referenced from the `/wwwroot/umbraco/` folder. + +The `LoginLogoImage` is displayed on top of the `LoginBackgroundImage` and the `LoginLogoImageAlternative` is displayed when the `LoginLogoImage` is not available, for example on small resolutions. + +## Custom CSS + +You can also customize the login screen by adding a custom CSS file. To do this, you will need to add a new file inside the `~/App_Plugins` folder, for example `~/App_Plugins/Login/my-custom-login-screen.css`. + +You can then add your custom CSS to the file: + +```css +:root { + --umb-login-curves-color: rgba(0, 0, 0, 0.1); +} +``` + +This will change the color of the SVG graphics (curves) shown on the login screen. You can also hide the curves by adding the following CSS: + +```css +:root { + --umb-login-curves-display: none; +} +``` + +### Load the custom CSS file + +To tell Umbraco about your custom CSS file, you will need to add a `umbraco-package.json` file. The `umbraco-package.json` file should look like this: + +```json +{ + "alias": "login.extensions", + "name": "Login extensions", + "version": "1.0.0", + "allowPublicAccess": true, + "extensions": [ + { + "type": "appEntryPoint", + "alias": "MyCustomLoginScreen", + "name": "My Custom Login Screen", + "js": "/App_Plugins/Login/my-custom-login-screen.js" + } + ] +} +``` + +Next add a JavaScript file, for example `~/App_Plugins/Login/my-custom-login-screen.js`, and add the following code to load the custom CSS file: + +```javascript +const link = document.createElement('link'); +link.rel = 'stylesheet'; +link.href = '/App_Plugins/Login/my-custom-login-screen.css'; +document.head.appendChild(link); +``` + +This will load the custom CSS file into Umbraco. + +{% hint style="warning" %} +Be aware that the custom CSS file will be loaded on all Umbraco screens, not only the login screen. +{% endhint %} + +### Custom CSS properties reference + +The following CSS properties are available for customization: + +| CSS Property | Description | Default Value | +| ---------------------------------------- | ---------------------------------------------- | ------------------------------------------------------------------------------------------ | +| `--umb-login-background` | The background of the layout | `#f4f4f4` | +| `--umb-login-primary-color` | The color of the headline | `#283a97` | +| `--umb-login-text-color` | The color of the text | `#000` | +| `--umb-login-header-font-size` | The font-size of the headline | `3rem` | +| `--umb-login-header-font-size-large` | The font-size of the headline on large screens | `4rem` | +| `--umb-login-header-secondary-font-size` | The font-size of the secondary headline | `2.4rem` | +| `--umb-login-image` | The background of the image wrapper | The value of the [LoginBackgroundImage](login.md#custom-background-image-and-logo) setting | +| `--umb-login-image-display` | The display of the image wrapper | `flex` | +| `--umb-login-image-border-radius` | The border-radius of the image wrapper | `38px` | +| `--umb-login-content-background` | The background of the content wrapper | `none` | +| `--umb-login-content-display` | The display of the content wrapper | `flex` | +| `--umb-login-content-width` | The width of the content wrapper | `100%` | +| `--umb-login-content-height` | The height of the content wrapper | `100%` | +| `--umb-login-content-border-radius` | The border-radius of the content wrapper | `0` | +| `--umb-login-align-items` | The align-items of the main wrapper | `unset` | +| `--umb-login-button-border-radius` | The border-radius of the buttons | `45px` | +| `--umb-login-curves-color` | The color of the curves | `#f5c1bc` | +| `--umb-login-curves-display` | The display of the curves | `inline` | + +The CSS custom properties may change in future versions of Umbraco. You can always find the latest values in the [login layout element](https://github.com/umbraco/Umbraco-CMS/blob/v14/dev/src/Umbraco.Web.UI.Login/src/components/layouts/auth-layout.element.ts) in the Umbraco CMS GitHub repository. + +## The Time Out Screen + +![Time out screen](images/timeout-screen.jpg) + +The time out screen is displayed when the user has been inactive for a certain amount of time. The screen resembles the login screen in many ways and the two are sometimes confused. The most notable difference is that the time out screen does not have a login form. It only has a message and a button to log in again with Umbraco. + +If you have added more than one login provider, the users will also see this screen first. This is because they need to choose which provider to use first. In that case, the screen is also referred to as the **Choose provider screen**. + +You can customize the time out screen in the same way as the login screen. The time out screen uses the same localization files as the rest of the Backoffice and **not** those of the login screen. The notable difference is that the time out screen is scoped to the `login` section. The login screen is scoped to the `auth` section of the localization files. + +### Greeting + +To update the greeting message on this screen, you will have to change the section to `login`: + +{% code title="App_Plugins/Login/umbraco-package.json" lineNumbers="true" %} +```json +{ + "alias": "login.extensions", + "name": "Login extensions", + "version": "1.0.0", + "allowPublicAccess": true, + "extensions": [ + { + "type": "localization", + "alias": "Login.Localize.EnUS", + "name": "English", + "js": "/App_Plugins/Login/en-us.js", + "meta": { + "culture": "en-US" + } + } + ] +} +``` +{% endcode %} + +The `en-us.js` file should contain the following: + +{% code title="App_Plugins/Login/en-us.js" %} +```javascript +export default { + auth: { + instruction: "Log in again to continue", + greeting0: "Is is Sunday", + greeting1: "Is is Monday", + greeting2: "Is is Tuesday", + greeting3: "Is is Wednesday", + greeting4: "Is is Thursday", + greeting5: "Is is Friday", + greeting6: "Is is Saturday", + } +} +``` +{% endcode %} + +The `instruction` key is shown when the user has timed out, and the `greeting0..6` keys are shown when the user has to choose a login provider. + +### Image + +You can update the image on the time out screen through a custom CSS variable. The default value is `--umb-login-image` and it is set to the same value as the login screen. You can override this value in your custom CSS file: + +```css +:root { + --umb-login-image: url(../myImagesFolder/myTimeout.jpg); +} +``` diff --git a/16/umbraco-cms/fundamentals/backoffice/logviewer.md b/16/umbraco-cms/fundamentals/backoffice/logviewer.md new file mode 100644 index 00000000000..c4ecca6f28c --- /dev/null +++ b/16/umbraco-cms/fundamentals/backoffice/logviewer.md @@ -0,0 +1,163 @@ +--- +description: Information on using the Umbraco log viewer +--- + +# Log Viewer + +Umbraco ships with a built-in Log Viewer feature. This allows you to filter, view log entries, perform complex search queries, and analyze logs for debugging. You can find the Log viewer in the **Settings** section of the Umbraco backoffice. + +{% embed url="https://youtu.be/PDqIRVygAQ4?t=102" %} +Learn how to use the Log Viewer to read and understand logs for your Umbraco CMS website. +{% endembed %} + +## Benefits + +Ever needed to find all log entries containing the same request ID? Or locate all logs where a property called `Duration` exceeds 1000ms? + +With structured logging and a query language, you can efficiently search and identify log items for specific scenarios. This helps in debugging and finding patterns in your logs, making it easier to resolve issues. + +## Example Queries + +Here are some example queries to help you get started. For more details on the syntax, see the https://github.com/serilog/serilog-filters-expressions project. + +**Find all logs that are from the namespace 'Umbraco.Core'** +`StartsWith(SourceContext, 'Umbraco.Core')` + +**Find all logs that have the property 'Duration' and the duration is greater than 1000ms** +`Has(Duration) and Duration > 1000` + +**Find all logs where the message has localhost in it with SQL like** +`@Message like '%localhost%'` + +## Saved Searches + +If you frequently use a custom query, you can save it for quick access. Type your query in the search box and click the heart icon to save it with a friendly name. Saved queries are stored in the `umbracoLogViewerQuery` table in the database. + +## Implementing Your Own Log Viewer + +Umbraco allows you to implement a customn `ILogViewer` to fetch logs from alternative sources, such as **Azure Table Storage**. + +### Creating a Custom Log Viewer + +To fetch logs from Azure Table Storage, implement the `SerilogLogViewerSourceBase` class from `Umbraco.Cms.Core.Logging.Viewer`. + +{% hint style="info" %} +This implementation requires the `Azure.Data.Tables` NuGet package. +{% endhint %} + +```csharp +using Azure; +using Azure.Data.Tables; +using Serilog.Events; +using Serilog.Formatting.Compact.Reader; +using Serilog.Sinks.AzureTableStorage; +using Umbraco.Cms.Core.Logging.Viewer; +using ITableEntity = Azure.Data.Tables.ITableEntity; + +namespace My.Website; + +public class AzureTableLogViewer : SerilogLogViewerSourceBase +{ + public AzureTableLogViewer(ILogViewerConfig logViewerConfig, Serilog.ILogger serilogLog, ILogLevelLoader logLevelLoader) + : base(logViewerConfig, logLevelLoader, serilogLog) + { + } + + public override bool CanHandleLargeLogs => true; + + // This method will not be called - as we have indicated that this 'CanHandleLargeLogs' + public override bool CheckCanOpenLogs(LogTimePeriod logTimePeriod) => throw new NotImplementedException(); + + protected override IReadOnlyList GetLogs(LogTimePeriod logTimePeriod, ILogFilter filter, int skip, int take) + { + //Replace ACCOUNT_NAME and KEY with your actual Azure Storage Account details. The "Logs" parameter refers to the table name where logs will be stored and retrieved from. + var client = + new TableClient( + "DefaultEndpointsProtocol=https;AccountName=ACCOUNT_NAME;AccountKey=KEY;EndpointSuffix=core.windows.net", + "Logs"); + + // Table storage does not support skip, only take, so the best we can do is to not fetch more entities than we need in total. + // See: https://learn.microsoft.com/en-us/rest/api/storageservices/writing-linq-queries-against-the-table-service#returning-the-top-n-entities for more info. + var requiredEntities = skip + take; + IEnumerable results = client.Query().Take(requiredEntities); + + return results + .Skip(skip) + .Take(take) + .Select(x => LogEventReader.ReadFromString(x.Data)) + // Filter by timestamp to avoid retrieving all logs from the table, preventing memory and performance issues + .Where(evt => evt.Timestamp >= logTimePeriod.StartTime.Date && + evt.Timestamp <= logTimePeriod.EndTime.Date.AddDays(1).AddSeconds(-1)) + .Where(filter.TakeLogEvent) + .ToList(); + } + + public override IReadOnlyList? GetSavedSearches() + { + //This method is optional. If you store saved searches in Azure Table Storage, implement fetching logic here. + return base.GetSavedSearches(); + } + + public override IReadOnlyList? AddSavedSearch(string? name, string? query) + { + //This method is optional. If you store saved searches in Azure Table Storage, implement adding logic here. + return base.AddSavedSearch(name, query); + } + + public override IReadOnlyList? DeleteSavedSearch(string? name, string? query) + { + //This method is optional. If you store saved searches in Azure Table Storage, implement deleting logic here. + return base.DeleteSavedSearch(name, query); + } +} + +public class AzureTableLogEntity : LogEventEntity, ITableEntity +{ + public DateTimeOffset? Timestamp { get; set; } + + public ETag ETag { get; set; } +} +``` + +Azure Table Storage requires entities to implement the `ITableEntity` interface. Since Umbraco’s default log entity does not implement this, a custom entity (`AzureTableLogEntity`) must be created to ensure logs are correctly fetched and stored. + +### Register implementation + +Umbraco needs to be made aware that there is a new implementation of an `ILogViewer` to register. We also need to replace the default JSON LogViewer that we ship in the core of Umbraco. + +```csharp +using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Infrastructure.DependencyInjection; + +namespace My.Website; + +public class LogViewerSavedSearches : IComposer +{ + public void Compose(IUmbracoBuilder builder) => builder.SetLogViewer(); +} +``` + +### Configuring Logging to Azure Table Storage + +With the above two classes, the setup is in place to view logs from an Azure Table. However, logs are not yet persisted into the Azure Table Storage account. To enable persistence, configure the Serilog logging pipeline to store logs in Azure Table Storage. + +* Install `Serilog.Sinks.AzureTableStorage` from NuGet. +* Add a new sink to `appsettings.json` with credentials to persist logs to Azure. + +The following sink needs to be added to the [`Serilog:WriteTo`](https://github.com/serilog/serilog-sinks-azuretablestorage#json-configuration) array. + +```json +{ +"Name": "AzureTableStorage", +"Args": { + "storageTableName": "LogEventEntity", + "formatter": "Serilog.Formatting.Compact.CompactJsonFormatter, Serilog.Formatting.Compact", + "connectionString": "DefaultEndpointsProtocol=https;AccountName=ACCOUNT_NAME;AccountKey=KEY;EndpointSuffix=core.windows.net"} +} +``` + +For more in-depth information about logging and how to configure it, see the [Logging](../code/debugging/logging.md) article. + +### Compact Log Viewer - Desktop App + +[Compact Log Viewer](https://www.microsoft.com/store/apps/9N8RV8LKTXRJ?cid=storebadge\&ocid=badge). A desktop tool is available for viewing and querying JSON log files in the same way as the built-in Log Viewer in Umbraco. diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/README.md b/16/umbraco-cms/fundamentals/backoffice/property-editors/README.md new file mode 100644 index 00000000000..9b7e69cf5ca --- /dev/null +++ b/16/umbraco-cms/fundamentals/backoffice/property-editors/README.md @@ -0,0 +1,36 @@ +--- +description: >- + Learn more about the default property editors that ships with an Umbraco + installation. +--- + +# Property Editors + +A Property Editor is the editor that a Data Type references. A Data Type is defined by a user in the Umbraco backoffice and references a Property Editor. In Umbraco a Property Editor is defined in a JSON manifest file and associated JavaScript files. + +{% hint style="info" %} +**Are you looking for the Grid Layout or Nested Content?** + +The following Property Editors have been removed with the release of Umbraco 14: + +* Grid Layout +* Nested content + +We recommend using the [Block Editor](built-in-umbraco-property-editors/block-editor/) or the [Rich Text Editor Blocks](built-in-umbraco-property-editors/rich-text-editor/blocks.md) instead. +{% endhint %} + +When creating a Data Type, specify the property editor for the Data Type to use by selecting from the "Property editor" list (as shown below). + +![Data Type Definition](../../../../../14/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/images/Media-picker-dataType.png) + +## [Built-in Property Editors in Umbraco](built-in-umbraco-property-editors/) + +Umbraco comes pre-installed with many useful property editors. + +## More information + +* [Customizing Data Types](../../data/data-types/) + +## Tutorials + +* [How to create a custom Property Editor](../../../tutorials/creating-a-property-editor/) diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/block-editor/images/BlockGridEditor_AddContent.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/block-editor/images/BlockGridEditor_AddContent.png new file mode 100644 index 00000000000..b77fcfc1257 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/block-editor/images/BlockGridEditor_AddContent.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/block-editor/images/BlockGridEditor_AddContentInline.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/block-editor/images/BlockGridEditor_AddContentInline.png new file mode 100644 index 00000000000..644ec9d1b1a Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/block-editor/images/BlockGridEditor_AddContentInline.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/block-editor/images/BlockGridEditor_Areas.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/block-editor/images/BlockGridEditor_Areas.png new file mode 100644 index 00000000000..ae25f727ec1 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/block-editor/images/BlockGridEditor_Areas.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/block-editor/images/BlockGridEditor_AreasConfiguration.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/block-editor/images/BlockGridEditor_AreasConfiguration.png new file mode 100644 index 00000000000..27a9a510790 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/block-editor/images/BlockGridEditor_AreasConfiguration.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/block-editor/images/BlockGridEditor_BlockPicker.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/block-editor/images/BlockGridEditor_BlockPicker.png new file mode 100644 index 00000000000..b519f0b755b Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/block-editor/images/BlockGridEditor_BlockPicker.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/block-editor/images/BlockGridEditor_BlockPicker_exsetup.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/block-editor/images/BlockGridEditor_BlockPicker_exsetup.png new file mode 100644 index 00000000000..826d46925e4 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/block-editor/images/BlockGridEditor_BlockPicker_exsetup.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/block-editor/images/BlockGridEditor_Configuration.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/block-editor/images/BlockGridEditor_Configuration.png new file mode 100644 index 00000000000..fe5fec9d0df Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/block-editor/images/BlockGridEditor_Configuration.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/block-editor/images/BlockGridEditor_DataType_Blocks.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/block-editor/images/BlockGridEditor_DataType_Blocks.png new file mode 100644 index 00000000000..5509be958d5 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/block-editor/images/BlockGridEditor_DataType_Blocks.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/block-editor/images/BlockGridEditor_DeleteContent.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/block-editor/images/BlockGridEditor_DeleteContent.png new file mode 100644 index 00000000000..aee9a56f88a Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/block-editor/images/BlockGridEditor_DeleteContent.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/block-editor/images/BlockListEditor_AddContent.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/block-editor/images/BlockListEditor_AddContent.png new file mode 100644 index 00000000000..aeb71eb6354 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/block-editor/images/BlockListEditor_AddContent.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/block-editor/images/BlockListEditor_AddContentInline.jpg b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/block-editor/images/BlockListEditor_AddContentInline.jpg new file mode 100644 index 00000000000..8b20c59740b Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/block-editor/images/BlockListEditor_AddContentInline.jpg differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/block-editor/images/BlockListEditor_BlockPicker.jpg b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/block-editor/images/BlockListEditor_BlockPicker.jpg new file mode 100644 index 00000000000..48ca8012ceb Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/block-editor/images/BlockListEditor_BlockPicker.jpg differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/block-editor/images/BlockListEditor_BlockPicker_simplesetup.jpg b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/block-editor/images/BlockListEditor_BlockPicker_simplesetup.jpg new file mode 100644 index 00000000000..265f40b2b50 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/block-editor/images/BlockListEditor_BlockPicker_simplesetup.jpg differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/block-editor/images/BlockListEditor_DataType.jpg b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/block-editor/images/BlockListEditor_DataType.jpg new file mode 100644 index 00000000000..d55344cfab6 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/block-editor/images/BlockListEditor_DataType.jpg differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/block-editor/images/BlockListEditor_DataType_Blocks.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/block-editor/images/BlockListEditor_DataType_Blocks.png new file mode 100644 index 00000000000..55f022f185c Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/block-editor/images/BlockListEditor_DataType_Blocks.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/block-editor/images/BlockListEditor_EditingOverlay.jpg b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/block-editor/images/BlockListEditor_EditingOverlay.jpg new file mode 100644 index 00000000000..06e3952f0d6 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/block-editor/images/BlockListEditor_EditingOverlay.jpg differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/block-editor/images/BlockListEditor_InlineEditing.jpg b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/block-editor/images/BlockListEditor_InlineEditing.jpg new file mode 100644 index 00000000000..0bc57cb6dcb Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/block-editor/images/BlockListEditor_InlineEditing.jpg differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/block-editor/images/Install-Sample-Configuration.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/block-editor/images/Install-Sample-Configuration.png new file mode 100644 index 00000000000..3c461f50f2a Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/block-editor/images/Install-Sample-Configuration.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/block-editor/images/Resizing-Blocks.gif b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/block-editor/images/Resizing-Blocks.gif new file mode 100644 index 00000000000..3cd187ba55c Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/block-editor/images/Resizing-Blocks.gif differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/block-editor/images/Sorting_BlockGrid_Blocks.gif b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/block-editor/images/Sorting_BlockGrid_Blocks.gif new file mode 100644 index 00000000000..b5a762aad5d Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/block-editor/images/Sorting_BlockGrid_Blocks.gif differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/block-editor/images/resizing-block-block-grid.gif b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/block-editor/images/resizing-block-block-grid.gif new file mode 100644 index 00000000000..f56d714d12b Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/block-editor/images/resizing-block-block-grid.gif differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/dropdown/images/Dropdown-DataType-v10.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/dropdown/images/Dropdown-DataType-v10.png new file mode 100644 index 00000000000..51a442213de Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/dropdown/images/Dropdown-DataType-v10.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/dropdown/images/Dropdown-DataType-v8.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/dropdown/images/Dropdown-DataType-v8.png new file mode 100644 index 00000000000..0ea95d1f653 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/dropdown/images/Dropdown-DataType-v8.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/dropdown/images/Dropdown-DataType.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/dropdown/images/Dropdown-DataType.png new file mode 100644 index 00000000000..da2e4783a21 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/dropdown/images/Dropdown-DataType.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/dropdown/images/Dropdown-List-Content.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/dropdown/images/Dropdown-List-Content.png new file mode 100644 index 00000000000..748994a5133 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/dropdown/images/Dropdown-List-Content.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/dropdown/images/Dropdown-List-DataType.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/dropdown/images/Dropdown-List-DataType.png new file mode 100644 index 00000000000..4c3e82f6475 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/dropdown/images/Dropdown-List-DataType.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/dropdown/images/Dropdown-List-Keys-Content.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/dropdown/images/Dropdown-List-Keys-Content.png new file mode 100644 index 00000000000..9f32f5a1403 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/dropdown/images/Dropdown-List-Keys-Content.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/dropdown/images/Dropdown-List-Keys-DataType.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/dropdown/images/Dropdown-List-Keys-DataType.png new file mode 100644 index 00000000000..ffcdc6226ed Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/dropdown/images/Dropdown-List-Keys-DataType.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/dropdown/images/DropdownMultiple-Content.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/dropdown/images/DropdownMultiple-Content.png new file mode 100644 index 00000000000..9d2fd30a993 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/dropdown/images/DropdownMultiple-Content.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/dropdown/images/DropdownSingle-Content.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/dropdown/images/DropdownSingle-Content.png new file mode 100644 index 00000000000..be48cc06e30 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/dropdown/images/DropdownSingle-Content.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/grid-layout/Images/Grid-config.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/grid-layout/Images/Grid-config.png new file mode 100644 index 00000000000..b1a7bebeeee Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/grid-layout/Images/Grid-config.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/grid-layout/Images/Grid-configuration.jpg b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/grid-layout/Images/Grid-configuration.jpg new file mode 100644 index 00000000000..1a1669ea764 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/grid-layout/Images/Grid-configuration.jpg differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/grid-layout/Images/Grid-layout-NO-SIDEBAR-rows.jpg b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/grid-layout/Images/Grid-layout-NO-SIDEBAR-rows.jpg new file mode 100644 index 00000000000..6f2ee70af01 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/grid-layout/Images/Grid-layout-NO-SIDEBAR-rows.jpg differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/grid-layout/Images/Grid-layout-rows.jpg b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/grid-layout/Images/Grid-layout-rows.jpg new file mode 100644 index 00000000000..82c269292fa Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/grid-layout/Images/Grid-layout-rows.jpg differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/grid-layout/Images/Grid-layout-scenarios-1.jpg b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/grid-layout/Images/Grid-layout-scenarios-1.jpg new file mode 100644 index 00000000000..68e9eaa12b6 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/grid-layout/Images/Grid-layout-scenarios-1.jpg differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/grid-layout/Images/Grid-layout-scenarios.jpg b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/grid-layout/Images/Grid-layout-scenarios.jpg new file mode 100644 index 00000000000..89c46c8e2a6 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/grid-layout/Images/Grid-layout-scenarios.jpg differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/grid-layout/Images/Grid-layout.jpg b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/grid-layout/Images/Grid-layout.jpg new file mode 100644 index 00000000000..c568c3bc883 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/grid-layout/Images/Grid-layout.jpg differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/grid-layout/Images/cells.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/grid-layout/Images/cells.png new file mode 100644 index 00000000000..7a08e22f31e Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/grid-layout/Images/cells.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/grid-layout/Images/editor.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/grid-layout/Images/editor.png new file mode 100644 index 00000000000..f2cabc6bd80 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/grid-layout/Images/editor.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/grid-layout/Images/grid-resizing.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/grid-layout/Images/grid-resizing.png new file mode 100644 index 00000000000..43795bb6e72 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/grid-layout/Images/grid-resizing.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/grid-layout/Images/grid-settings-and-style.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/grid-layout/Images/grid-settings-and-style.png new file mode 100644 index 00000000000..be277afce13 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/grid-layout/Images/grid-settings-and-style.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/grid-layout/Images/grid-wireframe.jpg b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/grid-layout/Images/grid-wireframe.jpg new file mode 100644 index 00000000000..743294e00e0 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/grid-layout/Images/grid-wireframe.jpg differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/grid-layout/Images/layout.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/grid-layout/Images/layout.png new file mode 100644 index 00000000000..1d56b4e1077 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/grid-layout/Images/layout.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/grid-layout/Images/layouts.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/grid-layout/Images/layouts.png new file mode 100644 index 00000000000..92d35914331 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/grid-layout/Images/layouts.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/grid-layout/Images/rows.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/grid-layout/Images/rows.png new file mode 100644 index 00000000000..324ada87d38 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/grid-layout/Images/rows.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/grid-layout/Images/settings.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/grid-layout/Images/settings.png new file mode 100644 index 00000000000..5cbec50a771 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/grid-layout/Images/settings.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Bulk-action.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Bulk-action.png new file mode 100644 index 00000000000..317fcc0be6f Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Bulk-action.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Checkbox-Content.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Checkbox-Content.png new file mode 100644 index 00000000000..816cd894624 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Checkbox-Content.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Checkbox-Data-Type-v10.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Checkbox-Data-Type-v10.png new file mode 100644 index 00000000000..3f1b3f5769d Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Checkbox-Data-Type-v10.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Checkbox-Data-Type.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Checkbox-Data-Type.png new file mode 100644 index 00000000000..b5c12f01a4f Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Checkbox-Data-Type.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Color-Picker-Content-v8.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Color-Picker-Content-v8.png new file mode 100644 index 00000000000..1bf70361a19 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Color-Picker-Content-v8.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Color-Picker-Content.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Color-Picker-Content.png new file mode 100644 index 00000000000..afa79301ebb Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Color-Picker-Content.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Color-Picker-DataType-v10.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Color-Picker-DataType-v10.png new file mode 100644 index 00000000000..192d2d14d6a Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Color-Picker-DataType-v10.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Color-Picker-DataType-v8.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Color-Picker-DataType-v8.png new file mode 100644 index 00000000000..dce3cfb3707 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Color-Picker-DataType-v8.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Color-Picker-DataType.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Color-Picker-DataType.png new file mode 100644 index 00000000000..cdb0b000552 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Color-Picker-DataType.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Content-Picker-Content-v10.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Content-Picker-Content-v10.png new file mode 100644 index 00000000000..7a5462d5c5f Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Content-Picker-Content-v10.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Content-Picker-Content-v8.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Content-Picker-Content-v8.png new file mode 100644 index 00000000000..e6501f8493b Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Content-Picker-Content-v8.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Content-Picker-Content.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Content-Picker-Content.png new file mode 100644 index 00000000000..c8f7c80d8db Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Content-Picker-Content.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Content-Picker-DataType-8_1.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Content-Picker-DataType-8_1.png new file mode 100644 index 00000000000..19ee214659f Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Content-Picker-DataType-8_1.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Content-Picker-DataType-v8.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Content-Picker-DataType-v8.png new file mode 100644 index 00000000000..b3fc75721fc Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Content-Picker-DataType-v8.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Content-Picker-DataType.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Content-Picker-DataType.png new file mode 100644 index 00000000000..9961538e7b7 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Content-Picker-DataType.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Content-Picker2-Content.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Content-Picker2-Content.png new file mode 100644 index 00000000000..305193e35a5 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Content-Picker2-Content.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Content-Picker2-DataType-v10.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Content-Picker2-DataType-v10.png new file mode 100644 index 00000000000..7785bfc843a Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Content-Picker2-DataType-v10.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Content-Picker2-DataType.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Content-Picker2-DataType.png new file mode 100644 index 00000000000..0488e5ca139 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Content-Picker2-DataType.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Content-app-icon.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Content-app-icon.png new file mode 100644 index 00000000000..e24f72dc290 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Content-app-icon.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Csv-example-v8.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Csv-example-v8.png new file mode 100644 index 00000000000..ad132be4a7f Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Csv-example-v8.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Date-Time-Content.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Date-Time-Content.png new file mode 100644 index 00000000000..1ebdd54aaaa Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Date-Time-Content.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Date-Time-With-Time-Content.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Date-Time-With-Time-Content.png new file mode 100644 index 00000000000..2460047b337 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Date-Time-With-Time-Content.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Date-Time-With-Time-Data-Type.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Date-Time-With-Time-Data-Type.png new file mode 100644 index 00000000000..1b1573f3cdc Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Date-Time-With-Time-Data-Type.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/DateTime-DataType.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/DateTime-DataType.png new file mode 100644 index 00000000000..c0c5dfb8d60 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/DateTime-DataType.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/EmailAddress-Content-v10.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/EmailAddress-Content-v10.png new file mode 100644 index 00000000000..71b57446e23 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/EmailAddress-Content-v10.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/EmailAddress-DataType-Content.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/EmailAddress-DataType-Content.png new file mode 100644 index 00000000000..4d69318f166 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/EmailAddress-DataType-Content.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/EmailAddress-DataType-v8.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/EmailAddress-DataType-v8.png new file mode 100644 index 00000000000..8a23ef3e7dd Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/EmailAddress-DataType-v8.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/EmailAddress-DataType-v88.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/EmailAddress-DataType-v88.png new file mode 100644 index 00000000000..fe097a4cfab Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/EmailAddress-DataType-v88.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Eye-Dropper-Color-Picker-Content-v8.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Eye-Dropper-Color-Picker-Content-v8.png new file mode 100644 index 00000000000..d404b98533b Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Eye-Dropper-Color-Picker-Content-v8.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Eye-Dropper-Color-Picker-DataType-v8.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Eye-Dropper-Color-Picker-DataType-v8.png new file mode 100644 index 00000000000..c8d568092ed Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Eye-Dropper-Color-Picker-DataType-v8.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/File-Upload-content-example.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/File-Upload-content-example.png new file mode 100644 index 00000000000..83a6fc3ac28 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/File-Upload-content-example.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Json-example-v8.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Json-example-v8.png new file mode 100644 index 00000000000..509f6bc80d8 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Json-example-v8.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Label-Content-v8.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Label-Content-v8.png new file mode 100644 index 00000000000..8542a32ceb9 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Label-Content-v8.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Label-Setup-v8.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Label-Setup-v8.png new file mode 100644 index 00000000000..c3c57f0a063 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Label-Setup-v8.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Markdown-Editor-content-example.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Markdown-Editor-content-example.png new file mode 100644 index 00000000000..e71cbb2825f Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Markdown-Editor-content-example.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Markdown-Editor-definition-example-v10.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Markdown-Editor-definition-example-v10.png new file mode 100644 index 00000000000..d697795c88a Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Markdown-Editor-definition-example-v10.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Media-Picker-Content-v8.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Media-Picker-Content-v8.png new file mode 100644 index 00000000000..22adeb1cfed Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Media-Picker-Content-v8.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Media-Picker-Content.jpg b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Media-Picker-Content.jpg new file mode 100644 index 00000000000..ab98dd1c544 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Media-Picker-Content.jpg differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Media-Picker-DataType-8_1.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Media-Picker-DataType-8_1.png new file mode 100644 index 00000000000..06b96e1f7aa Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Media-Picker-DataType-8_1.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Media-Picker-DataType-v10.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Media-Picker-DataType-v10.png new file mode 100644 index 00000000000..761f85d1b08 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Media-Picker-DataType-v10.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Media-Picker-DataType-v8.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Media-Picker-DataType-v8.png new file mode 100644 index 00000000000..47a81f806f8 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Media-Picker-DataType-v8.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Media-Picker-DataType.jpg b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Media-Picker-DataType.jpg new file mode 100644 index 00000000000..9ac41a5e833 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Media-Picker-DataType.jpg differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Media-Picker2-Content.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Media-Picker2-Content.png new file mode 100644 index 00000000000..d608e07bddc Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Media-Picker2-Content.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Media-Picker2-DataType.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Media-Picker2-DataType.png new file mode 100644 index 00000000000..aebba80af59 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Media-Picker2-DataType.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Media-Picker3-Content.jpg b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Media-Picker3-Content.jpg new file mode 100644 index 00000000000..4cf7c146c4a Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Media-Picker3-Content.jpg differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Media-Picker3-DataType.jpg b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Media-Picker3-DataType.jpg new file mode 100644 index 00000000000..5bd2ba0f4fa Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Media-Picker3-DataType.jpg differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/MediaPicker-DataType-v10.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/MediaPicker-DataType-v10.png new file mode 100644 index 00000000000..a779088cce0 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/MediaPicker-DataType-v10.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Member-Group-Picker-Content.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Member-Group-Picker-Content.png new file mode 100644 index 00000000000..33109a95d23 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Member-Group-Picker-Content.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Member-Picker-Content-v8.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Member-Picker-Content-v8.png new file mode 100644 index 00000000000..5f01d0e0fac Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Member-Picker-Content-v8.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Member-Picker-Content.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Member-Picker-Content.png new file mode 100644 index 00000000000..7b192ab851c Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Member-Picker-Content.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Member-Picker-DataType-v8.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Member-Picker-DataType-v8.png new file mode 100644 index 00000000000..e1b845db8e7 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Member-Picker-DataType-v8.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Member-Picker-DataType.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Member-Picker-DataType.png new file mode 100644 index 00000000000..b62615b15de Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Member-Picker-DataType.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Multinode-Treepicker-Content-v8.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Multinode-Treepicker-Content-v8.png new file mode 100644 index 00000000000..83e70feba90 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Multinode-Treepicker-Content-v8.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Multinode-Treepicker-Content.jpg b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Multinode-Treepicker-Content.jpg new file mode 100644 index 00000000000..1f59bd1aa4e Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Multinode-Treepicker-Content.jpg differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Multinode-Treepicker-DataType-8_1.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Multinode-Treepicker-DataType-8_1.png new file mode 100644 index 00000000000..a5b012f17c6 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Multinode-Treepicker-DataType-8_1.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Multinode-Treepicker-DataType-v8.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Multinode-Treepicker-DataType-v8.png new file mode 100644 index 00000000000..373a1683542 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Multinode-Treepicker-DataType-v8.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Multinode-Treepicker-DataType.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Multinode-Treepicker-DataType.png new file mode 100644 index 00000000000..824437a853c Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Multinode-Treepicker-DataType.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Multinode-Treepicker2-Content.jpg b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Multinode-Treepicker2-Content.jpg new file mode 100644 index 00000000000..bb3ab928c11 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Multinode-Treepicker2-Content.jpg differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Multinode-Treepicker2-DataType.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Multinode-Treepicker2-DataType.png new file mode 100644 index 00000000000..f6ce9d537f1 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Multinode-Treepicker2-DataType.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Multiple-Textbox-Repeatable-Textstrings-Content (1) (1).png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Multiple-Textbox-Repeatable-Textstrings-Content (1) (1).png new file mode 100644 index 00000000000..7c3e61eae19 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Multiple-Textbox-Repeatable-Textstrings-Content (1) (1).png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Multiple-Textbox-Repeatable-Textstrings-Content (1) (2).png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Multiple-Textbox-Repeatable-Textstrings-Content (1) (2).png new file mode 100644 index 00000000000..7c3e61eae19 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Multiple-Textbox-Repeatable-Textstrings-Content (1) (2).png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Multiple-Textbox-Repeatable-Textstrings-Content (1).png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Multiple-Textbox-Repeatable-Textstrings-Content (1).png new file mode 100644 index 00000000000..7c3e61eae19 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Multiple-Textbox-Repeatable-Textstrings-Content (1).png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Multiple-Textbox-Repeatable-Textstrings-Content (2).png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Multiple-Textbox-Repeatable-Textstrings-Content (2).png new file mode 100644 index 00000000000..7c3e61eae19 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Multiple-Textbox-Repeatable-Textstrings-Content (2).png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Multiple-Textbox-Repeatable-Textstrings-Content.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Multiple-Textbox-Repeatable-Textstrings-Content.png new file mode 100644 index 00000000000..7c3e61eae19 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Multiple-Textbox-Repeatable-Textstrings-Content.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Multy-Url-Picker-Content-v8.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Multy-Url-Picker-Content-v8.png new file mode 100644 index 00000000000..ce2969ab86c Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Multy-Url-Picker-Content-v8.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Multy-Url-Picker-DataType-8_1.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Multy-Url-Picker-DataType-8_1.png new file mode 100644 index 00000000000..3e2453ac34e Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Multy-Url-Picker-DataType-8_1.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Multy-Url-Picker-DataType-v10.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Multy-Url-Picker-DataType-v10.png new file mode 100644 index 00000000000..062da5499b4 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Multy-Url-Picker-DataType-v10.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Multy-Url-Picker-DataType-v8.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Multy-Url-Picker-DataType-v8.png new file mode 100644 index 00000000000..4f9616254e2 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Multy-Url-Picker-DataType-v8.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/NestedContent_AddContent.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/NestedContent_AddContent.png new file mode 100644 index 00000000000..98414978dc3 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/NestedContent_AddContent.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/NestedContent_DataType-v8.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/NestedContent_DataType-v8.png new file mode 100644 index 00000000000..161b7936661 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/NestedContent_DataType-v8.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/NestedContent_DataTypeDefinition.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/NestedContent_DataTypeDefinition.png new file mode 100644 index 00000000000..dda7d36f1eb Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/NestedContent_DataTypeDefinition.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/NestedContent_EditItem-v8.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/NestedContent_EditItem-v8.png new file mode 100644 index 00000000000..94c5c38a18e Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/NestedContent_EditItem-v8.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/NestedContent_EditItem.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/NestedContent_EditItem.png new file mode 100644 index 00000000000..da6b1155c87 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/NestedContent_EditItem.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/NestedContent_NewItem-v8.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/NestedContent_NewItem-v8.png new file mode 100644 index 00000000000..f762ddad70b Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/NestedContent_NewItem-v8.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/NestedContent_NewItem.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/NestedContent_NewItem.png new file mode 100644 index 00000000000..fcde18e4093 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/NestedContent_NewItem.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/NestedContent_NotSupported.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/NestedContent_NotSupported.png new file mode 100644 index 00000000000..0c6117c24cd Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/NestedContent_NotSupported.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/NestedContent_SelectSchema-v8.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/NestedContent_SelectSchema-v8.png new file mode 100644 index 00000000000..d1ce4c68a49 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/NestedContent_SelectSchema-v8.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/NestedContent_SelectSchema.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/NestedContent_SelectSchema.png new file mode 100644 index 00000000000..08e3d50241a Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/NestedContent_SelectSchema.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/NestedContent_SingleItemMode-v8.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/NestedContent_SingleItemMode-v8.png new file mode 100644 index 00000000000..7ca1a2aa5cd Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/NestedContent_SingleItemMode-v8.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/NestedContent_SingleItemMode.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/NestedContent_SingleItemMode.png new file mode 100644 index 00000000000..f0339bd55c7 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/NestedContent_SingleItemMode.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/RadioButton-List-Content-v7.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/RadioButton-List-Content-v7.png new file mode 100644 index 00000000000..1b127cb708f Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/RadioButton-List-Content-v7.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/RadioButton-List-Content-v8.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/RadioButton-List-Content-v8.png new file mode 100644 index 00000000000..dffc9993677 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/RadioButton-List-Content-v8.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/RadioButton-List-DataType-v10.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/RadioButton-List-DataType-v10.png new file mode 100644 index 00000000000..2127a4f1ced Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/RadioButton-List-DataType-v10.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/RadioButton-List-DataType-v7.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/RadioButton-List-DataType-v7.png new file mode 100644 index 00000000000..142fa243b8b Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/RadioButton-List-DataType-v7.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/RadioButton-List-DataType-v8.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/RadioButton-List-DataType-v8.png new file mode 100644 index 00000000000..8ba95eb0960 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/RadioButton-List-DataType-v8.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Related-Links-Content.jpg b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Related-Links-Content.jpg new file mode 100644 index 00000000000..69a93e1b650 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Related-Links-Content.jpg differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Related-Links-DataType.jpg b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Related-Links-DataType.jpg new file mode 100644 index 00000000000..ed7f4b3cd0f Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Related-Links-DataType.jpg differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Related-Links2-Content.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Related-Links2-Content.png new file mode 100644 index 00000000000..2a794696fc7 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Related-Links2-Content.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Related-Links2-DataType.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Related-Links2-DataType.png new file mode 100644 index 00000000000..7dff6a53a7a Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Related-Links2-DataType.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Repeatable-Textstrings-Content.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Repeatable-Textstrings-Content.png new file mode 100644 index 00000000000..7c3e61eae19 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Repeatable-Textstrings-Content.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Repeatable-Textstrings-DataType-v10.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Repeatable-Textstrings-DataType-v10.png new file mode 100644 index 00000000000..9712de40e4b Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Repeatable-Textstrings-DataType-v10.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Repeatable-Textstrings-DataType.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Repeatable-Textstrings-DataType.png new file mode 100644 index 00000000000..4111a53f682 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Repeatable-Textstrings-DataType.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Slider-Content-Example-With-Range.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Slider-Content-Example-With-Range.png new file mode 100644 index 00000000000..94a6744a65d Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Slider-Content-Example-With-Range.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Slider-Content-Example-no-range.PNG b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Slider-Content-Example-no-range.PNG new file mode 100644 index 00000000000..defbec9d5c0 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Slider-Content-Example-no-range.PNG differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Slider-Data-Type-Definition-Example.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Slider-Data-Type-Definition-Example.png new file mode 100644 index 00000000000..83075cd06e3 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Slider-Data-Type-Definition-Example.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Tags-DataType-v10.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Tags-DataType-v10.png new file mode 100644 index 00000000000..28df576cc66 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Tags-DataType-v10.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Tags-DataType-v8.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Tags-DataType-v8.png new file mode 100644 index 00000000000..4cdceafb098 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Tags-DataType-v8.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Textarea-Content-Limit-v8.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Textarea-Content-Limit-v8.png new file mode 100644 index 00000000000..11b0429ae07 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Textarea-Content-Limit-v8.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Textarea-Content-v8.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Textarea-Content-v8.png new file mode 100644 index 00000000000..a705fe2e373 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Textarea-Content-v8.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Textarea-Setup-Limit-v8.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Textarea-Setup-Limit-v8.png new file mode 100644 index 00000000000..10056565a9e Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Textarea-Setup-Limit-v8.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Textarea-Setup-v10.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Textarea-Setup-v10.png new file mode 100644 index 00000000000..c9fc0f6ab18 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Textarea-Setup-v10.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Textarea-Setup-v8.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Textarea-Setup-v8.png new file mode 100644 index 00000000000..57e204be5da Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Textarea-Setup-v8.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Textbox-Content-Limit-v8.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Textbox-Content-Limit-v8.png new file mode 100644 index 00000000000..3fac1920ccf Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Textbox-Content-Limit-v8.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Textbox-Content-v8.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Textbox-Content-v8.png new file mode 100644 index 00000000000..0d11476b79d Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Textbox-Content-v8.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Textbox-Setup-v10.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Textbox-Setup-v10.png new file mode 100644 index 00000000000..63c007e993b Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Textbox-Setup-v10.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Textbox-Setup-v8.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Textbox-Setup-v8.png new file mode 100644 index 00000000000..6be488e2c47 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Textbox-Setup-v8.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/True-False-Content.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/True-False-Content.png new file mode 100644 index 00000000000..f9e2ee1d4f9 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/True-False-Content.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/True-False-DataType-742.jpg b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/True-False-DataType-742.jpg new file mode 100644 index 00000000000..3fed90221c1 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/True-False-DataType-742.jpg differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Typeahead-v8.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Typeahead-v8.png new file mode 100644 index 00000000000..36c7d8ab560 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Typeahead-v8.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/User-Picker-Content-v8.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/User-Picker-Content-v8.png new file mode 100644 index 00000000000..b10862a78f2 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/User-Picker-Content-v8.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/User-Picker-DataType-v8.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/User-Picker-DataType-v8.png new file mode 100644 index 00000000000..e2e6107c9e6 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/User-Picker-DataType-v8.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/checkbox-list-content.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/checkbox-list-content.png new file mode 100644 index 00000000000..091b76744a6 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/checkbox-list-content.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/checkbox-list-setup-v8.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/checkbox-list-setup-v8.png new file mode 100644 index 00000000000..31e261468bb Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/checkbox-list-setup-v8.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/checkbox-list-setup.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/checkbox-list-setup.png new file mode 100644 index 00000000000..cbc8b32dab8 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/checkbox-list-setup.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/configuration.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/configuration.png new file mode 100644 index 00000000000..7fed23ebeaf Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/configuration.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/content-example-empty.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/content-example-empty.png new file mode 100644 index 00000000000..c52090520a5 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/content-example-empty.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/content-example.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/content-example.png new file mode 100644 index 00000000000..43de1d23057 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/content-example.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/crop.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/crop.png new file mode 100644 index 00000000000..9eab72c6175 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/crop.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/datatype.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/datatype.png new file mode 100644 index 00000000000..679cf442191 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/datatype.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/date-picker-v8.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/date-picker-v8.png new file mode 100644 index 00000000000..af4255a5566 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/date-picker-v8.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/date-time-v8.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/date-time-v8.png new file mode 100644 index 00000000000..04e52f6da2c Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/date-time-v8.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/definition-example-v10.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/definition-example-v10.png new file mode 100644 index 00000000000..64f63e9d0d3 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/definition-example-v10.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/definition-example.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/definition-example.png new file mode 100644 index 00000000000..16ad8acfcd0 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/definition-example.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/emailaddress-datatype-v10.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/emailaddress-datatype-v10.png new file mode 100644 index 00000000000..d89366ff495 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/emailaddress-datatype-v10.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/enable-listview.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/enable-listview.png new file mode 100644 index 00000000000..1bd0f06ee35 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/enable-listview.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/focalpoint.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/focalpoint.png new file mode 100644 index 00000000000..e315b62e092 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/focalpoint.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/imageCropper-crop-v8.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/imageCropper-crop-v8.png new file mode 100644 index 00000000000..fbbb3983ca0 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/imageCropper-crop-v8.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/imageCropper-focalpoint-v8.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/imageCropper-focalpoint-v8.png new file mode 100644 index 00000000000..6d7faf59490 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/imageCropper-focalpoint-v8.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/imageCropper-upload-v8.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/imageCropper-upload-v8.png new file mode 100644 index 00000000000..94e5f4e3056 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/imageCropper-upload-v8.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/imageCropper-v8.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/imageCropper-v8.png new file mode 100644 index 00000000000..71285c8067a Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/imageCropper-v8.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/imageCropper-v9.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/imageCropper-v9.png new file mode 100644 index 00000000000..18bddd8e816 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/imageCropper-v9.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/list-icon.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/list-icon.png new file mode 100644 index 00000000000..e9ed8be01f5 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/list-icon.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/list-member-picked.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/list-member-picked.png new file mode 100644 index 00000000000..303197e4571 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/list-member-picked.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/list-view-settings1-v10.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/list-view-settings1-v10.png new file mode 100644 index 00000000000..779fee60cd3 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/list-view-settings1-v10.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/list-view-settings2-v10.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/list-view-settings2-v10.png new file mode 100644 index 00000000000..ddb8b025c09 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/list-view-settings2-v10.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/listview-content-example-email-settings.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/listview-content-example-email-settings.png new file mode 100644 index 00000000000..4224ff7dcad Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/listview-content-example-email-settings.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/listview-content-example-email.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/listview-content-example-email.png new file mode 100644 index 00000000000..b20a1a15a63 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/listview-content-example-email.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/listview-property-dropdown.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/listview-property-dropdown.png new file mode 100644 index 00000000000..55783a6a881 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/listview-property-dropdown.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/listview-property.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/listview-property.png new file mode 100644 index 00000000000..6d8cc9b007a Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/listview-property.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/listview-settings-2.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/listview-settings-2.png new file mode 100644 index 00000000000..3f79b7b6af9 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/listview-settings-2.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/listview-settings.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/listview-settings.png new file mode 100644 index 00000000000..c34dd7dd17b Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/listview-settings.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/listview.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/listview.png new file mode 100644 index 00000000000..6e6495012ec Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/listview.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/mandatory-checkbox.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/mandatory-checkbox.png new file mode 100644 index 00000000000..72956366eb8 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/mandatory-checkbox.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/member-picker-settings.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/member-picker-settings.png new file mode 100644 index 00000000000..888e6eeb6ee Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/member-picker-settings.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/member-picker.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/member-picker.png new file mode 100644 index 00000000000..a750e3f6ab5 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/member-picker.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/numeric-content.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/numeric-content.png new file mode 100644 index 00000000000..165126a8334 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/numeric-content.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/numeric-datatype-v10.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/numeric-datatype-v10.png new file mode 100644 index 00000000000..80cab7f01f1 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/numeric-datatype-v10.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/numeric-datatype.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/numeric-datatype.png new file mode 100644 index 00000000000..cca349097c3 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/numeric-datatype.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/others-result.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/others-result.png new file mode 100644 index 00000000000..d9ca0303dc3 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/others-result.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/others.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/others.png new file mode 100644 index 00000000000..29780fc0632 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/others.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/picked-member.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/picked-member.png new file mode 100644 index 00000000000..e7ebbae510e Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/picked-member.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/upload.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/upload.png new file mode 100644 index 00000000000..2d0bddbf0c7 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/upload.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/wip.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/wip.png new file mode 100644 index 00000000000..0665c1c1b87 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/wip.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/README.md b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/README.md new file mode 100644 index 00000000000..59ba7b8ffe7 --- /dev/null +++ b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/README.md @@ -0,0 +1,2 @@ +# Built-in Property Editors + diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/block-editor/README.md b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/block-editor/README.md new file mode 100644 index 00000000000..bf1f3ed396e --- /dev/null +++ b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/block-editor/README.md @@ -0,0 +1,19 @@ +# Block Editors + +The Block Editors are property editors that enabled you to build advanced editor tools using a set of predefined Document Types. + +{% hint style="warning" %} +This article is a work in progress and may undergo further revisions, updates, or amendments. The information contained herein is subject to change without notice. +{% endhint %} + +Umbraco CMS currently ships with two Block Editors: the Block List and the Block Grid. + +## [Block List](block-list-editor.md) + +## [Block Grid](block-grid-editor.md) + +## Customizing Block Editors + +### [Creating custom views for blocks](../../../../../tutorials/creating-custom-views-for-blocklist.md) + +Learn how to create custom views for the blocks used in your Block Grid or Block List property editors. diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/block-editor/block-grid-editor.md b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/block-editor/block-grid-editor.md new file mode 100644 index 00000000000..9e5ee12fe48 --- /dev/null +++ b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/block-editor/block-grid-editor.md @@ -0,0 +1,737 @@ +# Block Grid + +`Schema Alias: Umbraco.BlockGrid` + +`UI Alias: Umb.PropertyEditorUi.BlockGrid` + +`Returns: BlockGridModel` + +The **Block Grid** property editor enables editors to layout their content in the Umbraco backoffice. The content is made of Blocks that can contain different types of data. + +{% hint style="warning" %} +This article is a work in progress and may undergo further revisions, updates, or amendments. The information contained herein is subject to change without notice. +{% endhint %} + +## Sample configuration + +When testing out the property editor, you can use a set of predefined Blocks. The option will only be possible when there are no other Data Types using the Block Grid property editor. + +
+ +* Create a new **Data Type**. +* Select the **Block Grid** as the **Property editor**. +* **Install** the "Sample Configuration". + +4 Blocks will be added to the property, ready for testing. + +## Configuring the Block Grid + +The Block Grid property editor is configured via the **Data Types** backoffice interface. + +To set up the Block Grid property editor, follow these steps: + +1. Navigate to the **Settings** section in the Umbraco backoffice. +2. Click **...** next to the **Data Types** folder. +3. Select **Create** -> **New Data Type**. +4. Select **Block Grid** from the list of available property editors. + +You will see the configuration options for a Block Grid property editor as shown below: + +![Block Grid - Data Type Configuration](../../../../../../../10/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/block-editor/images/BlockGridEditor_Configuration.png) + +The Data Type editor allows you to configure the following properties: + +* **Blocks** - Defines the Block Types available for use in the property. For more information, see [Setup Block Types](block-grid-editor.md#setup-block-types). +* **Amount** - Sets the minimum and/or the maximum number of Blocks that should be allowed at the root of the layout. +* **Live editing mode** - Enabling this option will allow you to see the changes as you are editing them. +* **Editor width** - Overwrites the width of the property editor. This field takes any valid CSS value for "max-width". For example: 100% or 800px. +* **Grid Columns** - Define the number of columns in your Block Grid. The default is 12 columns. +* **Layout Stylesheet** - Replaces the built-in Layout Stylesheet. Additionally, you can retrieve the default layout stylesheet to use as a base for your own inspiration or for writing your own stylesheet. +* **Create Button Label** - Overwrites the label on the Create button. + +## Setup Block Types + +Block Types are based on [**Element Types**](../../../../data/defining-content/#element-types). These can be created beforehand or while setting up your Block Types. + +Once you have added an Element Type as a Block Type on your Block Grid Data Type you have the option to configure it. + +![Block Grid - Data Type Block Configuration](../../../../../../../10/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/block-editor/images/BlockGridEditor_DataType_Blocks.png) + +### Groups + +Blocks can also be grouped. This is visible in the Block Catalogue and can also be used to allow a group of Blocks in an Area. + +## Block Configuration Settings + +Each Block has a set of properties that are optional to configure. These are described below. + +### General + +Customize the user experience for your content editors when they work with the Blocks in the Content section. + +* **Label** - Defines a label for the appearance of the Block in the editor. The label can use AngularJS template-string-syntax to display values of properties. + +{% hint style="info" %} +Label example: "My Block {=myPropertyAlias}" will be shown as: "My Block FooBar". +{% endhint %} + +* **Content model** - Presents the Element Type used as model for the Content section of this Block. This cannot be changed but you can open the Element Type to perform edits or view the properties available. Useful when writing your Label. +* **Settings model** - Adds a Settings section to your Block based on a given Element Type. When selected you can open the Element Type or choose to remove the Settings section again. + +### Size options + +Customize the Blocks size in the Grid. If you define multiple options, the Block becomes scalable. + +By default, a Block takes up the available width. + +A Block can be resized in two ways: + +1. When a Block is placed in an Area, it will fit to the Areas width. Learn more about [Areas](block-grid-editor.md#areas). +2. A Block can have one or more Column Span options defined. + +A Column Span option is used to define the width of a Block. With multiple Column Span options defined, the Content Editor can scale the Block to fit specific needs. + +Additionally, Blocks can be configured to span rows, this enables one Block to be placed next to a few rows containing other Blocks. + +* **Available column spans** - Defines one or more columns, the Block spans across. For example: in a 12 columns grid, 6 columns is equivalent to half width. By enabling 6 columns and 12 columns, the Block can be scaled to either half width or full width. +* **Available row spans** - Defines the amount of rows the Block spans across. + +See the [scaling blocks](block-grid-editor.md#scaling-blocks) section of this article for an example of how scaling works. + +### Catalogue appearance + +These properties refer to how the Block is presented in the Block catalogue when editors choose which Blocks to use for their content. + +* **Background color** - Defines a background color to be displayed beneath the icon or thumbnail. Example: `#424242`. +* **Icon color** - Changes the color of the Element Type icon. Example: `#242424`. +* **Thumbnail** - Pick an image or Scalable Vector Graphics (SVG) file to replace the icon of the Block in the catalogue. + +The thumbnails for the catalogue are presented in the format of 16:10. We recommend a resolution of 400px width and 250px height. + +### Permissions + +* **Allow in root** - Determines whether the Block can be created at the root of your layout. Turn this off if you only want a Block to appear within Block Areas. +* **Allow in areas** - Determines whether the Block can be created inside Areas of other Blocks. If this is turned off it can still be allowed in Block Areas by defining specific allowed Blocks. + +## Areas + +Blocks can nest other Blocks to support specific compositions. These compositions can be used as a layout for other Blocks. + +To achieve nesting, a Block must have one or more Areas defined. Each Area can contain one or more Blocks. + +Each Area has a size, defined by column and rows spans. The grid for the Areas are based on the same amount of columns as your root grid, unless you choose to change it. + +To scale an Area, click and drag the scale-button in the bottom-right corner of an Area. + +* **Grid Columns for Areas** - Overwrites the amount of columns used for the Area grid. +* **Areas** - Determines whether the Block can be created inside Areas of other Blocks. + +![Block Grid - Areas](../../../../../../../10/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/block-editor/images/BlockGridEditor_Areas.png) + +### Area configuration + +![Block Grid - Area Configuration](../../../../../../../10/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/block-editor/images/BlockGridEditor_AreasConfiguration.png) + +* **Alias** - The alias is used to identify this Area. It is being printed by `GetBlockGridHTML()` and used as name for the Area slot in Custom Views. The alias is also available for CSS Selectors to target the HTML-Element representing an Area. +* **Create Button Label** - Overwrites the Create Button Label of the Area. +* **Number of blocks** - Determines the total number of Blocks in an Area. +* **Allowed block types** - When this is empty, all Blocks with Permissions for creation in Areas, will be available. This can be overwritten by specifying the allowed Blocks. Define the types of Blocks or Groups of Blocks that are allowed. Additionally, you can also set how many Blocks of each type/group should be present. + +When allowing a Group of Blocks, you might want to require a specific amount for a certain Block of that Group. This can be done by adding that Block Type to the list as well and set the requirements. + +## Advanced + +These properties are relevant when working with custom views or complex projects. + +* **Custom view** - Overwrites the AngularJS view for the block presentation in the Content editor. Use this view to make a more visual presentation of the Block or make your own editing experience by adding your own AngularJS controller to the view. + +{% hint style="info" %} +Notice that any styling of a Block is scoped. This means that the default backoffice styles are not present for the view of this Block. +{% endhint %} + +* **Custom stylesheet** - Pick your own stylesheet to be used by the Block in the Content editor. +* **Overlay editor size** - Sets the size for the Content editor overlay for editing this block. +* **Hide content editor** - Hides the UI for editing the content in a Block Editor. This is only relevant if you made a custom view that provides the UI for editing of content. + +## Editing Blocks + +When viewing a **Block Grid** property editor in the **Content** section for the first time, you will be presented with the option to **Add content**. + +![Block Grid - Add Content](../../../../../../../10/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/block-editor/images/BlockGridEditor_AddContent.png) + +Clicking the **Add content** button opens up the **Block Catalogue**. + +![Block Grid - Setup](../../../../../../../10/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/block-editor/images/BlockGridEditor_BlockPicker.png) + +The Block Catalogue looks different depending on the amount of available Blocks and their catalogue appearance. + +![Block Grid - example setup](../../../../../../../10/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/block-editor/images/BlockGridEditor_BlockPicker_exsetup.png) + +Click the Block Type you wish to create and a new Block will appear in the layout. + +More Blocks can be added to the layout by clicking the Add content button. Alternatively, use the Add content button that appears on hover to add new Blocks between, besides, or above the existing Blocks. + +![Block Grid - Add Content Inline](../../../../../../../10/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/block-editor/images/BlockGridEditor_AddContentInline.png) + +To delete a Block, click the trash icon which appears on the mouse hover. + +![Block Grid - Delete Content](../../../../../../../10/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/block-editor/images/BlockGridEditor_DeleteContent.png) + +## Sorting Blocks + +Blocks can be rearranged using the click and drag feature. Move them up or down to place them in the desired order. + +Moving a Block from one Area to another is done in the same way. If a Block is not allowed in the given position, the area will display a red color and not allow the new position. + +![Block Grid - Sorting Blocks](../../../../../../../10/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/block-editor/images/Sorting_BlockGrid_Blocks.gif) + +## Scaling Blocks + +If a Block has multiple size options it can be scaled via the UI. This appears in the bottom left corner of the Block. + +The Block is resized using a click-and-drag feature. Moving the mouse will change the size to the size options closest to the mouse pointer. + +

Scale blocks in the grid by dragging from the bottom-right corner.

+ +## Rendering Block Grid Content + +Rendering the stored value of your **Block Grid** property editor can be done in two ways: + +1. [Default rendering](block-grid-editor.md#1-default-rendering) +2. [Build your own rendering](block-grid-editor.md#2-build-your-own-rendering) + +### 1. Default rendering + +You can choose to use the built-in rendering mechanism for rendering Blocks using a Partial View for each block. + +The default rendering method is named `GetBlockGridHtmlAsync()` and comes with a few options - for example: + +```csharp +@await Html.GetBlockGridHtmlAsync(Model, "myGrid") +``` + +In the sample above `"myGrid"` is the alias of the Block Grid editor. + +If you are using ModelsBuilder, the example will look like this: + +```csharp +@await Html.GetBlockGridHtmlAsync(Model.MyGrid) +``` + +To use the `GetBlockGridHtmlAsync()` method, you will need to create a Partial View for each Block Type. The Partial View must be named using the alias of the Element Type that is being used as Content Model for the Block Type. + +These Partial View files need to go into the `Views/Partials/blockgrid/Components/` folder. + +Example: `Views/Partials/blockgrid/Components/MyElementTypeAliasOfContent.cshtml`. + +The Partial Views will receive a model of type `Umbraco.Cms.Core.Models.Blocks.BlockGridItem`. This model contains `Content` and `Settings` from your block, as well as the configured `RowSpan`, `ColumnSpan`, and `Areas` of the Block. + +#### Rendering the Block Areas + +The Partial View for the Block is responsible for rendering its own Block Areas. This is done using another built-in rendering mechanism: + +```csharp +@await Html.GetBlockGridItemAreasHtmlAsync(Model) +``` + +Here you will need to create a Partial View for each Block Type within the Block Area. For the name, use the alias of the Element Type that is being used as Content Model for the Block Type. + +These Partial Views must be placed in the same folder as before, (`Views/Partials/blockgrid/Components/`), and will receive a model of type `Umbraco.Cms.Core.Models.Blocks.BlockGridItem`. + +#### Putting it all together + +The following is an example of a Partial View for a Block Type of type `MyElementTypeAliasOfContent`. + +{% code title="MyElementTypeAliasOfContent.cshtml" %} +```csharp +@inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage; + +@* Render the value of field with alias 'heading' from the Element Type selected as Content section *@ +

@Model.Content.Value("heading")

+ +@* Render the block areas *@ +@await Html.GetBlockGridItemAreasHtmlAsync(Model) +``` +{% endcode %} + +If you are using ModelsBuilder, you can make the property rendering strongly typed by letting your view accept a model of type `BlockGridItem`. For example: + +{% code title="MyElementTypeAliasOfContent.cshtml" %} +```csharp +@inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage>; +@using ContentModels = Umbraco.Cms.Web.Common.PublishedModels; + +@* Render the Heading property from the Element Type selected as Content section *@ +

@Model.Content.Heading

+ +@* Render the block areas *@ +@await Html.GetBlockGridItemAreasHtmlAsync(Model) +``` +{% endcode %} + +#### Stylesheet + +Using the default rendering together with your layout stylesheet will provide what you need for rendering the layout. + +To use the Default Layout Stylesheet, copy the stylesheet to your frontend. You can download the default layout stylesheet from the link within the DataType, we recommend putting the file in the `css` folder, example: `wwwroot/css/umbraco-blockgridlayout.css`. + +```csharp + +``` + +{% hint style="info" %} +A set of built-in Partial Views are responsible for rendering the Blocks and Areas in a Block Grid. If you want to tweak or change the way the Block Grid is rendered, you can use the built-in Partial Views as a template: + +1. Clone the views from [GitHub](https://github.com/umbraco/Umbraco-CMS/tree/contrib/src/Umbraco.Web.UI/Views/Partials/blockgrid). They can be found in `src/Umbraco.Web.UI/Views/Partials/blockgrid` . +2. Copy the cloned views to the local folder `Views/Partials/blockgrid/` . +3. Make changes to your copied views. The entry point for `GetBlockGridHtmlAsync()` is the view `default.cshtml` . +{% endhint %} + +### 2. Build custom rendering + +The built-in value converter for the Block Grid property editor lets you use the block data as you like. Call the `Value` method with a type of `BlockGridModel` to have the stored value returned as a `BlockGridModel` instance. + +`BlockGridModel` contains the Block Grid configuration (like the number of columns as `GridColumns`) whilst also being an implementation of `IEnumerable` (see details for `BlockGridItem` above). + +The following example mimics the built-in rendering mechanism for rendering Blocks using Partial Views: + +{% code title="View.cshtml" %} +```csharp +@inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage +@using Umbraco.Cms.Core.Models.Blocks +@{ + var grid = Model.Value("myGrid"); + + // get the number of columns defined for the grid + var gridColumns = grid.GridColumns; + + // iterate the block items + foreach (var item in grid) + { + var content = item.Content; + + @await Html.PartialAsync("PathToMyFolderOfPartialViews/" + content.ContentType.Alias, item); + } +} +``` +{% endcode %} + +If you do not want to use Partial Views, you can access the block item data directly within your rendering: + +{% code title="View.cshtml" %} +```csharp +@inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage +@using Umbraco.Cms.Core.Models.Blocks +@{ + var grid = Model.Value("myGrid"); + + // get the number of columns defined for the grid + var gridColumns = grid.GridColumns; + + // iterate the block items + foreach (var item in grid) + { + // get the content and settings of the block + var content = item.Content; + var settings = item.Settings; + // get the areas of the block + var areas = item.Areas; + // get the dimensions of the block + var rowSpan = item.RowSpan; + var columnSpan = item.ColumnSpan; + + // render the block data +
+

@(content.Value("title"))

+ This block is supposed to span @rowSpan rows and @columnSpan columns +
+ } +} +``` +{% endcode %} + +## Write a Custom Layout Stylesheet + +The default layout stylesheet is using CSS Grid. This can be modified to fit your implementation and your project. + +### Adjusting the Default Layout Stylesheet + +To make additions or overwrite parts of the default layout stylesheet, import the default stylesheet at the top of your own file. + +```css +@import 'css/umbblockgridlayout.css'; +``` + +You need to copy the Default Layout Stylesheet to your frontend. You can download the default layout stylesheet from the link within the DataType, we recommend putting the file in the `css` folder, example: `wwwroot/css/umbraco-blockgridlayout.css`. + +### Write a new Layout Stylesheet + +In this case, you would have to write the layout from scratch. + +You are free to pick any style, meaning there is no requirement to use CSS Grid. It is, however, recommended to use CSS Grid to ensure complete compatibility with the Umbraco backoffice. + +### CSS Class structure and available data + +When extending or writing your own layout, you need to know the structure and what data is available. + +For example: You can use the below HTML structure: + +```html +
+ + +
+ + +
+ + + + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ +
+``` + +## Build a Custom Backoffice View + +Building Custom Views for Block representations in Backoffice is based on the same API for all Block Editors. + +[Read about building a Custom View for Blocks here](../../../../../customizing/extending-overview/extension-types/block-custom-view.md) + +## Creating a Block Grid programmatically + +In this example, we will be creating content programmatically for a "spot" Blocks in a Block Grid. + +1. On a Document Type add a property called **blockGrid**. +2. Then add as editor **Block Grid**. +3. In the Block Grid add a new block and click to **Create new Element Type** +4. Name this element type **spotElement** with the following properties: + +* A property called **title** with the editor of **Textstring** +* A property called **text** with the editor of **Textstring** + +5. Then on the **Settings model** click to add a new Setting. +6. Then click to **Create new Element Type**. +7. Name this element type **spotSettings** with the following properties: + +* A property called **featured** with the editor of **True/false**. + +![Block Grid - Block Configuration](../../../images/BlockEditorConfigurationProgramatically.png) + +The raw input data for the spots looks like this: + +```csharp +new[] +{ + new { Title = "Item one", Text = "This is item one", Featured = false, ColumnSpan = 12, RowSpan = 1 }, + new { Title = "Item two", Text = "This is item two", Featured = true, ColumnSpan = 6, RowSpan = 2 } +} +``` + +The resulting JSON object stored for the Block Grid will look like this: + +```json +{ + "layout": { + "Umbraco.BlockGrid": [{ + "contentUdi": "umb://element/bb23fe28160941efa506da7aa314172d", + "settingsUdi": "umb://element/9b832ee528464456a8e9a658b47a9801", + "areas": [], + "columnSpan": 12, + "rowSpan": 1 + }, { + "contentUdi": "umb://element/a11e06ca155d40b78189be0bdaf11c6d", + "settingsUdi": "umb://element/d182a0d807fc4518b741b77c18aa73a1", + "areas": [], + "columnSpan": 6, + "rowSpan": 2 + } + ] + }, + "contentData": [{ + "contentTypeKey": "0e9f8609-1904-4fd1-9801-ad1880825ff3", + "udi": "umb://element/bb23fe28160941efa506da7aa314172d", + "title": "Item one", + "text": "This is item one" + }, { + "contentTypeKey": "0e9f8609-1904-4fd1-9801-ad1880825ff3", + "udi": "umb://element/a11e06ca155d40b78189be0bdaf11c6d", + "title": "Item two", + "text": "This is item two" + } + ], + "settingsData": [{ + "contentTypeKey": "22be457c-8249-42b8-8685-d33262f7ce2a", + "udi": "umb://element/9b832ee528464456a8e9a658b47a9801", + "featured": "0" + }, { + "contentTypeKey": "22be457c-8249-42b8-8685-d33262f7ce2a", + "udi": "umb://element/d182a0d807fc4518b741b77c18aa73a1", + "featured": "1" + } + ] +} +``` + +For each item in the raw data, we need to create: + +* One `contentData` entry with the _title_ and _text_. +* One `settingsData` entry with the _featured_ value (the checkbox expects `"0"` or `"1"` as data value). +* One `layout` entry with the desired column and row spans. + +All `contentData` and `layoutData` entries need their own unique `Udi` as well as the ID (key) of their corresponding Element Types. In this sample, we only have one Element Type for content (`spotElement`) and one for settings (`spotSettings`). In a real life scenario, there could be any number of Element Type combinations. + +8. First and foremost, we need models to transform the raw data into Block Grid compatible JSON. Create a class called **Model.cs** containing the following: + +{% code title="Models.cs" lineNumbers="true" %} +```csharp +using Umbraco.Cms.Core; +using System.Text.Json.Serialization; +namespace My.Site.Models; + +// this is the "root" of the block grid data +public class BlockGridData +{ + public BlockGridData(BlockGridLayout layout, BlockGridElementData[] contentData, BlockGridElementData[] settingsData) + { + Layout = layout; + ContentData = contentData; + SettingsData = settingsData; + } + + [JsonPropertyName("layout")] + public BlockGridLayout Layout { get; } + + [JsonPropertyName("contentData")] + public BlockGridElementData[] ContentData { get; } + + [JsonPropertyName("settingsData")] + public BlockGridElementData[] SettingsData { get; } +} + +// this is a wrapper for the block grid layout, purely required for correct serialization +public class BlockGridLayout +{ + public BlockGridLayout(BlockGridLayoutItem[] layoutItems) => LayoutItems = layoutItems; + + [JsonPropertyName("Umbraco.BlockGrid")] + public BlockGridLayoutItem[] LayoutItems { get; } +} + +// this represents an item in the block grid layout collection +public class BlockGridLayoutItem +{ + public BlockGridLayoutItem(Udi contentUdi, Udi settingsUdi, int columnSpan, int rowSpan) + { + ContentUdi = contentUdi; + SettingsUdi = settingsUdi; + ColumnSpan = columnSpan; + RowSpan = rowSpan; + } + + [JsonPropertyName("contentUdi")] + public Udi ContentUdi { get; } + + [JsonPropertyName("settingsUdi")] + public Udi SettingsUdi { get; } + + [JsonPropertyName("areas")] + // areas are omitted from this sample for abbreviation + public object[] Areas { get; } = { }; + + [JsonPropertyName("columnSpan")] + public int ColumnSpan { get; } + + [JsonPropertyName("rowSpan")] + public int RowSpan { get; } + +} + +// this represents an item in the block grid content or settings data collection +public class BlockGridElementData +{ + public BlockGridElementData(Guid contentTypeKey, Udi udi) + { + ContentTypeKey = contentTypeKey; + Udi = udi; + } + + [JsonPropertyName("contentTypeKey")] + public Guid ContentTypeKey { get; } + + [JsonPropertyName("udi")] + public Udi Udi { get; } + + [JsonExtensionData] + public Dictionary? Data { get; set;} +} +``` +{% endcode %} + +9. By injecting [ContentService](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Services.ContentService.html) and [ContentTypeService](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Services.ContentTypeService.html) into an API controller, we can transform the raw data into Block Grid JSON. It can then be saved to the target content item. Create a class called **BlockGridTestController.cs** containing the following: + +{% code title="BlockGridTestController.cs" lineNumbers="true" %} +```csharp +using Microsoft.AspNetCore.Mvc; +using My.Site.Models; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Serialization; +using Umbraco.Cms.Core.Services; + +namespace My.Site.Controllers; + +[ApiController] +[Route("/umbraco/api/blockgridtest")] +public class BlockGridTestController : Controller +{ + private readonly IContentService _contentService; + private readonly IContentTypeService _contentTypeService; + private readonly IJsonSerializer _serializer; + + public BlockGridTestController(IContentService contentService, IContentTypeService contentTypeService, IJsonSerializer serializer) + { + _contentService = contentService; + _contentTypeService = contentTypeService; + _serializer = serializer; + } + + // POST: /umbraco/api/blockgridtest/create + [HttpPost("create")] + public ActionResult Create() + { + // get the item content to modify + IContent? content = _contentService.GetById(Guid.Parse("efba7b97-91b6-4ddf-b2cc-eef89ff48c3b")); + if (content == null) + { + return NotFound("Could not find the content item to modify"); + } + + // get the element types for spot blocks (content and settings) + IContentType? spotContentType = _contentTypeService.Get("spotElement"); + IContentType? spotSettingsType = _contentTypeService.Get("spotSettings"); + if (spotContentType == null || spotSettingsType == null) + { + return NotFound("Could not find one or more content types for block data"); + } + + // this is the raw data to insert into the block grid + var rawData = new[] + { + new { Title = "Item one", Text = "This is item one", Featured = false, ColumnSpan = 12, RowSpan = 1 }, + new { Title = "Item two", Text = "This is item two", Featured = true, ColumnSpan = 6, RowSpan = 2 } + }; + + // build the individual parts of the block grid data from the raw data + var layoutItems = new List(); + var spotContentData = new List(); + var spotSettingsData = new List(); + foreach (var data in rawData) + { + // generate new UDIs for block content and settings + var contentUdi = Udi.Create(Constants.UdiEntityType.Element, Guid.NewGuid()); + var settingsUdi = Udi.Create(Constants.UdiEntityType.Element, Guid.NewGuid()); + + // create a new layout item + layoutItems.Add(new BlockGridLayoutItem(contentUdi, settingsUdi, data.ColumnSpan, data.RowSpan)); + + // create new content data + spotContentData.Add(new BlockGridElementData(spotContentType.Key, contentUdi) + { + Data = new Dictionary + { + { "title", data.Title }, + { "text", data.Text }, + } + }); + + // create new settings data + spotSettingsData.Add(new BlockGridElementData(spotSettingsType.Key, settingsUdi) + { + Data = new Dictionary + { + { "featured", data.Featured ? "1" : "0" }, + } + }); + } + + // construct the block grid data from layout, content and settings + var blockGridData = new BlockGridData( + new BlockGridLayout(layoutItems.ToArray()), + spotContentData.ToArray(), + spotSettingsData.ToArray()); + + // serialize the block grid data as JSON and save it to the "blockGrid" property on the content item + var propertyValue = _serializer.Serialize(blockGridData); + content.SetValue("blockGrid", propertyValue); + _contentService.Save(content); + + return Ok("Saved"); + } +} +``` +{% endcode %} + +For the above code `IContent? content = _contentService.GetById(1203);` change the id with your content node that is using the Block Grid. + +10. In order to test this implementation, run the project and add `/umbraco/api/blockgridtest/create` after your domain name. If the result shows as **Saved** then check your content node and you will see the 2 spotElement contents. + +![Block Grid - Result](../../../images/BlockEditorContentCreated.png) + +_This can also be tested via Postman as well if preffered._ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/block-editor/block-level-variance.md b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/block-editor/block-level-variance.md new file mode 100644 index 00000000000..339e0404f8c --- /dev/null +++ b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/block-editor/block-level-variance.md @@ -0,0 +1,68 @@ +--- +description: An intro to achieving content variance at block level. +--- + +# Block Level Variance + +In a variant context, a Block Editor behaves like any other Umbraco property editor by default. The Blocks contained within the editor "belong" to the Document variant, and there is no connection between Blocks across variants. + +In other words, both Block content and structure can vary between each Document variant. + +![Default Block Editor behavior in the backoffice](images/block-level-variance-1.png) + +This is the desired behavior for many cases. However, in some cases it is preferable to have a shared Block structure across all variants, where only the Block content varies. + +This is known as Block Level Variance: + +![Block Level Variance in the backoffice](images/block-level-variance-2.png) + +Block Level Variance is achieved when: + +* The [Document Type](../../../../data/defining-content/default-document-types.md#document-type) is configured for variance, and +* The Block Editor property is _not_ configured for variance, and +* The Block Editor property editor is configured to use [Element Types](../../../../data/defining-content/default-document-types.md#element-type) that _do_ vary. + +## The "unexposed" Block state + +When adding a new _variant_ Block to one Document variant, it is automatically added to all variants of the Document. + +The Block will start out in an "unexposed" state for all other Document variants than the one where it was added. It will remain like that for each variant until it is edited in that variant. + +The "unexposed" state is visualized by a dimmed-down icon and title (or likely a missing title, if [Umbraco Flavored Markdown](../../../../../reference/umbraco-flavored-markdown.md) is used): + +![Block Level Variance in the backoffice - with an unexposed block](images/block-level-variance-3.png) + +{% hint style="info" %} +"Unexposed" Blocks are omitted from the published Document output. So, you do not need to worry about defensive coding to avoid rendering these Blocks. +{% endhint %} + +## Invariance vs. Block Level Variance + +It is entirely possible to mix and match variance and invariance within the scope of Block Level Variance. Invariance is fully supported, both at Block level and at Block property level. + +Invariance within Block Level Variance follows the same rules as invariance at Document level: + +- Invariant content is added to and updated across all Document variants. +- Invariant content is explicitly published for all published Document variants when one or more variants are published. + +### Examples + +Consider a Document with English and Danish language variants, which is published in both languages. + +- An editor opens the English variant. +- They add an invariant Block, and +- They re-publish the English variant. + +**Result:** The new block will appear in both the English and Danish published content. + +- An editor opens the Danish variant. +- They update an invariant property value in a variant Block, and +- They re-publish the Danish variant. + +**Result:** The updated property value appears in both the English and Danish published content. + +## Structure vs. Block Level Variance + +The Block Editor structure is _invariant_ for Block Level Variance. This means that the structure follows the same rules for invariance as outlined in the section above. + +In other words: If an editor changes the order of the Blocks in one Document variant, it changes for all Document variants. The change is applied to all published Document variants, as soon as one or more variants are published. diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/block-editor/block-list-editor.md b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/block-editor/block-list-editor.md new file mode 100644 index 00000000000..4cc0075e30f --- /dev/null +++ b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/block-editor/block-list-editor.md @@ -0,0 +1,248 @@ +# Block List + +`Schema Alias: Umbraco.BlockList` + +`UI Alias: Umb.PropertyEditorUi.BlockList` + +`Returns: IEnumerable` + +{% hint style="warning" %} +This article is a work in progress and may undergo further revisions, updates, or amendments. The information contained herein is subject to change without notice. +{% endhint %} + +**Block List** is a list editing property editor, using [Element Types](../../../../data/defining-content/#element-types) to define the list item schema. + +{% hint style="info" %} +The _Block List_ replaces the obsolete _Nested Content_ editor. +{% endhint %} + +## Configure Block List + +The Block List property editor is configured in the same way as any standard property editor, via the _Data Types_ admin interface. + +To set up your Block List Editor property, create a new _Data Type_ and select **Block List** from the list of available property editors. + +Then you will see the configuration options for a Block List as shown below. + +![Block List - Data Type Definition](../../../../../../../10/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/block-editor/images/BlockListEditor_DataType.jpg) + +The Data Type editor allows you to configure the following properties: + +* **Available Blocks** - Here you will define the Block Types to be available for use in the property. Read more on how to set up Block Types below. +* **Amount** - Sets the minimum and/or maximum number of blocks that should be allowed in the list. +* **Single block mode** - When in Single block mode, the output will be `BlockListItem<>` instead of `BlockListModel` +* **Live editing mode** - Enabling this will make editing of a block happening directly to the document model, making changes appear as you type. +* **Inline editing mode** - Enabling this will change editing experience to inline, meaning that editing the data of blocks happens at sight as accordions. +* **Property editor width** - Overwrite the width of the property editor. This field takes any valid css value for "max-width". + +## Setup Block Types + +Block Types are **Element Types** which need to be created before you can start configuring them as Block Types. This can be done either directly from the property editor setup process, or you can set them up beforehand and add them to the block list after. + +Once you have added an element type as a Block Type on your Block List Data Type you will have the option to configure it further. + +![Block List - Data Type Block Configuration](../../../../../../../10/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/block-editor/images/BlockListEditor_DataType_Blocks.png) + +Each Block has a set of properties that are optional to configure. They are described below. + +### Editor Appearance + +By configuring the properties in the group you can customize the user experience for your content editors when they work with the blocks in the Content section. + +* **Label** - Define a label for the appearance of the Block in the editor. The label uses [Umbraco Flavoured Markdown](../../../../../reference/umbraco-flavored-markdown.md) to display values of properties. +* **Overlay editor size** - Set the size for the Content editor overlay for editing this block. + +### Data Models + +It is possible to use two separate Element Types for your Block Types. Its required to have one for Content and optional to add one for Settings. + +* **Content model** - This presents the Element Type used as model for the content section of this Block. This cannot be changed, but you can open the Element Type to perform edits or view the properties available. Useful when writing your Label. +* **Settings model** - Add a Settings section to your Block based on a given Element Type. When picked you can open the Element Type or choose to remove the settings section again. + +### Catalogue appearance + +These properties refer to how the Block is presented in the Block catalogue, when editors choose which Blocks to use for their content. + +* **Background color** - Define a background color to be displayed beneath the icon or thumbnail. Eg. `#424242`. +* **Icon color** - Change the color of the Element Type icon. Eg. `#242424`. +* **Thumbnail** - Pick an image or SVG file to replace the icon of this Block in the catalogue. + +The thumbnails for the catalogue are presented in the format of 16:10, and we recommend a resolution of 400px width and 250px height. + +### Advanced + +These properties are relevant when you work with custom views. + +* **Force hide content editor** - If you made a custom view that enables you to edit the content part of a block and you are using default editing mode (not inline) you might want to hide the content-editor from the block editor overlay. + +## Editing Blocks + +When viewing a **Block List** editor in the Content section for the first time, you will be presented with the option to Add content. + +![Block List - Add Content](../../../../../../../10/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/block-editor/images/BlockListEditor_AddContent.png) + +Clicking the Add content button brings up the Block Catalogue. + +![Block List - Setup](../../../../../../../10/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/block-editor/images/BlockListEditor_BlockPicker_simplesetup.jpg) + +The Block Catalogue looks different depending on the amount of available Blocks and their catalogue appearance. + +![Block List - example setup from Umbraco.com](../../../../../../../10/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/block-editor/images/BlockListEditor_BlockPicker.jpg) + +Click the Block Type you wish to create and a new Block will appear in the list. + +Depending on whether your Block List Editor is setup to use default or inline editing mode you will see one of the following things happening: + +In default mode you will enter the editing overlay of that Block: + +![Block List - Overlay editing](../../../../../../../10/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/block-editor/images/BlockListEditor_EditingOverlay.jpg) + +In inline editing mode the new Blocks will expand to show its inline editor: + +![Block List - Inline editing](../../../../../../../10/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/block-editor/images/BlockListEditor_InlineEditing.jpg) + +More Blocks can be added to the list by clicking the Add content button or using the inline Add content button that appears on hover between or above existing Blocks. + +![Block List - Add Content](../../../../../../../10/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/block-editor/images/BlockListEditor_AddContentInline.jpg) + +To reorder the Blocks, click and drag a Block up or down to place in the desired order. + +To delete a Block click the trash-bin icon appearing on hover. + +## Rendering Block List Content + +Rendering the stored value of your **Block List** property can be done in two ways. + +### 1. Default rendering + +You can choose to use the built-in rendering mechanism for rendering blocks via a Partial View for each block. + +The default rendering method is named `GetBlockListHtml()` and comes with a few options to go with it. The typical use could be: + +```csharp +@Html.GetBlockListHtml(Model, "MyBlocks") +``` + +"MyBlocks" above is the alias for the Block List editor. + +If using ModelsBuilder the example can be simplified: + +Example: + +```csharp +@Html.GetBlockListHtml(Model.MyBlocks) +``` + +To make this work you will need to create a Partial View for each block, named by the alias of the Element Type that is being used as Content Model. + +These partial views must be placed in this folder: `Views/Partials/BlockList/Components/`. Example: `Views/Partials/BlockList/Components/MyElementTypeAliasOfContent.cshtml`. + +A Partial View will receive the model of `Umbraco.Core.Models.Blocks.BlockListItem`. This gives you the option to access properties of the Content and Settings section of your Block. + +In the following example of a Partial view for a Block Type, please note that the `MyElementTypeAliasOfContent`and `MyElementTypeAliasOfSettings` should correspond with the selected Element Type Alias for the given model in your case. + +Example: + +```csharp +@inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage; +@using ContentModels = Umbraco.Cms.Web.Common.PublishedModels; +@{ + var content = (ContentModels.MyElementTypeAliasOfContent)Model.Content; + var settings = (ContentModels.MyElementTypeAliasOfSettings)Model.Settings; +} + +// Output the value of field with alias 'heading' from the Element Type selected as Content section +

@content.Value("heading")

+``` + +With ModelsBuilder: + +```csharp +// Output the value of field with alias 'heading' from the Element Type selected as Content section +

@content.Heading

+``` + +### 2. Build your own rendering + +A built-in value converter is available to use the data as you like. Call the `Value` method with a generic type of `IEnumerable` and the stored value will be returned as a list of `BlockListItem` entities. + +Example: + +```csharp +@inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage; +@using Umbraco.Cms.Core.Models.Blocks; +@{ + var blocks = Model.Value>("myBlocksProperty"); + foreach (var block in blocks) + { + var content = block.Content; + + @Html.Partial("MyFolderOfBlocks/" + content.ContentType.Alias, block) + } +} +``` + +Each item is a `BlockListItem` entity that contains two main properties `Content` and `Settings`. Each of these is a `IPublishedElement` which means you can use all the value converters you are used to using. + +Example: + +```csharp +@inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage; +@using ContentModels = Umbraco.Cms.Web.Common.PublishedModels; +@using Umbraco.Cms.Core.Models.Blocks; +@{ + var blocks = Model.Value>("myBlocksProperty"); + foreach (var block in blocks) + { + var content = (ContentModels.MyAliasOfContentElementType)block.Content; + var settings = (ContentModels.MyAliasOfSettingsElementType)block.Settings; + +

@content.MyExampleHeadlinePropertyAlias

+ } +} +``` + +## Extract Block List Content data + +In some cases, you might want to use the Block List Editor to hold some data and not necessarily render a view since the data should be presented in different areas on a page. An example could be a product page with variants stored in a Block List Editor. + +In this case, you can extract the variant's data using the following, which returns `IEnumerable`. + +Example: + +```csharp +@inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage; +@using ContentModels = Umbraco.Cms.Web.Common.PublishedModels; +@using Umbraco.Cms.Core.Models.Blocks; +@{ + var variants = Model.Value>("variants").Select(x => x.Content); + foreach (var variant in variants) + { +

@variant.Value("variantName")

+

@variant.Value("description")

+ } +} +``` + +If using ModelsBuilder the example can be simplified: + +Example: + +```csharp +@inherits Umbraco.Web.Mvc.UmbracoViewPage +@using ContentModels = Umbraco.Web.PublishedModels; +@{ + var variants = Model.Variants.Select(x => x.Content).OfType(); + foreach (var variant in variants) + { +

@variant.VariantName

+

@variant.Description + } +} +``` + +If you know the Block List Editor only uses a single block, you can cast the collection to a specific type `T` using `.OfType()` otherwise the return value will be `IEnumerable`. + +## Build a Custom Backoffice View + +Building Custom Views for Block representations in Backoffice is the same for all Block Editors. [Read about building a Custom View for Blocks here](../../../../../customizing/extending-overview/extension-types/block-custom-view.md) diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/block-editor/images/block-level-variance-1.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/block-editor/images/block-level-variance-1.png new file mode 100644 index 00000000000..437c13e81e8 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/block-editor/images/block-level-variance-1.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/block-editor/images/block-level-variance-2.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/block-editor/images/block-level-variance-2.png new file mode 100644 index 00000000000..e20614bac8a Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/block-editor/images/block-level-variance-2.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/block-editor/images/block-level-variance-3.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/block-editor/images/block-level-variance-3.png new file mode 100644 index 00000000000..35b5a386e49 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/block-editor/images/block-level-variance-3.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/checkbox-list.md b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/checkbox-list.md new file mode 100644 index 00000000000..c9b965db5c3 --- /dev/null +++ b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/checkbox-list.md @@ -0,0 +1,113 @@ +# Checkbox List + +`Schema Alias: Umbraco.CheckBoxList` + +`UI Alias: Umb.PropertyEditorUi.CheckBoxList` + +`Returns: IEnumerable` + +Displays a list of preset values as a list of checkbox controls. The text saved is an IEnumerable collection of the text values. + +{% hint style="info" %} +Unlike other property editors, the Option IDs are not directly accessible in Razor. +{% endhint %} + +## Data Type Definition Example + +![True/Checkbox List Definition](images/checkbox-list-setup.png) + +{% hint style="info" %} +You can use dictionary items to translate the options in a Checkbox List property editor in a multilingual setup. For more details, see the [Creating a Multilingual Site](../../../../tutorials/multilanguage-setup.md#translating-multi-value-property-editors) article. +{% endhint %} + +## Content Example + +![Checkbox List Example](../../../../../../10/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/checkbox-list-content.png) + +## MVC View Example + +### Without Modelsbuilder + +```csharp +@{ + if (Model.HasValue("superHeros")) + { +

    + @foreach (var item in Model.Value>("superHeros")) + { +
  • @item
  • + } +
+ } +} +``` + +### With Modelsbuilder + +```csharp +@{ + if (Model.SuperHeros.Any()) + { +
    + @foreach (var item in Model.SuperHeros) + { +
  • @item
  • + } +
+ } +} +``` + +## Add values programmatically + +See the example below to see how a value can be added or changed programmatically. To update a value of a property editor you need the [Content Service](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Services.ContentService.html). + +{% hint style="info" %} +The example below demonstrates how to add values programmatically using a Razor view. However, this is used for illustrative purposes only and is not the recommended method for production environments. +{% endhint %} + +```csharp +@inject IContentService Services; +@using Umbraco.Cms.Core.Serialization +@using Umbraco.Cms.Core.Services; +@inject IJsonSerializer Serializer; +@{ + // Get access to ContentService + var contentService = Services; + + // Create a variable for the GUID of the page you want to update + var guid = Guid.Parse("32e60db4-1283-4caa-9645-f2153f9888ef"); + + // Get the page using the GUID you've defined + var content = contentService.GetById(guid); // ID of your page + + // Set the value of the property with alias 'superHeros'. + content.SetValue("superHeros", Serializer.Serialize(new[] { "Umbraco", "CodeGarden"})); + + // Save the change + contentService.Save(content); +} +``` + +Although the use of a GUID is preferable, you can also use the numeric ID to get the page: + +```csharp +@{ + // Get the page using it's id + var content = contentService.GetById(1234); +} +``` + +If Modelsbuilder is enabled you can get the alias of the desired property without using a magic string: + +{% include "../../../../.gitbook/includes/obsolete-warning-ipublishedsnapshotaccessor.md" %} + +```csharp +@inject IPublishedSnapshotAccessor _publishedSnapshotAccessor; +@using Umbraco.Cms.Core.PublishedCache; +@{ + +// Set the value of the property with alias 'superHeros' +content.SetValue(Home.GetModelPropertyType(_publishedSnapshotAccessor,x => x.SuperHeros).Alias, Serializer.Serialize(new[] { "Umbraco", "CodeGarden"})); +} +``` diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/collection.md b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/collection.md new file mode 100644 index 00000000000..89b88a394a6 --- /dev/null +++ b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/collection.md @@ -0,0 +1,116 @@ +# Collection + +`Schema Alias: Umbraco.ListView` + +`UI Alias: Umb.PropertyEditorUi.Collection` + +`Returns: IEnumerable` + +**Collection** displays a collection of categories when it is enabled on a Document Type with children. + +![Collection example](images/listview-v14.png) + +## Configure Collection + +Once Collections are configured, the parent content item displays its child items in a list view format within the content item itself. If Collections are not configured, the child items are displayed directly in the Content Tree, rather than being grouped within the parent content item. + +![Enable Collection example](images/enable-listview-v14.png) + +## Settings + +![Collection settings example](../../../../.gitbook/assets/collection-settings-example-15-1.png) + +### Columns Displayed + +It is possible to add more columns to the collection, via adding the properties through the picker modal. These properties are based on the Data Types which are used by the Document Type. The properties will listed for selection. + +![Collection property picker example](images/collection-property-picker.png) + +Once you have selected a column you want to display, define what its heading label should be and what kind of value it should display. You can also move the headers around, re-ordering how they should look. This is done by the move icon on the left side of the alias. + +The template section is where you define what kind of value you want to display. The value of the column is in the `value` variable. + +### Layouts + +Collection comes with two layouts by default. A list and a grid view. These views can be disabled if you are not interested in any of them. + +{% hint style="info" %} +A minimum of one layout needs to be enabled for Collection to work. +{% endhint %} + +You can also make your own layout and add it to the settings. For example, if you wanted to change the width or length of the grid, you will be able to do so. + +### Order By + +Will sort your collection by the selection you choose in the dropdown. By default it selects "Last edited" and you get the following three columns: + +* **Last edited** - When the content node was last edited and saved. +* **Name** - Name of the content node(s). +* **Created by** - This is the user who the content node was created by. + +You can add more sorting to this collection by adding more datatypes to the columns in the "Columns Displayed" section. + +### Order Direction + +You can select order of the content nodes displayed, "Ascending [a-z]" or "Descending [z-a]". The order is affected by the "Order By" selection. + +### Page Size + +Defines how many child content nodes you want to see per page. This will limit how many content items you will see in your collection. If you set it to 5, then only 5 content items will be shown in the collection. + +### Workspace View icon + +{% hint style="info" %} +Support for changing the Workspace View icon has not been implemented yet. +{% endhint %} + +Changes the icon in the backoffice of the collection. By default it will look like the image below. + +![Collection icon example](../../../../../../10/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/list-icon.png) + +### Workspace View name + +{% hint style="info" %} +Support for changing the Workspace View name has not been implemented yet. +{% endhint %} + +You can change the name of the collection itself. Default if empty: 'Child Items'. + +### Show Content Workspace View First + +{% hint style="info" %} +Support for setting the Content Workspace View First has not been implemented yet. +{% endhint %} + +Enable this to show the Content Workspace View by default instead of the collection's. + +## Content Example + +### Generic field value + +This example shows how to use a generic field from a child item and display its value in a collection. + +![Collection content email label template](images/collection-label-template.png) + +You can use the [Umbraco Flavored Markdown](../../../../reference/umbraco-flavored-markdown.md) syntax to display the label value. Here, the `{=value}` placeholder retrieves the value of the *Email* property and displays it in the collection, as shown in the image below: + +![Collection content email value displayed](images/collections-display-email.png) + +### Content name + +First, a Content Picker property needs to be present on the content item. In this example, the `child item` has gotten a Content Picker Data Type with the alias of `contentPicker`. + +![Collection content picker](images/content-picker-property.png) + +The child item has a document and the value that should be displayed is the name of the picked value. The next step is to reconfigure the template value in the collection setting. + +![Collection content picker](images/collection-column-content-picker.png) + +This will take the value picked up by the content picker. + +![Collection content picker with picked value](images/content-picker-picked-value.png) + +And display it in the collection. Shown in the example below: + +![Collection view cards with content picker value](images/collection-view-cards-content-picker.png) + diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/color-picker.md b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/color-picker.md new file mode 100644 index 00000000000..f6399f438bd --- /dev/null +++ b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/color-picker.md @@ -0,0 +1,132 @@ +# Color Picker + +`Schema Alias: Umbraco.ColorPicker` + +`UI Alias: Umb.PropertyEditorUi.ColorPicker` + +`Returns: String (Hexadecimal)` + +`Returns: Umbraco.Cms.Core.PropertyEditors.ValueConverters.ColorPickerValueConverter.PickedColor (When using labels)` + +The Color picker allows you to set some predetermined colors that the editor can choose between. + +It is possible to add a label to use with the color. + +## Data Type Definition Example + +![Color Picker Data Type Definition](images/Color-Picker-DataType.png) + +## Content Example + +![Color Picker Content](../../../../../../10/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Color-Picker-Content-v8.png) + +## Example with Modelsbuilder + +```csharp +@{ + // Model has a property called "Color" which holds a Color Picker editor + var hexColor = Model.Color; + // Define the label if you've included it + String colorLabel = Model.Color.Label; + + if (hexColor != null) + { +
@colorLabel
+ } +} +``` + +## Example without Modelsbuilder + +```csharp +@using Umbraco.Cms.Core.PropertyEditors.ValueConverters +@{ + // Model has a property called "Color" which holds a Color Picker editor + var hexColor = Model.Value("Color"); + // Define the label if you've included it + var colorLabel = Model.Value("Color").Label; + + if (hexColor != null) + { +
@colorLabel
+ } +} +``` + +## Add values programmatically + +See the example below to see how a value can be added or changed programmatically. To update a value of a property editor you need the [Content Service](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Services.ContentService.html). + +{% hint style="info" %} +The example below demonstrates how to add values programmatically using a Razor view. However, this is used for illustrative purposes only and is not the recommended method for production environments. +{% endhint %} + +### Without labels + +```csharp +@inject IContentService Services; +@using Umbraco.Cms.Core.Services; +@{ + // Get access to ContentService + var contentService = Services; + + // Create a variable for the GUID of the page you want to update + var guid = Guid.Parse("32e60db4-1283-4caa-9645-f2153f9888ef"); + + // Get the page using the GUID you've defined + var content = contentService.GetById(guid); // ID of your page + + // Set the value of the property with alias 'color'. + // The value set here, needs to be one of the colors on the Color Picker + content.SetValue("color", "38761d"); + + // Save the change + contentService.Save(content); +} +``` + +### With labels + +```csharp +@inject IContentService Services; +@using Umbraco.Cms.Core.Services; +@{ + // Get access to ContentService + var contentService = Services; + + // Create a variable for the GUID of the page you want to update + var guid = Guid.Parse("32e60db4-1283-4caa-9645-f2153f9888ef"); + + // Get the page using the GUID you've defined + var content = contentService.GetById(guid); // ID of your page + + // Set the value of the property with alias 'color'. + // The value set here, needs to be one of the colors on the Color Picker + content.SetValue("color", "{'value':'000000', 'label':'Black', 'sortOrder':1, 'id':'1'}"); + + // Save the change + contentService.Save(content); +} +``` + +Although the use of a GUID is preferable, you can also use the numeric ID to get the page: + +```csharp +@{ + // Get the page using it's id + var content = contentService.GetById(1234); +} +``` + +If Modelsbuilder is enabled you can get the alias of the desired property without using a magic string: + +{% include "../../../../.gitbook/includes/obsolete-warning-ipublishedsnapshotaccessor.md" %} + +```csharp +@inject IPublishedSnapshotAccessor _publishedSnapshotAccessor; +@using Umbraco.Cms.Core.PublishedCache; +@{ + // Set the value of the property with alias 'color' + content.SetValue(Home.GetModelPropertyType(_publishedSnapshotAccessor, x => x.Color).Alias, "38761d"); +} +``` diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/content-picker.md b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/content-picker.md new file mode 100644 index 00000000000..59f53d6e103 --- /dev/null +++ b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/content-picker.md @@ -0,0 +1,291 @@ +# Content Picker + +`Schema Alias: Umbraco.ContentPicker` + +`UI Alias: Umb.PropertyEditorUi.DocumentPicker` + +`Returns: IEnumerable` + +The Content Picker enables choosing the type of content tree to display and which specific part to render. It also allows you to set a dynamic root node for the content based on the current document using the Content Picker. + +{% hint style="info" %} +The Content Picker was formerly known as the **Multinode Treepicker** in version 13 and below. + +The renaming is purely a client-side UI change, meaning the property editor still uses the `Umbraco.MultiNodeTreePicker` schema alias. + +The change was made as the word **Content** in the backoffice acts as an umbrella term covering Documents, Media, and Members. + +**Are you looking for the original Content Picker?** + +The Content Picker from version 13 and below has been renamed [Document Picker](document-picker.md). +{% endhint %} + +## Data Type Definition Example + +![Content Picker Data Type Settings](../../../../.gitbook/assets/ContentPicker-data-type-definition.png) + +### Minimum/maximum number of items + +Define a limit on the number of items allowed to be selected. + +### Ignore user start nodes + +Checking this field allows users to choose nodes they normally cannot access. + +### Node Type + +This option allows for configuring what type of content is available when using the Data Type. The available types of content are Content, Members, or Media items. + +When selecting Content from the dropdown, the option to specify a root node, also called the **origin**, becomes available. + +

The option to specify a root node also called the "origin" becomes available when Content is selected as the Node Type.

+ + + +When picking the **origin** there are several different options available: + +

The available options for setting a root node (origin) for the Content Picker.

+ +The following options are available when picking the origin: + +* **Root**: The root is the first level item of the sub-tree of the current node. +* **Parent**: The parent is the nearest ancestor of the current node. +* **Current**: The current node. + * A picker that uses the current node, cannot pick anything when the current node is created, as it will not have any children. +* **Site**: The nearest ancestor of the current node with a domain assigned. +* **Specific node**: A specific node selected from the existing content. + +When an origin has been specified, it becomes possible to continue to build a _Dynamic Root_ by adding additional query steps. + +Navigate the content tree relative to the specified origin to execute multiple query steps and find the root node needed. + +![The default options for executing additional steps to locate the Dynamic Root.](../../../../.gitbook/assets/append-step-to-query.png) + +The following options are available: + +* **Nearest Ancestor or Self:** Find the nearest ancestor or current item that fits with one of the configured Document Types. +* **Furthest Ancestor or Self:** Find the furthest ancestor or current item that fits with one of the configured Document Types. +* **Nearest Descendant or Self:** Find the nearest descendant or current item that fits with one of the configured Document Types. +* **Furthest Descendant or Self:** Find the furthest descendant or current item that fits with one of the configured Document Types. + +The options above are all based on navigating the document hierarchy by locating ancestors and descendants. It is possible to execute **custom steps** to build even more complex queries. Once a custom query is available it will be added to the bottom of the _Append steps to query_ dialog. Learn more about [adding custom query steps in the section below](content-picker.md#adding-a-custom-query-step). + +Each query step takes the output from the origin or the previous step as input. It is only ever the result of the last query step that is passed to the next step. + +![Query steps appended to a Content Picker with the type Content.](../../../../.gitbook/assets/content-picker-query-steps.png) + +#### Adding a custom query step + +Custom query steps can be used to solve specific use cases, such as traversing sibling documents or matching property values. Before the custom query steps can be selected in the Data Type settings, they must be defined via code. + +When implementing a query step it requires a collection of origins and information about the query step. The collection is taken from where the name specified in the UI can be found. + +{% hint style="warning" %} +**Specifying the origin is required** for the custom query step to become available. + +Read the [Node Type section](content-picker.md#node-type) above to learn more about this. +{% endhint %} + +You can inject dependencies into the constructor. These dependencies could be custom repositories or the `IVariationContextAccessor`, if you want to use the current culture. + +The `ExecuteAsync` method receives a set of content keys from the last executed query step or the origin. It has to return a new set of content keys. + +```csharp +public class MyCustomDynamicRootQueryStep : IDynamicRootQueryStep +{ + private readonly IMyCustomRepository _myCustomRepository; + + public MyCustomDynamicRootQueryStep(IMyCustomRepository myCustomRepository) + { + _myCustomRepository = myCustomRepository; + } + + // The string below is what you specify in the UI to execute this custom query step. + public virtual string SupportedDirectionAlias { get; set; } = "MyCustomStep"; + + public async Task>> ExecuteAsync(ICollection origins, DynamicRootQueryStep filter) + { + if (filter.Alias != SupportedDirectionAlias) + { + return Attempt>.Fail(); + } + + if (origins.Any() is false) + { + return Attempt>.Succeed(Array.Empty()); + } + + // Replace the following with your custom logic + var result = await _myCustomRepository.GetWhateverIWantAsync(origins); + + return Attempt>.Succeed(result); + } +} +``` + +To register the custom query step, append it to the existing query steps, `DynamicRootSteps()`. This is done from a composer as shown below. + +```csharp +public class CustomQueryStepComposer : IComposer +{ + public void Compose(IUmbracoBuilder builder) + { + builder.DynamicRootSteps().Append(); + } +} +``` + +Finally, register the custom query step on the client side and provide a brief description. + +You can do this in an `umbraco-package.json` file, as shown below: + +```json +{ + "$schema": "../../umbraco-package-schema.json", + "name": "My.Test.Extension", + "version": "0.1.0", + "extensions": [ + { + "type": "dynamicRootQueryStep", + "alias": "Umb.DynamicRootQueryStep.MyCustomStep", + "name": "Dynamic Root Query Step: My Custom Step", + "meta": { + "queryStepAlias": "MyCustomStep", + "label": "My Custom Step", + "description": "My custom step description.", + "icon": "icon-coffee" + }, + "weight": 0 + } + ] +} +``` + +### Allow items of type + +Choose which types of content should be available to pick using the Content Picker. + +This is done by selecting one or more Document Types. + +## Query Example + +Consider the following tree structure where the Document Type alias is presented in square brackets. + +* Codegarden + * 2023 \[`year`] + * Talks \[`talks`] + * ... + * Umbraco anno MMXXIII \[`talk`] + * Stages \[`stages`] + * Social Space \[`stage`] + * No 10 \[`stage`] + * No 16 \[`stage`] + * The Theatre \[`stage`] + * 2022 \[`year`] + * Talks \[`talks`] + * ... + * Stages \[`stages`] + * Main Stage \[`stage`] + * The Barn \[`stage`] + * The Theatre \[`stage`] + +Consider configuring a Content Picker on the `talk` Document Type to select a `stage` for the `talk`. Here, you want to display only the stages for the actual year. To do this, you need to set the parent as the origin. + +For instance, if you are on the `Umbraco anno MMXXIII` node, the collection of content keys passed into the first query step will only contain the `Talks` content node. + +* First, query for the nearest ancestors of the type `year`. This will return `2023`. +* Second, query for the nearest descendants of the type `stages`. + +When opening the picker on the `Umbraco anno MMXXIII` node, it will now show the children of the node on the path `Codegarden > 2023 > Stages`. + +## MVC View Example + +### Without Modelsbuilder + +```csharp +@{ + var typedContentPicker = Model.Value>("featuredArticles"); + if (typedContentPicker != null) { + foreach (var item in typedContentPicker) + { +

@item.Name

+ } +} +``` + +### With Modelsbuilder + +```csharp +@{ + var typedContentPicker = Model.FeaturedArticles; + foreach (var item in typedContentPicker) + { +

@item.Name

+ } +} +``` + +## Add values programmatically + +See the example below to see how a value can be added or changed programmatically. To update the value of a property editor you need the [Content Service](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Services.ContentService.html). + +{% hint style="info" %} +The example below demonstrates how to add values programmatically using a Razor view. However, this is used for illustrative purposes only and is not the recommended method for production environments. +{% endhint %} + +```csharp +@inject IContentService Services; +@using Umbraco.Cms.Core; +@using Umbraco.Cms.Core.Services + +@{ + // Get access to ContentService + var contentService = Services; + + // Create a variable for the GUID of the page you want to update + var guid = Guid.Parse("32e60db4-1283-4caa-9645-f2153f9888ef"); + + // Get the page using the GUID you've defined + var content = contentService.GetById(guid); // ID of your page + + // Get the pages you want to assign to the Content Picker + var page = Umbraco.Content("665d7368-e43e-4a83-b1d4-43853860dc45"); + var anotherPage = Umbraco.Content("1f8cabd5-2b06-4ca1-9ed5-fbf14d300d59"); + + // Create Udi's of the pages + var pageUdi = Udi.Create(Constants.UdiEntityType.Document, page.Key); + var anotherPageUdi = Udi.Create(Constants.UdiEntityType.Document, anotherPage.Key); + + // Create a list of the page udi's + var udis = new List{pageUdi.ToString(), anotherPageUdi.ToString()}; + + // Set the value of the property with alias 'featuredArticles'. + content.SetValue("featuredArticles", string.Join(",", udis)); + + // Save the change + contentService.Save(content); +} +``` + +Although the use of a GUID is preferable, you can also use the numeric ID to get the page: + +```csharp +@{ + // Get the page using it's id + var content = contentService.GetById(1234); +} +``` + +If Modelsbuilder is enabled you can get the alias of the desired property without using a magic string: + +{% include "../../../../.gitbook/includes/obsolete-warning-ipublishedsnapshotaccessor.md" %} + +```csharp +@inject IPublishedSnapshotAccessor _publishedSnapshotAccessor; +@using Umbraco.Cms.Core.PublishedCache; + +@{ + // Set the value of the property with alias 'featuredArticles' + content.SetValue(Home.GetModelPropertyType(_publishedSnapshotAccessor ,x => x.FeaturedArticles).Alias, string.Join(",", udis)); +} +``` diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/date-time.md b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/date-time.md new file mode 100644 index 00000000000..d566af2ee51 --- /dev/null +++ b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/date-time.md @@ -0,0 +1,87 @@ +# DateTime + +`Schema Alias: Umbraco.DateTime` + +`UI Alias: Umb.PropertyEditorUi.DatePicker` + +`Returns: DateTime` + +Displays a calendar UI for selecting dates which are saved as a DateTime value. + +## Data Type Definition Example + +![Data Type Definiton](images/date-time.png) + +There is one setting available for manipulating the DateTime property. + +The setting involves defining the format. The default date format in the Umbraco backoffice is `YYYY-MM-DD HH:mm:ss`, but you can change it to a different format. See [MomentJS.com](https://momentjs.com/) for the supported formats. + +## Content Example + +![Content Example](../built-in-property-editors/images/date-picker-v8.png) + +## MVC View Example - displays a datetime + +### With Modelsbuilder + +```csharp +@Model.DatePicker +``` + +### Without Modelsbuilder + +```csharp +@Model.Value("datePicker") +``` + +## Add values programmatically + +See the example below to see how a value can be added or changed programmatically. To update a value of a property editor you need the [Content Service](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Services.ContentService.html). + +{% hint style="info" %} +The example below demonstrates how to add values programmatically using a Razor view. However, this is used for illustrative purposes only and is not the recommended method for production environments. +{% endhint %} + +```csharp +@inject IContentService Services; +@using Umbraco.Cms.Core.Services; +@{ + // Get access to ContentService + var contentService = Services; + + // Create a variable for the GUID of the page you want to update + var guid = new Guid("32e60db4-1283-4caa-9645-f2153f9888ef"); + + // Get the page using the GUID you've defined + var content = contentService.GetById(guid); // ID of your page + + // Set the value of the property with alias 'datePicker' + content.SetValue("datePicker", DateTime.Now); + + // Save the change + contentService.Save(content); +} +``` + +Although the use of a GUID is preferable, you can also use the numeric ID to get the page: + +```csharp +@{ + // Get the page using it's id + var content = contentService.GetById(1234); +} +``` + +If Modelsbuilder is enabled you can get the alias of the desired property without using a magic string: + +{% include "../../../../.gitbook/includes/obsolete-warning-ipublishedsnapshotaccessor.md" %} + +```csharp +@using Umbraco.Cms.Core.PublishedCache; +@inject IPublishedSnapshotAccessor _publishedSnapshotAccessor; +@{ + + // Set the value of the property with alias 'datePicker' + content.SetValue(Home.GetModelPropertyType(_publishedSnapshotAccessor, x => x.DatePicker).Alias, DateTime.Now); +} +``` diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/date.md b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/date.md new file mode 100644 index 00000000000..f570390121e --- /dev/null +++ b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/date.md @@ -0,0 +1,39 @@ +# Date + +`Schema Alias: Umbraco.DateTime` + +`UI Alias: Umb.PropertyEditorUi.DatePicker` + +`Returns: Date` + +Displays a calendar UI for selecting dates which are saved as a DateTime value. + +## Data Type Definition Example + +![Data Type Definition Example](../../../../../../10/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/DateTime-DataType.png) + +The only setting that is available for manipulating the Date property is to set a format. By default the format of the date in the Umbraco backoffice will be `YYYY-MM-DD`, but you can change this to something else. See [MomentJS.com](https://momentjs.com/) for the supported formats. + +## Content Example + +![Content Example](../../../../../../10/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Date-Time-Content.png) + +## MVC View Example - displays a datetime + +### Typed + +```csharp +@(Model.Content.GetPropertyValue("datePicker").ToString("dd MM yyyy")) +``` + +### Dynamic (Obsolete) + +{% hint style="warning" %} +See [Common pitfalls](../../../../reference/common-pitfalls.md) for more information about why the dynamic approach is obsolete. +{% endhint %} + +```csharp +@{ + @CurrentPage.datePicker.ToString("dd-MM-yyyy") +} +``` diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/decimal.md b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/decimal.md new file mode 100644 index 00000000000..069f5f13722 --- /dev/null +++ b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/decimal.md @@ -0,0 +1,86 @@ +# Decimal + +`Schema Alias: Umbraco.Decimal` + +`UI Alias: Umb.PropertyEditorUi.Decimal` + +`Returns: decimal` + +## Data Type Definition Example + +![Decimal Content Example](images/content-example.png) + +In the example above the possible values for the input field would be \[8, 8.5, 9, 9.5, 10] + +_All other values will be removed in the content editor when saving or publishing._ + +If the value of **Step Size** is not set then all decimal values between 8 and 10 is possible to input in the content editor. + +## Content Example + +![Content Example](../../../../../../10/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/content-example.png) + +## MVC View Example + +### With Modelsbuilder + +```csharp +@Model.MyDecimal +``` + +### Without Modelsbuilder + +```csharp +@Model.Value("MyDecimal") +``` + +## Add values programmatically + +See the example below to see how a value can be added or changed programmatically. To update a value of a property editor you need the [Content Service](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Services.ContentService.html). + +{% hint style="info" %} +The example below demonstrates how to add values programmatically using a Razor view. However, this is used for illustrative purposes only and is not the recommended method for production environments. +{% endhint %} + +```csharp +@inject IContentService Services; +@using Umbraco.Cms.Core.Services; +@{ + // Get access to ContentService + var contentService = Services; + + // Create a variable for the GUID of the page you want to update + var guid = Guid.Parse("32e60db4-1283-4caa-9645-f2153f9888ef"); + + // Get the page using the GUID you've defined + var content = contentService.GetById(guid); // ID of your page + + // Set the value of the property with alias 'myDecimal'. + content.SetValue("myDecimal", 3); + + // Save the change + contentService.Save(content); +} +``` + +Although the use of a GUID is preferable, you can also use the numeric ID to get the page: + +```csharp +@{ + // Get the page using it's id + var content = contentService.GetById(1234); +} +``` + +If Modelsbuilder is enabled you can get the alias of the desired property without using a magic string: + +{% include "../../../../.gitbook/includes/obsolete-warning-ipublishedsnapshotaccessor.md" %} + +```csharp +@inject IPublishedSnapshotAccessor _publishedSnapshotAccessor; +@using Umbraco.Cms.Core.PublishedCache; +@{ + // Set the value of the property with alias 'myDecimal' + content.SetValue(Home.GetModelPropertyType(_publishedSnapshotAccessor, x => x.MyDecimal).Alias, 3); +} +``` diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/document-picker.md b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/document-picker.md new file mode 100644 index 00000000000..cf312983c59 --- /dev/null +++ b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/document-picker.md @@ -0,0 +1,111 @@ +# Document Picker + +`Schema Alias: Umbraco.ContentPicker` + +`UI Alias: Umb.PropertyEditorUi.DocumentPicker` + +`Returns: IPublishedContent` + +The Document Picker opens a panel to pick a specific page from the content structure. The value saved is the selected nodes [UDI](../../../../reference/querying/udi-identifiers.md). + +{% hint style="info" %} +The Document Picker was formerly known as the **Content Picker** in version 13 and below. + +The renaming is purely a client-side UI change, meaning the property editor still uses the `Umbraco.ContentPicker` schema alias. + +The change was made as the word **Content** in the backoffice acts as an umbrella term covering the terms Document, Media, and Member. +{% endhint %} + +## Data Type Definition Example + +![Document Picker Data Type Definition](images/Document-Picker-DataType.png) + +## Document Picker Example + +![Document Picker Content](../../../../../../10/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Content-Picker-Content-v10.png) + +## MVC View Example + +### Without Modelsbuilder + +```csharp +@{ + IPublishedContent typedContentPicker = Model.Value("featurePicker"); + if (typedContentPicker != null) + { +

@typedContentPicker.Name

+ } +} +``` + +### With Modelsbuilder + +```csharp +@{ + IPublishedContent typedContentPicker = Model.FeaturePicker; + if (typedContentPicker != null) + { +

@typedContentPicker.Name

+ } +} +``` + +## Add values programmatically + +See the example below to see how a value can be added or changed programmatically. To update a value of a property editor you need the [Content Service](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Services.ContentService.html). + +{% hint style="info" %} +The example below demonstrates how to add values programmatically using a Razor view. However, this is used for illustrative purposes only and is not the recommended method for production environments. +{% endhint %} + +```csharp +@using Umbraco.Cms.Core.Services; + +@inject IContentService Services; +@{ + // Get access to ContentService + var contentService = Services; + + // Create a variable for the GUID of the page you want to update + var guid = Guid.Parse("32e60db4-1283-4caa-9645-f2153f9888ef"); + + // Get the page using the GUID you've defined + var content = contentService.GetById(guid); // ID of your page + + // Get the page you want to assign to the document picker + var page = Umbraco.Content("665d7368-e43e-4a83-b1d4-43853860dc45"); + + // Create an Udi of the page + var udi = Udi.Create(Constants.UdiEntityType.Document, page.Key); + + // Set the value of the property with alias 'featurePicker'. + content.SetValue("featurePicker", udi.ToString()); + + // Save the change + contentService.Save(content); +} +``` + +Although the use of a GUID is preferable, you can also use the numeric ID to get the page: + +```csharp +@{ + // Get the page using it's id + var content = contentService.GetById(1234); +} +``` + +If Modelsbuilder is enabled you can get the alias of the desired property without using a magic string: + +{% include "../../../../.gitbook/includes/obsolete-warning-ipublishedsnapshotaccessor.md" %} + +```csharp +@using Umbraco.Cms.Core.PublishedCache; +@using Umbraco.Cms.Core; + +@inject IPublishedSnapshotAccessor _publishedSnapshotAccessor; +@{ + // Set the value of the property with alias 'featurePicker' + content.SetValue(Home.GetModelPropertyType(_publishedSnapshotAccessor, x => x.FeaturePicker).Alias, udi.ToString()); +} +``` diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/dropdown/README.md b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/dropdown/README.md new file mode 100644 index 00000000000..965dfd2afe5 --- /dev/null +++ b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/dropdown/README.md @@ -0,0 +1,139 @@ +# Dropdown + +`Schema Alias: Umbraco.DropDown.Flexible` + +`UI Alias: Umb.PropertyEditorUi.Dropdown` + +`Returns: String` or `IEnumerable` + +Displays a list of preset values. Either a single value or multiple values (formatted as a collection of strings) can be returned. + +## Settings + +### Enable multiple choice + +If enabled, editors will be able to select multiple values from the dropdown otherwise only a single value can be selected. + +### Add options + +Options are the values which are shown in the dropdown list. You can add, edit, or remove values here. + +{% hint style="info" %} +You can use dictionary items to translate the options in a Dropdown property editor in a multilingual setup. For more details, see the [Creating a Multilingual Site](../../../../../tutorials/multilanguage-setup.md#translating-multi-value-property-editors) article. +{% endhint %} + +## Data Type Definition Example + +![Dropdown-data-type](../images/Dropdown-DataType.png) + +## Content Example + +### Single Value + +![Single dropdown content example](../../../../../../../10/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/dropdown/images/DropdownSingle-Content.png) + +### Multiple Values + +![Multiple dropdown content example](../../../../../../../10/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/dropdown/images/DropdownMultiple-Content.png) + +## MVC View Example + +### Single item - without Modelsbuilder + +```csharp +@if (Model.HasValue("category")) +{ +

@(Model.Value("category"))

+} +``` + +### Multiple items - without Modelsbuilder + +```csharp +@if (Model.HasValue("categories")) +{ + var categories = Model.Value>("categories"); +
    + @foreach (var category in categories) + { +
  • @category
  • + } +
+} +``` + +### Single item - with Modelsbuilder + +```csharp +@if (!Model.HasValue(Model.Category)) +{ +

@Model.Category

+} +``` + +### Multiple items - with Modelsbuilder + +```csharp +@if (Model.Categories.Any()) +{ +
    + @foreach (var category in Model.Categories) + { +
  • @category
  • + } +
+} +``` + +## Add values programmatically + +See the example below to see how a value can be added or changed programmatically. To update a value of a property editor you need the [Content Service](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Services.ContentService.html). + +{% hint style="info" %} +The example below demonstrates how to add values programmatically using a Razor view. However, this is used for illustrative purposes only and is not the recommended method for production environments. +{% endhint %} + +```csharp +@using Umbraco.Cms.Core.Services; +@inject IContentService Services; +@using Umbraco.Cms.Core.Serialization +@inject IJsonSerializer Serializer; +@{ + // Get access to ContentService + var contentService = Services; + + // Create a variable for the GUID of the page you want to update + var guid = Guid.Parse("32e60db4-1283-4caa-9645-f2153f9888ef"); + + // Get the page using the GUID you've defined + var content = contentService.GetById(guid); // ID of your page + + // Set the value of the property with alias 'categories'. + content.SetValue("categories", Serializer.Serialize(new[] { "News" })); + + // Save the change + contentService.Save(content); +} +``` + +Although the use of a GUID is preferable, you can also use the numeric ID to get the page: + +```csharp +@{ + // Get the page using it's id + var content = contentService.GetById(1234); +} +``` + +If Modelsbuilder is enabled you can get the alias of the desired property without using a magic string: + +{% include "../../../../../.gitbook/includes/obsolete-warning-ipublishedsnapshotaccessor.md" %} + +```csharp +@using Umbraco.Cms.Core.PublishedCache; +@inject IPublishedSnapshotAccessor _publishedSnapshotAccessor; +@{ + // Set the value of the property with alias 'categories' + content.SetValue(Home.GetModelPropertyType(_publishedSnapshotAccessor, x => x.Categories).Alias, Serializer.Serialize(new[] { "News" })); +} +``` diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/email-address.md b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/email-address.md new file mode 100644 index 00000000000..73085cfc942 --- /dev/null +++ b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/email-address.md @@ -0,0 +1,79 @@ +--- +description: In this article you can learn how to use the build in email property editor +--- + +# Email Address + +`Schema Alias: Umbraco.EmailAddress` + +`UI Alias: Umb.PropertyEditorUi.EmailAddress` + +`Returns: String` + +Displays an email address. + +## Settings + +The Email Address Property Editor does not come with any further configuration. The property can be configured once it has been added to a Document Type. + +![Checkbox Example](images/emailaddress-datatype.png) + +## Content Example + +![Single email address content example](../../../../../../10/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/EmailAddress-Content-v10.png) + +## MVC View Example + +### Without Modelsbuilder + +```csharp +@if (Model.HasValue("email")) +{ + var emailAddress = Model.Value("email"); +

@emailAddress

+} +``` + +### With Modelsbuilder + +```csharp +@if (!string.IsNullOrWhiteSpace(Model.Email)) +{ +

@Model.Email

+} +``` + +## Add value programmatically + +See the example below to learn how a value can be added or changed programmatically to an Email-address property. To update a value of a property editor you need the [Content Service](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Services.ContentService.html). + +{% hint style="info" %} +The example below demonstrates how to add values programmatically using a Razor view. However, this is used for illustrative purposes only and is not the recommended method for production environments. +{% endhint %} + +```csharp +@using Umbraco.Cms.Core.Services; + +@inject IContentService Services; +@{ + // Get access to ContentService + var contentService = Services; + + // Create a variable for the GUID of your page + var guid = new Guid("796a8d5c-b7bb-46d9-bc57-ab834d0d1248"); + + // Get the page using the GUID you've just defined + var content = contentService.GetById(guid); + // Set the value of the property with alias 'email' + content.SetValue("email", "jpk@umbraco.dk"); + + // Save the change + contentService.Save(content); +} +``` + +{% hint style="info" %} +The value sent to an EmailAddress property needs to be a correct email address, For example: [name@domain.com](mailto:name@domain.com). + +It is recommended that you set up validation on this property, in order to verify whether the value added is in the correct format. +{% endhint %} diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/eye-dropper-color-picker.md b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/eye-dropper-color-picker.md new file mode 100644 index 00000000000..38430db5703 --- /dev/null +++ b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/eye-dropper-color-picker.md @@ -0,0 +1,97 @@ +# Eye Dropper Color Picker + +`Schema Alias: Umbraco.ColorPicker.EyeDropper` + +`UI Alias: Umb.PropertyEditorUi.EyeDropper` + +`Returns: string` + +The Eye Dropper Color picker allows you to choose a color from the full color spectrum using HEX and RGBA. + +## Data Type Definition Example + +![Eye Dropper Color Picker Data Type Definition](images/Eye-Dropper-Color-Picker-DataType.png) + +## Content Example + +![Eye Dropper Color Picker Content](images/Eye-Dropper-Color-Picker-Content.png) + +## Example with Modelsbuilder + +```csharp +@{ + var color = Model.Color?.ToString(); + + if (color != null) + { + + } +} +``` + +## Example without Modelsbuilder + +```csharp +@{ + var color = Model.Value("Color"); + + if (color != null) + { + + } +} +``` + +## Add values programmatically + +See the example below to see how a value can be added or changed programmatically. To update a value of a property editor you need the [Content Service](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Services.ContentService.html). + +{% hint style="info" %} +The example below demonstrates how to add values programmatically using a Razor view. However, this is used for illustrative purposes only and is not the recommended method for production environments. +{% endhint %} + +```csharp +@using Umbraco.Cms.Core.Services; +@inject IContentService Services; +@{ + // Get access to ContentService + var contentService = Services; + + // Create a variable for the GUID of the page you want to update + var guid = Guid.Parse("32e60db4-1283-4caa-9645-f2153f9888ef"); + + // Get the page using the GUID you've defined + var content = contentService.GetById(guid); // ID of your page + + // Set the value of the property with alias 'color'. + content.SetValue("color", "#6fa8dc"); + + // Save the change + contentService.Save(content); +} +``` + +Although the use of a GUID is preferable, you can also use the numeric ID to get the page: + +```csharp +@{ + // Get the page using it's id + var content = contentService.GetById(1234); +} +``` + +If Modelsbuilder is enabled you can get the alias of the desired property without using a magic string: + +{% include "../../../../.gitbook/includes/obsolete-warning-ipublishedsnapshotaccessor.md" %} + +```csharp +@using Umbraco.Cms.Core.PublishedCache; +@inject IPublishedSnapshotAccessor _publishedSnapshotAccessor; +@{ + // Set the value of the property with alias 'color' + content.SetValue(Home.GetModelPropertyType(_publishedSnapshotAccessor, x => x.Color).Alias, "#6fa8dc"); + + // Set the value of the property with alias 'theme' + content.SetValue(Home.GetModelPropertyType(_publishedSnapshotAccessor, x => x.Theme).Alias, "rgba(111, 168, 220, 0.7)"); +} +``` diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/file-upload.md b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/file-upload.md new file mode 100644 index 00000000000..962846a7b68 --- /dev/null +++ b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/file-upload.md @@ -0,0 +1,143 @@ +# File Upload + +`Schema Alias: Umbraco.UploadField` + +`UI Alias: Umb.PropertyEditorUi.UploadField` + +`Returns: string` + +Adds an upload field, which allows documents or images to be uploaded to Umbraco. + +You can define which file types should be accepted through the upload field. + +{% hint style="info" %} +For uploading and adding files and images to your Umbraco project, we recommend using the Media Picker. + +Find the full documentation for the property in the [Media Picker](media-picker-3.md) article. +{% endhint %} + +## Data Type Definition Example + +![File Upload Definition](images/file-upload-definition.png) + +## Content Example + +![Content Example Empty](../../../../../../10/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/content-example-empty.png) ![Content Example](../../../../../../10/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/File-Upload-content-example.png) + +In code, the property is a string, which references the location of the file. + +Example: `"/media/o01axaqu/guidelines-on-remote-working.pdf"` + +## MVC View Example + +### Without Modelsbuilder + +```csharp +@using System.IO; +@{ + if (Model.HasValue("myFile")) + { + var myFile = Model.Value("myFile"); + + @System.IO.Path.GetFileName(myFile) + } + +} +``` + +### With Modelsbuilder + +```csharp +@if (!Model.HasValue(Model.MyFile)) +{ + @System.IO.Path.GetFileName(Model.MyFile) +} +``` + +## Add values programmatically + +{% hint style="info" %} +The samples in this section have not been verified against the latest version of Umbraco. + +Instead, we recommend using the [Media Picker](media-picker-3.md) for uploading files to your Umbraco website. +{% endhint %} + +See the example below to see how a value can be added or changed programmatically. To update a value of this property editor you need the [Content Service](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Services.ContentService.html) and the [Media Service](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Services.MediaService.html). + +{% hint style="info" %} +The example below demonstrates how to add values programmatically using a Razor view. However, this is used for illustrative purposes only and is not the recommended method for production environments. +{% endhint %} + +```csharp +@using Umbraco.Cms.Core.IO +@using Umbraco.Cms.Core.Serialization +@using Umbraco.Cms.Core.Strings +@inject MediaFileManager _mediaFileManager; +@inject IShortStringHelper _shortStringHelper; +@inject IContentTypeBaseServiceProvider _contentTypeBaseServiceProvider; +@inject IContentService Services; +@inject IJsonSerializer _serializer; +@inject MediaUrlGeneratorCollection _mediaUrlGeneratorCollection; + +@{ + // Get access to ContentService + var contentService = Services; + + // Get access to MediaService + var mediaService = MediaService; + + // Create a variable for the GUID of the parent where you want to add a child item + var guid = Guid.Parse("32e60db4-1283-4caa-9645-f2153f9888ef"); + + // Get the page using the GUID you've defined + var content = contentService.GetById(guid); // ID of your page + + // Create a variable for the file you want to upload, in this case the Our Umbraco logo + var imageUrl = "https://our.umbraco.com/assets/images/logo.svg"; + + // Create a request to get the file + var request = WebRequest.Create(imageUrl); + var webResponse = request.GetResponse(); + var responseStream = webResponse.GetResponseStream(); + + // Get the file name + var lastIndex = imageUrl.LastIndexOf("/", StringComparison.Ordinal) + 1; + var filename = imageUrl.Substring(lastIndex, imageUrl.Length - lastIndex); + + // Create a media file + var media = mediaService.CreateMediaWithIdentity("myImage", -1, "File"); + media.SetValue(_mediaFileManager, _mediaUrlGeneratorCollection, _shortStringHelper, _contentTypeBaseServiceProvider, Constants.Conventions.Media.File, filename, responseStream); + // Save the created media + mediaService.Save(media); + + // Get the published version of the media (IPublishedContent) + var publishedMedia = Umbraco.Media(media.Id); + + // Set the value of the property with alias 'myFile' + content.SetValue("myFile", publishedMedia.Url()); + + // Save the child item + contentService.Save(content); +} +``` + +Although the use of a GUID is preferable, you can also use the numeric ID to get the page: + +```csharp +@{ + // Get the page using it's id + var content = contentService.GetById(1234); +} +``` + +If Modelsbuilder is enabled you can get the alias of the desired property without using a magic string: + +{% include "../../../../.gitbook/includes/obsolete-warning-ipublishedsnapshotaccessor.md" %} + +```csharp +@inject IPublishedSnapshotAccessor _publishedSnapshotAccessor; +@{ + // Set the value of the property with alias 'myFile' + content.SetValue(Home.GetModelPropertyType(_publishedSnapshotAccessor, x => x.MyFile).Alias, publishedMedia.Url(); +} +``` diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/image-cropper.md b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/image-cropper.md new file mode 100644 index 00000000000..5a99d0b7b60 --- /dev/null +++ b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/image-cropper.md @@ -0,0 +1,243 @@ +# Image Cropper + +`Schema Alias: Umbraco.ImageCropper` + +`UI Alias: Umb.PropertyEditorUi.ImageCropper` + +`Returns: MediaWithCrops` + +Returns a path to an image, along with information about focal point and available crops. + +When the Image Cropper is used on a Media Type the crops are shared between all usages of a Media Item. This is called **global crops**. + +If the Image Cropper is used on a Document Type, the file and crops will be **local** to the Document. + +Notice that it is possible make local crops on shared Media Items via the [Media Picker Property Editor](media-picker-3.md). + +## Settings + +### Define Crops + +You can add, edit & delete crop presets the cropper UI can use. + +## Data Type Definition Example + +![Image Cropper Data Type Definition](images/imageCropper.png) + +## Content Example + +The Image Cropper provides a UI to upload an image, set a focal point on the image, and use predefined crops. + +By default, images in the Image Cropper will be shown based on a set focal point and only use specific crops if they are available. + +The Image Cropper comes with 3 modes: + +* Uploading an image +* Setting a focal point +* Cropping the image to predefined crops + +### Uploading images + +The editor exposes a drop area for files. Select it to upload an image. + +![Image Cropper Upload](../../../../../../10/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/imageCropper-upload-v8.png) + +### Set focal point + +By default, the Image Cropper allows the editor to set a focal point on the uploaded image. + +All the preset crops are shown to give the editor a preview of what the image will look like on the frontend. + +![Image Cropper Focal Point](images/imageCropper-focalpoint.png) + +### Crop and resize + +The editor can fit the crop to the image to ensure that the image is presented as intended. + +![Image Cropper crop](images/imageCropper-crop.png) + +## Powered by ImageSharp.Web + +[ImageSharp.Web](https://sixlabors.com/products/imagesharp-web/) is image processing middleware for ASP.NET. + +We bundle this package with Umbraco and you can therefore take full advantage of all its features for resizing and format changing. Learn more about the built in processing commands in [the official ImageSharp documentation](https://docs.sixlabors.com/articles/imagesharp.web/processingcommands.html). + +## Sample code + +The Image Cropper comes with an API to generate crop URLs. You can also access the raw data directly as a dynamic object. + +For rendering a cropped media item, the `.GetCropUrl` is used: + +```html + +``` + +The third parameter is `HtmlEncode` and is by default set to true. This means you only need to define the parameter if you want to disable HTML encoding. + +### Example to output a "banner" crop from a cropper property with the property alias "customCropper" + +```html + +``` + +Or, alternatively using the `MediaWithCrops` extension method: + +```html + +``` + +### Example to dynamically create a crop using the focal point - in this case 300 x 400px image + +```csharp +@if (Model.Photo is not null) +{ + @Model.Photo.Name +} +``` + +### CSS background example to output a "banner" crop + +Set the `htmlEncode` to false so that the URL is not HTML encoded + +```csharp +@if (Model.Photo is not null) +{ + var cropUrl = Url.GetCropUrl(Model.Photo, "square", false); + +
+} +``` + +## Add values programmatically + +To update a content property value you need the [Content Service](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Services.ContentService.html). + +The following sample demonstrates how to add or change the value of an Image Cropper property programmatically. The sample creates an API controller with an action, which must be invoked via a POST request to the URL written above the action. + +```csharp +using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Cms.Core.PropertyEditors.ValueConverters; +using Umbraco.Cms.Core.Serialization; +using Umbraco.Cms.Core.Services; + +namespace Umbraco.Docs.Samples.Web.Property_Editors_Add_Values; + +[ApiController] +[Route("/umbraco/api/createimagecroppervalues")] +public class CreateImageCropperValuesController : Controller +{ + private readonly IContentService _contentService; + private readonly IMediaService _mediaService; + private readonly MediaUrlGeneratorCollection _mediaUrlGeneratorCollection; + private readonly IJsonSerializer serializer; + + public CreateImageCropperValuesController( + IContentService contentService, + IMediaService mediaService, + MediaUrlGeneratorCollection mediaUrlGeneratorCollection, IJsonSerializer serializer) + { + _contentService = contentService; + _mediaService = mediaService; + _mediaUrlGeneratorCollection = mediaUrlGeneratorCollection; + this.serializer = serializer; + } + + // /Umbraco/Api/CreateImageCropperValues/CreateImageCropperValues + [HttpPost("createimagecroppervalues")] + public ActionResult CreateImageCropperValues() + { + // Create a variable for the GUID of the page you want to update + var contentKey = Guid.Parse("89974f8b-e213-4c32-9f7a-40522d87aa2f"); + + // Get the page using the GUID you've defined + IContent? content = _contentService.GetById(contentKey); + if (content == null) + { + return false; + } + + // Create a variable for the GUID of the media item you want to use + var mediaKey = Guid.Parse("b6d4e98a-07c0-45f9-bfcc-52994f2806b6"); + + // Get the desired media file + IMedia? media = _mediaService.GetById(mediaKey); + if (media == null) + { + return false; + } + + // Create a variable for the image cropper and set the source + var imageCropperValue = new ImageCropperValue + { + Src = media.GetUrl("umbracoFile", _mediaUrlGeneratorCollection) + }; + + // Serialize the image cropper value + var propertyValue = serializer.Serialize(imageCropperValue); + + // Set the value of the property with alias "cropper" + // - remember to add the "culture" parameter if "cropper" is set to vary by culture + content.SetValue("cropper", propertyValue); + + return _contentService.Save(content).Success; + } +} +``` + +If you use Models Builder to generate source code (modes `SourceCodeAuto` or `SourceCodeManual`), you can use `nameof([generated property name])` to access the desired property without using a magic string: + +```csharp +// Set the value of the "Cropper" property on content of type MyContentType +// - remember to add the "culture" parameter if "cropper" is set to vary by culture +content.SetValue(nameof(MyContentType.Cropper).ToFirstLowerInvariant(), propertyValue); +``` + +## Get all the crop urls for a specific image + +Crop URLs are not limited to usage within a view. `IPublishedContent` has a `GetCropUrl` extension method, which can be used to access crop URLs anywhere. + +The following sample demonstrates how to use `GetCropUrl` to retrieve URLs for all crops defined on a specific image: + +```csharp +public Dictionary GetCropUrls(IPublishedContent image) +{ + // Get the Image Cropper property value for property with alias "umbracoFile" + ImageCropperValue? imageCropperValue = image.Value("umbracoFile"); + if (imageCropperValue?.Crops == null) + { + return new Dictionary(); + } + + // Return all crop aliases and their corresponding crop URLs as a dictionary + var cropUrls = new Dictionary(); + foreach (ImageCropperValue.ImageCropperCrop crop in imageCropperValue.Crops) + { + // Get the cropped URL and add it to the dictionary that I will return + var cropUrl = crop.Alias != null + ? image.GetCropUrl(crop.Alias) + : null; + if (cropUrl != null) + { + cropUrls.Add(crop.Alias!, cropUrl); + } + } + + return cropUrls; +} +``` + +## Sample on how to change the format of the image + +Below the example to output a PNG using ImageSharp.Web [format](https://docs.sixlabors.com/articles/imagesharp.web/processingcommands.html#format) command. + +```html + +``` diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/images/Checkbox-Data-Type.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/images/Checkbox-Data-Type.png new file mode 100644 index 00000000000..70eab847d64 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/images/Checkbox-Data-Type.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/images/Color-Picker-DataType.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/images/Color-Picker-DataType.png new file mode 100644 index 00000000000..c6ea4a2cd1f Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/images/Color-Picker-DataType.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/images/Document-Picker-DataType.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/images/Document-Picker-DataType.png new file mode 100644 index 00000000000..18573dd59d2 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/images/Document-Picker-DataType.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/images/Dropdown-DataType.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/images/Dropdown-DataType.png new file mode 100644 index 00000000000..ea1da4a3cfc Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/images/Dropdown-DataType.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/images/Eye-Dropper-Color-Picker-Content.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/images/Eye-Dropper-Color-Picker-Content.png new file mode 100644 index 00000000000..d8ce4de3f88 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/images/Eye-Dropper-Color-Picker-Content.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/images/Eye-Dropper-Color-Picker-DataType.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/images/Eye-Dropper-Color-Picker-DataType.png new file mode 100644 index 00000000000..0f1fb36893a Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/images/Eye-Dropper-Color-Picker-DataType.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/images/Label-Setup.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/images/Label-Setup.png new file mode 100644 index 00000000000..11094fa224c Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/images/Label-Setup.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/images/Markdown-Editor-definition-example.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/images/Markdown-Editor-definition-example.png new file mode 100644 index 00000000000..0b4b46160d6 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/images/Markdown-Editor-definition-example.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/images/Media-picker-dataType.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/images/Media-picker-dataType.png new file mode 100644 index 00000000000..0521ffd75db Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/images/Media-picker-dataType.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/images/MediaPicker-DataType.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/images/MediaPicker-DataType.png new file mode 100644 index 00000000000..bc6ac25056b Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/images/MediaPicker-DataType.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/images/Member-Picker-DataType.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/images/Member-Picker-DataType.png new file mode 100644 index 00000000000..a0510029d2f Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/images/Member-Picker-DataType.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/images/Member-Picker.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/images/Member-Picker.png new file mode 100644 index 00000000000..2f42aa8db1c Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/images/Member-Picker.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/images/Multi-Url-Picker-DataType.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/images/Multi-Url-Picker-DataType.png new file mode 100644 index 00000000000..63981d74060 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/images/Multi-Url-Picker-DataType.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/images/RadioButton-List-DataType.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/images/RadioButton-List-DataType.png new file mode 100644 index 00000000000..ec80c6b02c1 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/images/RadioButton-List-DataType.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/images/Repeatable-Textstrings-DataType.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/images/Repeatable-Textstrings-DataType.png new file mode 100644 index 00000000000..eda135248f9 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/images/Repeatable-Textstrings-DataType.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/images/Slider-Data-Type-Definition.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/images/Slider-Data-Type-Definition.png new file mode 100644 index 00000000000..9620542ac7f Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/images/Slider-Data-Type-Definition.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/images/Textarea-Setup.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/images/Textarea-Setup.png new file mode 100644 index 00000000000..0373a9f3dec Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/images/Textarea-Setup.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/images/Textbox-Setup.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/images/Textbox-Setup.png new file mode 100644 index 00000000000..75804945088 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/images/Textbox-Setup.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/images/User-Picker-DataType.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/images/User-Picker-DataType.png new file mode 100644 index 00000000000..4edc07b73e6 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/images/User-Picker-DataType.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/images/checkbox-list-setup.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/images/checkbox-list-setup.png new file mode 100644 index 00000000000..798ccd419be Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/images/checkbox-list-setup.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/images/collection-column-content-picker.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/images/collection-column-content-picker.png new file mode 100644 index 00000000000..f0e664a416b Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/images/collection-column-content-picker.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/images/collection-label-template.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/images/collection-label-template.png new file mode 100644 index 00000000000..30d0e0033ec Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/images/collection-label-template.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/images/collection-property-picker.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/images/collection-property-picker.png new file mode 100644 index 00000000000..375af70dc02 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/images/collection-property-picker.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/images/collection-view-cards-content-picker.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/images/collection-view-cards-content-picker.png new file mode 100644 index 00000000000..cfb2e51eb9f Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/images/collection-view-cards-content-picker.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/images/collections-display-email.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/images/collections-display-email.png new file mode 100644 index 00000000000..d54e5d4a712 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/images/collections-display-email.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/images/content-example.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/images/content-example.png new file mode 100644 index 00000000000..98474c05999 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/images/content-example.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/images/content-picker-picked-value.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/images/content-picker-picked-value.png new file mode 100644 index 00000000000..9726d5c05ea Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/images/content-picker-picked-value.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/images/content-picker-property.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/images/content-picker-property.png new file mode 100644 index 00000000000..a20251e8876 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/images/content-picker-property.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/images/date-time.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/images/date-time.png new file mode 100644 index 00000000000..260e07b5791 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/images/date-time.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/images/emailaddress-datatype.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/images/emailaddress-datatype.png new file mode 100644 index 00000000000..6806c536bb2 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/images/emailaddress-datatype.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/images/enable-listview-v14.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/images/enable-listview-v14.png new file mode 100644 index 00000000000..f15477d46b6 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/images/enable-listview-v14.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/images/file-upload-definition.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/images/file-upload-definition.png new file mode 100644 index 00000000000..8fd78917069 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/images/file-upload-definition.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/images/imageCropper-crop.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/images/imageCropper-crop.png new file mode 100644 index 00000000000..de2e956bea5 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/images/imageCropper-crop.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/images/imageCropper-focalpoint.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/images/imageCropper-focalpoint.png new file mode 100644 index 00000000000..8124cc53681 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/images/imageCropper-focalpoint.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/images/imageCropper.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/images/imageCropper.png new file mode 100644 index 00000000000..786888e25e9 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/images/imageCropper.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/images/list-view-settings.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/images/list-view-settings.png new file mode 100644 index 00000000000..dad410c9d85 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/images/list-view-settings.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/images/listview-v14.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/images/listview-v14.png new file mode 100644 index 00000000000..63e85069886 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/images/listview-v14.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/images/numeric-datatype.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/images/numeric-datatype.png new file mode 100644 index 00000000000..359e2787a82 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/images/numeric-datatype.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/images/tags-DataType.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/images/tags-DataType.png new file mode 100644 index 00000000000..007770a99a3 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/images/tags-DataType.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/label.md b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/label.md new file mode 100644 index 00000000000..1c9043287aa --- /dev/null +++ b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/label.md @@ -0,0 +1,97 @@ +# Label + +`Schema Alias: Umbraco.Label` + +`UI Alias: Umb.PropertyEditorUi.Label` + +`Returns: String` + +Label is a non-editable control and can only be used to display a pre-set value. + +## Data Type Definition Example + +![Label Data Type definition](images/Label-Setup.png) + +### Value type + +If you want to set a value other than a String, you can define the data using one of the other available Data Types. These include Decimal, Date/time, Time, Integer, and Big integer. + +There is also a Value Type: Long string if you need to set a long string value for your Label. + +## Content Example + +![Label Content Example](../../../../../../10/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Label-Content-v8.png) + +## MVC View Example + +### Without ModelsBuilder + +```csharp +@{ + if (Model.HasValue("pageLabel")){ +

@(Model.Value("pageLabel"))

+ } +} +``` + +### With ModelsBuilder + +```csharp +@{ + if (!string.IsNullOrEmpty(Model.PageLabel)) + { +

@Model.PageLabel

+ } +} +``` + +## Add values programmatically + +See the example below to see how a value can be added or changed programmatically. To update a value of a property editor you need the [Content Service](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Services.ContentService.html). + +{% hint style="info" %} +The example below demonstrates how to add values programmatically using a Razor view. However, this is used for illustrative purposes only and is not the recommended method for production environments. +{% endhint %} + +```csharp +@{ + @inject IContentService Services; + + // Get access to ContentService + var contentService = Services; + + // Create a variable for the GUID of the page you want to update + var guid = Guid.Parse("32e60db4-1283-4caa-9645-f2153f9888ef"); + + // Get the page using the GUID you've defined + var content = contentService.GetById(guid); // ID of your page + + // Set the value of the property with alias 'pageLabel'. + content.SetValue("pageLabel", "A pre-set string value"); + + // Save the change + contentService.Save(content); +} +``` + +Although the use of a GUID is preferable, you can also use the numeric ID to get the page: + +```csharp +@{ + // Get the page using it's id + var content = contentService.GetById(1234); +} +``` + +If Modelsbuilder is enabled you can get the alias of the desired property without using a magic string: + +{% include "../../../../.gitbook/includes/obsolete-warning-ipublishedsnapshotaccessor.md" %} + +```csharp +@{ + @inject IPublishedSnapshotAccessor _publishedSnapshotAccessor + + // Set the value of the property with alias 'pageLabel' + content.SetValue(Home.GetModelPropertyType(_publishedSnapshotAccessor, x => x.MyLabel).Alias, "A Preset string"); +} +``` diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/markdown-editor.md b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/markdown-editor.md new file mode 100644 index 00000000000..67c298f50f5 --- /dev/null +++ b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/markdown-editor.md @@ -0,0 +1,116 @@ +# Markdown Editor + +`Schema Alias: Umbraco.MarkdownEditor` + +`UI Alias: Umb.PropertyEditorUi.MarkdownEditor` + +`Returns: System.Web.HtmlString` + +This built-in editor allow the user to use the markdown formatting options, from within a tinyMCE-like interface. + +## Data Type Definition Example + +![Markdown Editor definition example](images/Markdown-Editor-definition-example.png) + +There are three settings available for manipulating the **Markdown editor** property. + +* **Preview** toggles if a preview of the markdown should be displayed beneath the editor in the content view. +* **Default value** is inserted if no content has been saved to the Document Type using this property editor. +* **Overlay Size** is used to select the width of the link picker overlay in the content view. + +## Content Example + +![Content Example](../../../../../../10/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Markdown-Editor-content-example.png) + +### Explanation of buttons from left to right + +| Function | Shortcut | Further explanation | +| --------------------- | -------- | -------------------------------------- | +| toggle **bold** text | Ctrl + B | | +| toggle _italic_ text | Ctrl + I | | +| insert link | Ctrl + L | This opens the Select Link interface. | +| toggle quote | Ctrl + Q | | +| toggle code block | Ctrl + K | | +| insert image | Ctrl + G | This opens the Select Media interface. | +| toggle ordered list | Ctrl + O | | +| toggle unordered list | Ctrl + U | | +| toggle heading | Ctrl + H | This toggles between h1, h2 and off. | +| toggle a hr | | | +| undo | Ctrl + Z | | +| redo | Ctrl + Y | | + +### Other functionality + +| Function | Shortcut | +| ---------- | -------- | +| select all | Ctrl + A | +| copy | Ctrl + C | +| paste | Ctrl + V | + +## MVC View Example + +### With Modelsbuilder + +```csharp +@Model.MyMarkdownEditor +``` + +### Without Modelsbuilder + +```csharp +@Model.Value("MyMarkdownEditor") +``` + +## Add values programmatically + +See the example below to see how a value can be added or changed programmatically. To update a value of a property editor you need the [Content Service](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Services.ContentService.html). + +{% hint style="info" %} +The example below demonstrates how to add values programmatically using a Razor view. However, this is used for illustrative purposes only and is not the recommended method for production environments. +{% endhint %} + +```csharp +@using Umbraco.Cms.Core.Services; +@inject IContentService Services; +@{ + // Get access to ContentService + var contentService = Services; + + // Create a variable for the GUID of the page you want to update + var guid = Guid.Parse("32e60db4-1283-4caa-9645-f2153f9888ef"); + + // Get the page using the GUID you've defined + var content = contentService.GetById(guid); // ID of your page + + // Create markdown value + var markdownValue = new HtmlString("#heading \n**strong text**"); + + // Set the value of the property with alias 'myMarkdownEditor'. + content.SetValue("myMarkdownEditor", markdownValue); + + // Save the change + contentService.Save(content); +} +``` + +Although the use of a GUID is preferable, you can also use the numeric ID to get the page: + +```csharp +@{ + // Get the page using it's id + var content = contentService.GetById(1234); +} +``` + +If Modelsbuilder is enabled you can get the alias of the desired property without using a magic string: + +{% include "../../../../.gitbook/includes/obsolete-warning-ipublishedsnapshotaccessor.md" %} + +```csharp +@using Umbraco.Cms.Core.PublishedCache; +@inject IPublishedSnapshotAccessor _publishedSnapshotAccessor; +@{ + // Set the value of the property with alias 'myMarkdownEditor' + content.SetValue(Home.GetModelPropertyType(_publishedSnapshotAccessor, x => x.MyMarkdownEditor).Alias, markdownValue); +} +``` diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/media-picker-3.md b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/media-picker-3.md new file mode 100644 index 00000000000..5371ed8c4eb --- /dev/null +++ b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/media-picker-3.md @@ -0,0 +1,220 @@ +# Media Picker + +`Schema Alias: Umbraco.MediaPicker3` + +`UI Alias: Umb.PropertyEditorUi.MediaPicker` + +`Returns: IEnumerable` or `MediaWithCrops` + +This property editors returns one of the following: + +- A collection (`IEnumerable`) if the **Pick multiple items** setting is enabled. +- A single `MediaWithCrops` item if the **Pick multiple items** setting is disabled. + +## Data Type Definition Example + +![Media Picker Data Type Definition](images/MediaPicker-DataType.png) + +### Accepted types + +Use setting to limit the picker to only select Media Items of these types. + +### Pick multiple items + +Use this setting to enable the property to contain multiple items. When this is enabled the property editor returns an `IEnumerable`. + +You can still set the maximum amount to 1. Do so when you want to retrieve a collection but only allow the Content Editors to select one Media Item. + +### Amount + +Use this setting to enforce a minimum and/or maximum amount of selected Media Items. + +{% hint style="info" %} +It is not possible to set a maximum amount when the "Pick multiple items" feature is disabled. +{% endhint %} + +### Start node + +This setting is used to limit the Media Picker to certain parts of the Media Tree. + +### Ignore user start nodes + +Use this setting to overrule user permissions, to enable any user of this property to pick any Media Item of the chosen Start node. + +When this setting is enabled, a user can access the media available under the selected "Start Node" (/Design in this case). This applies even if they normally lack access. The access is granted specifically when using this particular Media Picker. + +### Enable Focal Point + +Enable the focal point setter, do only enable this if the focal point is used or if you have Image crops defined. + +### Image Crops + +Define local image crops. Local image crop data is stored on the document in this property. This means it can differentiate between documents. + +This is different from Global crops as they are defined on the Media Item, making the crops shared between all usage of that Media Item. + +Global crops are configured on the Image Cropper property of the Image Media Type + +[Read about the Image Cropper here](image-cropper.md) + +## Content Example + +![Media Picker Content](../../../../../../10/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Media-Picker3-Content.jpg) + +## MVC View Example + +### Multiple enabled without Modelsbuilder + +```csharp +@using Umbraco.Cms.Core.Models +@{ + var typedMultiMediaPicker = Model.Value>("medias"); + foreach (var entry in typedMultiMediaPicker) + { + + } +} +``` + +#### Multiple enabled without Modelsbuilder to retrieve IEnumerable data + +```csharp +@using Umbraco.Cms.Core.Models +@{ + var listOfImages = Model.Value>("medias"); + foreach (var image in listOfImages) + { + @image.Name + } +} +``` + +{% hint style="info" %} +While `MediaWithCrops` is the default return type, `IPublishedContent` may be used in backward-compatible implementations or when working directly with core APIs. +{% endhint %} + +### Multiple enabled with Modelsbuilder + +```csharp +@{ + var typedMultiMediaPicker = Model.Medias; + foreach (var entry in typedMultiMediaPicker) + { + + } +} +``` + +### Multiple disabled without Modelsbuilder + +```csharp +@using Umbraco.Cms.Core.Models +@{ + var typedMediaPickerSingle = Model.Value("media"); + if (typedMediaPickerSingle != null) + { + @typedMediaPickerSingle.Value( + } +} +``` + +### Multiple disabled with Modelsbuilder + +```csharp +@using Umbraco.Cms.Core.Models +@{ + var typedMediaPickerSingle = Model.Media; + if (typedMediaPickerSingle is MediaWithCrops mediaEntry) + { + + } +} +``` + +## Using crops + +Both local and global crops are retrieved using the method `GetCropUrl`. If crops with identical aliases are defined both locally and globally, the locally defined crops are always prioritized by `GetCropUrl`. + +The following is an example of how to retrieve a crop from a `MediaWithCrops` entry: + +```csharp +@{ + foreach (var entry in Model.Medias) + { + + } +} +``` + +### Explicitly retrieving global crops + +You can retrieve globally defined crops explicitly by using `GetCropUrl` on the `UrlHelper`: + +```csharp +@{ + foreach (var entry in Model.Medias) + { + + } +} +``` + +### Add values programmatically + +See the example below to see how a value can be added or changed programmatically. To update a value of a property editor you need the [Content Service](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Services.ContentService.html). + +The following sample will update a single image in a Media Picker. + +{% hint style="info" %} +The example below demonstrates how to add values programmatically using a Razor view. However, this is used for illustrative purposes only and is not the recommended method for production environments. +{% endhint %} + +```csharp +@using Umbraco.Cms.Core; +@using Umbraco.Cms.Core.Services; +@inject IContentService Services; +@{ + // Get access to ContentService + var contentService = Services; + + // Create a variable for the GUID of the page you want to update + var guid = Guid.Parse("32e60db4-1283-4caa-9645-f2153f9888ef"); + + // Get the page using the GUID you've defined + var content = contentService.GetById(guid); // ID of your page + + // Get the media you want to assign to the media picker + var media = Umbraco.Media("bca8d5fa-de0a-4f2b-9520-02118d8329a8"); + + // Create an Udi of the media + var udi = Udi.Create(Constants.UdiEntityType.Media, media.Key); + + // Set the value of the property with alias 'featuredBanner'. + content.SetValue("featuredBanner", udi.ToString()); + + // Save the change + contentService.Save(content); +} +``` + +Although the use of a GUID is preferable, you can also use the numeric ID to get the page: + +```csharp +@{ + // Get the page using it's id + var content = contentService.GetById(1234); +} +``` + +If Modelsbuilder is enabled you can get the alias of the desired property without using a magic string: + +{% include "../../../../.gitbook/includes/obsolete-warning-ipublishedsnapshotaccessor.md" %} + +```csharp +@using Umbraco.Cms.Core.PublishedCache; +@inject IPublishedSnapshotAccessor _publishedSnapshotAccessor; +@{ + // Set the value of the property with alias 'featuredBanner' + content.SetValue(Home.GetModelPropertyType(_publishedSnapshotAccessor, x => x.FeaturedBanner).Alias, udi.ToString()); +} +``` diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/member-group-picker.md b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/member-group-picker.md new file mode 100644 index 00000000000..7799b66b1f1 --- /dev/null +++ b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/member-group-picker.md @@ -0,0 +1,101 @@ +# Member Group Picker + +`Schema Alias: Umbraco.MemberGroupPicker` + +`UI Alias: Umb.PropertyEditorUi.MemberGroupPicker` + +`Returns: string` + +The Member Group Picker opens a panel to pick one or more member groups from the Member section. The value saved is of type string (comma separated IDs). + +## Data Type Definition Example + +![Member Group Picker Type Definition](images/Member-Picker-DataType.png) + +## Content Example + +![Member Grouep Picker Content](../../../../../../10/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Member-Group-Picker-Content.png) + +## MVC View Example + +### Without Modelsbuilder + +```csharp +@if (Model.HasValue("memberGroup")) +{ + var memberGroup = Model.Value("memberGroup"); +

@memberGroup

+} +``` + +### With Modelsbuilder + +```csharp +@if (!string.IsNullOrEmpty(Model.MemberGroup)) +{ +

@Model.MemberGroup

+} +``` + +## Add values programmatically + +See the example below to see how a value can be added or changed programmatically. To update a value of a property editor you need the [Content Service](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Services.ContentService.html). + +{% hint style="info" %} +The example below demonstrates how to add values programmatically using a Razor view. However, this is used for illustrative purposes only and is not the recommended method for production environments. +{% endhint %} + +```csharp +@using Umbraco.Cms.Core.Services; + +@inject IContentService Services; +@{ + // Get access to ContentService + var contentService = Services; + + // Create a variable for the GUID of the page you want to update + var guid = new Guid("796a8d5c-b7bb-46d9-bc57-ab834d0d1248"); + + // Get the page using the GUID you've defined + var content = contentService.GetById(guid); // ID of your page + + // Set the value of the property with alias 'memberGroup'. The value is the specific ID of the member group + content.SetValue("memberGroup", 1067); + + // Save the change + contentService.Save(content); +} +``` + +You can also add multiple groups by creating a comma separated string with the desired member group IDs. + +```csharp +@{ + // Set the value of the property with alias 'memberGroup'. + content.SetValue("memberGroup", "1067","1068"); +} +``` + +Although the use of a GUID is preferable, you can also use the numeric ID to get the page: + +```csharp +@{ + // Get the page using it's id + var content = contentService.GetById(1234); +} +``` + +If Modelsbuilder is enabled you can get the alias of the desired property without using a magic string: + +{% include "../../../../.gitbook/includes/obsolete-warning-ipublishedsnapshotaccessor.md" %} + +```csharp +@using Umbraco.Cms.Core.PublishedCache; +@using Umbraco.Cms.Core; + +@inject IPublishedSnapshotAccessor _publishedSnapshotAccessor; +@{ + // Set the value of the property with alias 'memberGroup' + content.SetValue(Home.GetModelPropertyType(_publishedSnapshotAccessor, x => x.MemberGroup).Alias, 1067); +} +``` diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/member-picker.md b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/member-picker.md new file mode 100644 index 00000000000..a39b0b3aca7 --- /dev/null +++ b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/member-picker.md @@ -0,0 +1,102 @@ +# Member Picker + +`Schema Alias: Umbraco.MemberPicker` + +`UI Alias: Umb.PropertyEditorUi.MemberPicker` + +`Returns: IPublishedContent` + +The member picker opens a panel to pick a specific member from the member section. The value saved is of type IPublishedContent. + +## Data Type Definition Example + +![Media Picker Data Type Definition](images/Member-Picker.png) + +## Content Example + +![Member Picker Content](../../../../../../10/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Member-Picker-Content-v8.png) + +## MVC View Example + +### Without Modelsbuilder + +```csharp +@{ + if (Model.HasValue("author")) + { + var member = Model.Value("author"); + @member.Name + } +} +``` + +### With Modelsbuilder + +```csharp +@{ + if (Model.Author != null) + { + var member = Model.Author; + @member.Name + } +} +``` + +## Add values programmatically + +See the example below to see how a value can be added or changed programmatically. To update a value of a property editor you need the [Content Service](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Services.ContentService.html). + +{% hint style="info" %} +The example below demonstrates how to add values programmatically using a Razor view. However, this is used for illustrative purposes only and is not the recommended method for production environments. +{% endhint %} + +```csharp +@using Umbraco.Cms.Core.Services; + +@inject IContentService Services; +@{ + // Get access to ContentService + var contentService = Services; + + // Create a variable for the GUID of the page you want to update + var guid = Guid.Parse("32e60db4-1283-4caa-9645-f2153f9888ef"); + + // Get the page using the GUID you've defined + var content = contentService.GetById(guid); // ID of your page + + // Create a variable for the GUID of the member ID + var authorId = Guid.Parse("ed944097281e4492bcdf783355219450"); + + // Set the value of the property with alias 'author'. + content.SetValue("author", authorId); + + // Save the change + contentService.Save(content); +} +``` + +Although the use of a GUID is preferable, you can also use the numeric ID to get the page: + +```csharp +@{ + // Get the page using it's id + var content = contentService.GetById(1234); +} +``` + +If Modelsbuilder is enabled you can get the alias of the desired property without using a magic string: + +{% include "../../../../.gitbook/includes/obsolete-warning-ipublishedsnapshotaccessor.md" %} + +```csharp +@using Umbraco.Cms.Core.PublishedCache; +@using Umbraco.Cms.Core; + +@inject IPublishedSnapshotAccessor _publishedSnapshotAccessor; +@{ + var udi = Udi.Create(Constants.UdiEntityType.Member, authorId); + + // Set the value of the property with alias 'author' + content.SetValue(Home.GetModelPropertyType(_publishedSnapshotAccessor, x => x.Author).Alias, udi); +} +``` diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/multi-url-picker.md b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/multi-url-picker.md new file mode 100644 index 00000000000..7f509db6989 --- /dev/null +++ b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/multi-url-picker.md @@ -0,0 +1,152 @@ +# Multi Url Picker + +`Schema Alias: Umbraco.MultiUrlPicker` + +`UI Alias: Umb.PropertyEditorUi.MultiUrlPicker` + +`Returns: IEnumerable or Link` + +Multi Url Picker allows an editor to pick and sort multiple urls. This property editor returns a single item if the "Maximum number of items" Data Type setting is set to 1 or a collection if it is 0. These can either be internal, external or media. + +## Data Type Definition Example + +![Related Links Data Type Definition](images/Multi-Url-Picker-DataType.png) + +## Content Example + +![Media Picker Content](../../../../../../10/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Multy-Url-Picker-Content-v8.png) + +## MVC View Example - value converters enabled + +## Typed + +```csharp +@using Umbraco.Cms.Core.Models +@{ + var links = Model.Value>("footerLinks"); + if (links.Any()) + { +
    + @foreach (var link in links) + { +
  • @link.Name
  • + } +
+ } +} +``` + +If `Max number of items` is configured to `1` + +```csharp +@using Umbraco.Cms.Core.Models +@{ + var link = Model.Value("link"); + if (link != null) + { + @link.Name + } +} +``` + +## Add values programmatically + +See the example below to see how a value can be added or changed programmatically. To update a value of a property editor you need the [Content Service](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Services.ContentService.html). + +{% hint style="info" %} +The example below demonstrates how to add values programmatically using a Razor view. However, this is used for illustrative purposes only and is not the recommended method for production environments. +{% endhint %} + +```csharp +@using Umbraco.Cms.Core; +@using Umbraco.Cms.Core.Serialization +@using Umbraco.Cms.Core.Services; +@using Umbraco.Cms.Core.Models; +@inject IContentService Services; +@inject IJsonSerializer Serializer; +@{ + // Get access to ContentService + var contentService = Services; + + // Create a variable for the GUID of the page you want to update + var guid = Guid.Parse("32e60db4-1283-4caa-9645-f2153f9888ef"); + + // Get the page using the GUID you've defined + var content = contentService.GetById(guid); // ID of your page + + // Get the media you want to assign to the footer links property + var media = Umbraco.Media("bca8d5fa-de0a-4f2b-9520-02118d8329a8"); + + // Create an Udi of the media + var mediaUdi = Udi.Create(Constants.UdiEntityType.Media, media.Key); + + // Get the content you want to assign to the footer links property + var contentPage = Umbraco.Content("665d7368-e43e-4a83-b1d4-43853860dc45"); + + // Create an Udi of the Content + var contentPageUdi = Udi.Create(Constants.UdiEntityType.Document, contentPage.Key); + + // Create a list with different link types + var externalLinks = new List + { + // External Link + new Link + { + Target = "_blank", + Name = "Our Umbraco", + Url = "https://our.umbraco.com/", + Type = LinkType.External + }, + // Media + new Link + { + Target = "_self", + Name = media.Name, + Url = media.MediaUrl(), + Type = LinkType.Media, + Udi = mediaUdi + }, + // Content + new Link + { + Target = "_self", + Name = contentPage.Name, + Url = contentPage.Url(), + Type = LinkType.Content, + Udi = contentPageUdi + } + }; + + // Serialize the list with links to JSON + var links = Serializer.Serialize(externalLinks); + + + // Set the value of the property with alias 'footerLinks'. + content.SetValue("footerLinks", links); + + // Save the change + contentService.Save(content); +} +``` + +Although the use of a GUID is preferable, you can also use the numeric ID to get the page: + +```csharp +@{ + // Get the page using it's id + var content = contentService.GetById(1234); +} +``` + +If Modelsbuilder is enabled you can get the alias of the desired property without using a magic string: + +{% include "../../../../.gitbook/includes/obsolete-warning-ipublishedsnapshotaccessor.md" %} + +```csharp +@using Umbraco.Cms.Core.PublishedCache; +@inject IPublishedSnapshotAccessor _publishedSnapshotAccessor; +@{ + // Set the value of the property with alias 'footerLinks' + content.SetValue(Home.GetModelPropertyType(_publishedSnapshotAccessor, x => x.FooterLinks).Alias, links); +} +``` diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/multiple-textbox.md b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/multiple-textbox.md new file mode 100644 index 00000000000..f22ad3a1114 --- /dev/null +++ b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/multiple-textbox.md @@ -0,0 +1,106 @@ +# Repeatable Textstrings + +`Schema Alias: Umbraco.MultipleTextstring` + +`UI Alias: Umb.PropertyEditorUi.MultipleTextString` + +`Returns: array of strings` + +The Repeatable textstrings property editor enables a content editor to make a list of text items. For best use with an unordered-list. + +## Data Type Definition Example + +![Repeatable textstrings Data Type Definition](images/Repeatable-Textstrings-DataType.png) + +## Content Example + +![Repeatable textstrings Content](<../../../../../../10/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Multiple-Textbox-Repeatable-Textstrings-Content (1) (1).png>) + +## MVC View Example + +### Without Modelsbuilder + +```csharp +@{ + if (Model.Value("keyFeatureList").Length > 0) + { +
    + @foreach (var item in Model.Value("keyFeatureList")) + { +
  • @item
  • + } +
+ } +} +``` + +### With Modelsbuilder + +```csharp +@{ + if (Model.KeyFeatureList.Any()) + { +
    + @foreach (var item in Model.KeyFeatureList) + { +
  • @item
  • + } +
+ } +} +``` + +## Add values programmatically + +See the example below to see how a value can be added or changed programmatically. To update a value of a property editor you need the [Content Service](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Services.ContentService.html). + +{% hint style="info" %} +The example below demonstrates how to add values programmatically using a Razor view. However, this is used for illustrative purposes only and is not the recommended method for production environments. +{% endhint %} + +```csharp +@using Umbraco.Cms.Core.Services; +@inject IContentService Services; +@{ + // Get access to ContentService + var contentService = Services; + + // Create a variable for the GUID of the page you want to update + var guid = new Guid("32e60db4-1283-4caa-9645-f2153f9888ef"); + + // Get the page using the GUID you've defined + var content = contentService.GetById(guid); // ID of your page + + // Set the value of the property with alias 'keyFeatureList' + content.SetValue("keyFeatureList", "Awesome" + Environment.NewLine + "Super"); + + // Save the change + contentService.Save(content); +} +``` + +{% hint style="info" %} +To add multiple values to the repeatable text strings property editor you have to put each value on a new line. This can be achieved using either `\r\n\` or `Environment.NewLine`. +{% endhint %} + +Although the use of a GUID is preferable, you can also use the numeric ID to get the page: + +```csharp +@{ + // Get the page using it's id + var content = contentService.GetById(1234); +} +``` + +If Modelsbuilder is enabled you can get the alias of the desired property without using a magic string: + +{% include "../../../../.gitbook/includes/obsolete-warning-ipublishedsnapshotaccessor.md" %} + +```csharp +@using Umbraco.Cms.Core.PublishedCache; +@inject IPublishedSnapshotAccessor _publishedSnapshotAccessor; +@{ + // Set the value of the property with alias 'keyFeatureList' + content.SetValue(Home.GetModelPropertyType(_publishedSnapshotAccessor, x => x.KeyFeatureList).Alias, "Awesome" + Environment.NewLine + "Super"); +} +``` diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/numeric.md b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/numeric.md new file mode 100644 index 00000000000..806ec75d0f1 --- /dev/null +++ b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/numeric.md @@ -0,0 +1,123 @@ +# Numeric + +`Schema Alias: Umbraco.Integer` + +`UI Alias: Umb.PropertyEditorUi.Integer` + +`Returns: Integer` + +Numeric is an HTML input control for entering numbers. Since it's a standard HTML element the options and behaviour are all controlled by the browser and therefore is beyond the control of Umbraco. + +## Data Type Definition Example + +![Numeric Data Type Definition](images/numeric-datatype.png) + +### Minimum + +This allows you to set up a minimum value. If you will always need a minimum value of 10 this is where you set it up and whenever you use the datatype the value will always start at 10. It's not possible to change the value to anything lower than 10. Only higher values will be accepted. + +### Step Size + +This allows you to control by how much value should be allowed to increase/decrease when clicking the up/down arrows. If you try to enter a value that does not match with the step setting then it will not be accepted. + +### Maximum + +This allows you to set up a maximum value. If you will always need a maximum value of 100 this is where you set it up. It's not possible to change the value to anything higher than 100. Only lower values will be accepted. + +## Settings + +## Content Example + +![Numeric Content Definition](../../../../../../10/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/numeric-content.png) + +## MVC View Examples + +### Rendering the output casting to an int (without Modelsbuilder) + +By casting the output as an int it's possible for you to do mathematical operations with the value. + +```csharp +@{ + int students = Model.HasValue("students") ? Model.Value("students") : 0; + int teachers = Model.HasValue("teachers") ? Model.Value("teachers") : 0; + int totalTravellers = students + teachers; + +

@totalTravellers

+} +``` + +### Rendering the output casting to a string (Without Modelsbuilder) + +You can also render the output by casting it to a string, which means you will not be able to do mathematical operations + +```csharp +@{ + if(Model.HasValue("students")){ +

@(Model.Value("students"))

+ } +} +``` + +### With Modelsbuilder + +```csharp +@{ + int students = Model.Students; + int teachers = Model.Teachers; + int totalTravellers = students + teachers; + +

@totalTravellers

+} +``` + +## Add values programmatically + +See the example below to see how a value can be added or changed programmatically. To update a value of a property editor you need the [Content Service](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Services.ContentService.html). + +{% hint style="info" %} +The example below demonstrates how to add values programmatically using a Razor view. However, this is used for illustrative purposes only and is not the recommended method for production environments. +{% endhint %} + +```csharp +@using Umbraco.Cms.Core.Services; +@inject IContentService Services; +@{ + // Get access to ContentService + var contentService = Services; + + // Create a variable for the GUID of the page you want to update + var guid = new Guid("32e60db4-1283-4caa-9645-f2153f9888ef"); + + // Get the page using the GUID you've defined + var content = contentService.GetById(guid); // ID of your page + + // Set the value of the property with alias 'students' + content.SetValue("students", 20); + + // Save the change + contentService.Save(content); + +} +``` + +Although the use of a GUID is preferable, you can also use the numeric ID to get the page: + +```csharp +@{ + // Get the page using it's id + var content = contentService.GetById(1234); +} +``` + +If Modelsbuilder is enabled you can get the alias of the desired property without using a magic string: + +{% include "../../../../.gitbook/includes/obsolete-warning-ipublishedsnapshotaccessor.md" %} + +```csharp +@using Umbraco.Cms.Core.PublishedCache; +@inject IPublishedSnapshotAccessor _publishedSnapshotAccessor; +@{ + // Set the value of the property with alias 'students' + content.SetValue(Home.GetModelPropertyType(_publishedSnapshotAccessor, x => x.Students).Alias, 20); +} +``` diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/radiobutton-list.md b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/radiobutton-list.md new file mode 100644 index 00000000000..18596f1dbe0 --- /dev/null +++ b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/radiobutton-list.md @@ -0,0 +1,96 @@ +# Radiobutton List + +`Schema Alias: Umbraco.RadioButtonList` + +`UI Alias: Umb.PropertyEditorUi.RadioButtonList` + +`Returns: string` + +Pretty much like the name indicates this Data type enables editors to choose from list of radio buttons and returns the value of the selected item as string. + +## Data Type Definition Example + +![Radiobutton List Data Type Definition](images/RadioButton-List-DataType.png) + +{% hint style="info" %} +You can use dictionary items to translate the values of a Radiobutton List property editor in a multilingual setup. For more details, see the [Creating a Multilingual Site](../../../../tutorials/multilanguage-setup.md#translating-multi-value-property-editors) article. +{% endhint %} + +## Content Example + +![Radiobutton List Content](../../../../../../10/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/RadioButton-List-Content-v8.png) + +## MVC View Example + +### Typed + +#### Without Modelsbuilder + +```csharp +@if (Model.HasValue("colorTheme")) +{ + var value = Model.Value("colorTheme"); +

@value

+} +``` + +#### With Modelsbuilder + +```csharp +@if (Model.ColorTheme != null) +{ + var value = Model.ColorTheme; +

@value

+} +``` + +## Add values programmatically + +See the example below to see how a value can be added or changed programmatically. To update a value of a property editor you need the [Content Service](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Services.ContentService.html). + +{% hint style="info" %} +The example below demonstrates how to add values programmatically using a Razor view. However, this is used for illustrative purposes only and is not the recommended method for production environments. +{% endhint %} + +```csharp +@using Umbraco.Cms.Core.Services; +@inject IContentService Services; +@{ + // Get access to ContentService + var contentService = Services; + + // Create a variable for the GUID of the page you want to update + var guid = new Guid("796a8d5c-b7bb-46d9-bc57-ab834d0d1248"); + + // Get the page using the GUID you've defined + var content = contentService.GetById(guid); // ID of your page + + // Set the value of the property with alias 'colorTheme' + content.SetValue("colorTheme", "water"); + + // Save the change + contentService.Save(content); +} +``` + +Although the use of a GUID is preferable, you can also use the numeric ID to get the page: + +```csharp +@{ + // Get the page using it's id + var content = contentService.GetById(1234); +} +``` + +If Modelsbuilder is enabled you can get the alias of the desired property without using a magic string: + +{% include "../../../../.gitbook/includes/obsolete-warning-ipublishedsnapshotaccessor.md" %} + +```csharp +@using Umbraco.Cms.Core.PublishedCache; +@inject IPublishedSnapshotAccessor _publishedSnapshotAccessor; +@{ + // Set the value of the property with alias 'colorTheme' + content.SetValue(Home.GetModelPropertyType(_publishedSnapshotAccessor, x => x.ColorTheme).Alias, "water"); +} +``` diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor-tinymce/README.md b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor-tinymce/README.md new file mode 100644 index 00000000000..c0c2f12ff39 --- /dev/null +++ b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor-tinymce/README.md @@ -0,0 +1,123 @@ +# Rich Text Editor TinyMce + +`Schema Alias: Umbraco.RichText` + +`UI Alias: Umb.PropertyEditorUi.TinyMCE` + +`Returns: HTML` + +{% hint style="warning" %} +This article is a work in progress and may undergo further revisions, updates, or amendments. The information contained herein is subject to change without notice. +{% endhint %} + +The Rich Text Editor (RTE) is highly configurable and based on [TinyMCE](https://www.tinymce.com/). Depending on the configuration, it will give your content editors more flexibility when working with content that should be more than plain text. + +{% hint style="info" %} +**Are you using custom configurations or plugins with TinyMCE?** + +In Umbraco 11 the TinyMCE version supported has been upgraded from version 4 to version 6. You need to migrate to the latest version if you are using TinyMCE plugins or custom configuration. + +If your site is upgraded from an older version, follow the migration guides below to upgrade the TinyMCE version as well. + +* [Migrate from version 4 to version 5](https://www.tiny.cloud/docs/tinymce/5/migration-from-4x/) +* [Migrate from version 5 to version 6](https://www.tiny.cloud/docs/tinymce/6/migration-from-5x/) +{% endhint %} + +## [Configuration options](configuration.md) + +Customize everything from toolbar options to editor size to where pasted images are saved. + +## [Styles](styles.md) + +Use CSS to define specific editor styles and add them as formatting options of the Rich Text Editor. + +## [Blocks](blocks.md) + +Use Blocks to define specific parts that can be added as part of the markup of the Rich Text Editor. + +## [Plugins](plugins.md) + +Extend the functionality of the Rich Text Editor with plugins. + +## Data Type Definition Example + +![Rich Text Editor - Data Type](images/rte-datatype-v10.png) + +## Content Example + +![Rich Text Editor - Content](images/rte-content-11.png) + +## MVC View Example + +### Without Modelsbuilder + +```csharp +@{ + if (Model.HasValue("richText")){ +

@(Model.Value("richText"))

+ } +} +``` + +### With Modelsbuilder + +```csharp +@{ + if (!string.IsNullOrEmpty(Model.RichText.ToString())) + { +

@Model.RichText

+ } +} +``` + +## Add values programmatically + +See the example below to see how a value can be added or changed programmatically. To update a value of a property editor you need the [Content Service](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Services.ContentService.html). + +{% hint style="info" %} +The example below demonstrates how to add values programmatically using a Razor view. However, this is used for illustrative purposes only and is not the recommended method for production environments. +{% endhint %} + +```csharp +@using Umbraco.Cms.Core.Services; +@inject IContentService Services; +@{ + // Get access to ContentService + var contentService = Services; + + // Create a variable for the GUID of the page you want to update + var guid = Guid.Parse("32e60db4-1283-4caa-9645-f2153f9888ef"); + + // Get the page using the GUID you've defined + var content = contentService.GetById(guid); // ID of your page + + // Create a variable for the desired value + var htmlValue = new HtmlString("Add some text here"); + + // Set the value of the property with alias 'richText'. + content.SetValue("richText", htmlValue); + + // Save the change + contentService.Save(content); +} +``` + +Although the use of a GUID is preferable, you can also use the numeric ID to get the page: + +```csharp +@{ + // Get the page using it's id + var content = contentService.GetById(1234); +} +``` + +If Modelsbuilder is enabled you can get the alias of the desired property without using a magic string. + +```csharp +@using Umbraco.Cms.Core.PublishedCache; +@inject IPublishedSnapshotAccessor _publishedSnapshotAccessor; +@{ + // Set the value of the property with alias 'richText' + content.SetValue(Home.GetModelPropertyType(_publishedSnapshotAccessor, x => x.RichText).Alias, "Add some text here"); +} +``` diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor-tinymce/blocks.md b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor-tinymce/blocks.md new file mode 100644 index 00000000000..4263b54ad75 --- /dev/null +++ b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor-tinymce/blocks.md @@ -0,0 +1,101 @@ +# Blocks + +{% hint style="warning" %} +This article is a work in progress and may undergo further revisions, updates, or amendments. The information contained herein is subject to change without notice. +{% endhint %} + +It is possible to insert Blocks into the markup of the Rich Text Editor (RTE). Once you've defined the Block Type as part of the RTE Data Type and enabled the Blocks Toolbar Option. Then Blocks can be created directly inside the Rich Text Editor. + +## Configure Blocks + +The Block List property editor is configured in the same way as any standard property editor, via the _Data Types_ admin interface. + +To set up your Block List Editor property, create a new _Data Type_ and select **Block List** from the list of available property editors. + +Then you will see the configuration options for a Block List as shown below. + +![Rich Text Editor - Data Type Block Fields](images/rte-data-type-block-fields.jpg) + +The Data Type editor allows you to configure the following properties: + +* **Available Blocks** - Here you will define the Block Types to be available for use in the property. Read more on how to set up Block Types below. +* **Blocks Live editing mode** - Enabling this will make editing of a Block happen directly to the Rich Text Editor, making changes appear as you type. + +## Setup Block Types + +Block Types are **Element Types** that need to be created before you can start configuring them as Block Types. This can be done either directly from the property editor setup process, or you can set them up beforehand. If they are set beforehand then they need to be added to the Rich Text Editor afterward. + +Once you add an Element Type as a Block Type on your Rich Text Editor Data Type you will have options to configure it further. + +![Rich Text Editor - Data Type Block Configuration](images/rte-data-type-block-type-editor.jpeg) + +Each Block has a set of properties that are optional to configure. They are described below. + +### Editor Appearance + +By configuring the properties in the group you can customize the user experience for your content editors when they work with the blocks in the Content section. + +* **Label** - Define a label for the appearance of the Block in the editor. The label can use AngularJS template string syntax to display values of properties. +* **Display Inline with text** - When turned on the Block Element will be able to stay in line with text or other elements. If not the Block will stay on its own line. +* **Custom view** - Overwrite the AngularJS view for the block presentation in the Content editor. Use this to make a more visual presentation of the block or even make your own editing experience by adding your own AngularJS controller to the view. +* **Custom stylesheet** - Pick your own stylesheet to be used for this block in the Content editor. By adding a stylesheet the styling of this block will become scoped. Meaning that backoffice styles are no longer present for the view of this block. +* **Overlay editor size** - Set the size for the Content editor overlay for editing this block. + +### Data Models + +It is possible to use two separate Element Types for your Block Types. It's required to have one for Content and optional to add one for Settings. + +* **Content model** - This presents the Element Type used as a model for the content section of this Block. This cannot be changed, but you can open the Element Type to perform edits or view the properties available. Useful when writing your Label. +* **Settings model** - Add a Settings section to your Block based on a given Element Type. When picked you can open the Element Type or choose to remove the settings section again. + +### Catalogue appearance + +These properties refer to how the Block is presented in the Block catalog when editors choose which Blocks to use for their content. + +* **Background color** - Define a background color to be displayed beneath the icon or thumbnail. Eg. `#424242`. +* **Icon Color** - Change the color of the Element Type icon. Eg. `#242424`. +* **Thumbnail** - Pick an image or Scalable Vector Graphics (SVG) file to replace the icon of this Block in the catalog. + +The thumbnails for the catalog are presented in the format of 16:10, and we recommend a resolution of 400px width and 250px height. + +### Advanced + +These properties are relevant when you work with custom views. + +* **Force hide content editor** - If you made a custom view that enables you to edit the content part of a block and you are using default editing mode (not inline) you might want to hide the content editor from the block editor overlay. + +## Rendering Blocks + +To render Blocks in the frontend, you must create Partial Views for each Block. + +The Partial Views must be: + +* Named by the alias of the Element Type that is being used as the Content Model for the Block. +* Placed in the folder `Views/Partials/RichText/Components/`. + +For example, if the Element Type alias of the Content Model is `myBlockType`, a Partial View must be created at `Views/Partials/RichText/Components/MyBlockType.cshtml`. + +The Partial View will receive the model of `Umbraco.Cms.Core.Models.Blocks.RichTextBlockItem`. This gives you the option to access properties of the Content and Settings Models of your Block, as illustrated in the following sample: + +```csharp +@inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage + +@* Output the 'heading' field from the Content Model using the `backgroundColor` field from the Settings Model as background color *@ +

@Model.Content.Value("heading")

+``` + +If you use ModelsBuilder, you can specify the Content Model (and optionally the Settings Model) in the Partial View model. This allows for type-safe access to the Block data. + +The following example shows a Partial View of a Block with `MyBlockType` as Content Model and `MyBlockSettingsType` as Settings Model: + +```csharp +@inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage> +@using ContentModels = Umbraco.Cms.Web.Common.PublishedModels; + +@* Output the Heading of field with alias 'heading' from the 'MyBlockType' Content Model *@ +

@Model.Content.Heading

+``` + +## Build a Custom Backoffice View + +Building Custom Views for Block representations in Backoffice is the same for all Block Editors. [Read about building a Custom View for Blocks here](../../../../../tutorials/creating-custom-views-for-blocklist.md). diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor-tinymce/configuration.md b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor-tinymce/configuration.md new file mode 100644 index 00000000000..3f2464248b7 --- /dev/null +++ b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor-tinymce/configuration.md @@ -0,0 +1,69 @@ +# Configuration + +{% embed url="" %} +Rich Text Editor default implementation +{% endembed %} + +In this article, you will learn about different ways to configure the Rich Text Editor (RTE). + +## Toolbar + +You have full control over which options should be available on the RTE. + +![Toolbar: All options enabled](images/toolbar-full-11.png) + +In the example above, all 34 options have been enabled. These options include copy/paste buttons, font styles like bold and italics, bullet lists, and options to embed videos and insert images. + +## Stylesheets + +It is possible to define specific styles that can be used when editing content using the RTE. You can use as many of these styles with the RTE as you want. + +The RTE styles are defined in CSS files which can be created in the **Settings** section. Read the [RTE Styles](styles.md) article to learn more about this feature. + +## Dimensions + +Define the `height` and `width` of the editor displayed in the Content section. + +## Maximum size for inserted images + +Define the maximum size for images added through the Rich Text Editor. + +If inserted images are larger than the dimensions defined here, the images will be resized automatically. + +## Mode + +The Rich Text Editor comes in two different modes: Classic and Inline. + +### Classic + +The default mode which displays the toolbar at the top. + +![RTE Mode: Classic](images/rte-mode-classic-11.png) + +### Inline + +In this mode, the toolbar is hidden and only shows up when content in the editor is highlighted. + +![Rich Text Editor Inline mode](images/inline-mode-new.png) + +## Blocks + +Blocks can be added as elements in the Rich Text Editor. Configuration and rendering of Blocks are described in the [Blocks in Rich Text Editor](blocks.md) article. + +## Overlay Size + +Select the width of the link picker overlay. The overlay size comes in three sizes: Small, Medium, and Large. + +## Hide Label + +When this option is checked the label and description for the RTE property will be hidden. + +## Ignore User Start Nodes + +Some backoffice users might be restricted to specific parts of the Content tree. When the "Ignore User Start Nodes" option is checked, users can pick any piece of content, when adding internal links. + +## Image Upload Folder + +Images added through the RTE are by default added to the root of the Media library. + +Sometimes you might want to add the images to a specific folder. This folder can be configured using the "Image Upload Folder" setting. diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor-tinymce/images/inline-mode-new.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor-tinymce/images/inline-mode-new.png new file mode 100644 index 00000000000..6b6ea1f4288 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor-tinymce/images/inline-mode-new.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor-tinymce/images/my-rte-button-editor.jpg b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor-tinymce/images/my-rte-button-editor.jpg new file mode 100644 index 00000000000..8b9a659bf46 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor-tinymce/images/my-rte-button-editor.jpg differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor-tinymce/images/my-rte-button.jpg b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor-tinymce/images/my-rte-button.jpg new file mode 100644 index 00000000000..8e43b2a5167 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor-tinymce/images/my-rte-button.jpg differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor-tinymce/images/rte-choose-stylesheet.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor-tinymce/images/rte-choose-stylesheet.png new file mode 100644 index 00000000000..8452a57a36b Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor-tinymce/images/rte-choose-stylesheet.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor-tinymce/images/rte-code-tab.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor-tinymce/images/rte-code-tab.png new file mode 100644 index 00000000000..767739a3f31 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor-tinymce/images/rte-code-tab.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor-tinymce/images/rte-content-11.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor-tinymce/images/rte-content-11.png new file mode 100644 index 00000000000..ab1e9acf68c Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor-tinymce/images/rte-content-11.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor-tinymce/images/rte-create-style.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor-tinymce/images/rte-create-style.png new file mode 100644 index 00000000000..5d5ca865fdd Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor-tinymce/images/rte-create-style.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor-tinymce/images/rte-data-type-block-fields.jpg b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor-tinymce/images/rte-data-type-block-fields.jpg new file mode 100644 index 00000000000..ed0ed6da5d4 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor-tinymce/images/rte-data-type-block-fields.jpg differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor-tinymce/images/rte-data-type-block-type-editor.jpeg b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor-tinymce/images/rte-data-type-block-type-editor.jpeg new file mode 100644 index 00000000000..dd54171ac52 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor-tinymce/images/rte-data-type-block-type-editor.jpeg differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor-tinymce/images/rte-datatype-v10.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor-tinymce/images/rte-datatype-v10.png new file mode 100644 index 00000000000..a69a02a082d Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor-tinymce/images/rte-datatype-v10.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor-tinymce/images/rte-datatype.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor-tinymce/images/rte-datatype.png new file mode 100644 index 00000000000..0c22f13a5bf Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor-tinymce/images/rte-datatype.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor-tinymce/images/rte-mode-classic-11.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor-tinymce/images/rte-mode-classic-11.png new file mode 100644 index 00000000000..082f49bdf78 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor-tinymce/images/rte-mode-classic-11.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor-tinymce/images/rte-mode-classic-new.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor-tinymce/images/rte-mode-classic-new.png new file mode 100644 index 00000000000..ade47ad435a Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor-tinymce/images/rte-mode-classic-new.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor-tinymce/images/rte-styles.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor-tinymce/images/rte-styles.png new file mode 100644 index 00000000000..2d5435266c7 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor-tinymce/images/rte-styles.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor-tinymce/images/toolbar-all-options.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor-tinymce/images/toolbar-all-options.png new file mode 100644 index 00000000000..7c289bd39bd Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor-tinymce/images/toolbar-all-options.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor-tinymce/images/toolbar-full-11.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor-tinymce/images/toolbar-full-11.png new file mode 100644 index 00000000000..7f4d5b84f19 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor-tinymce/images/toolbar-full-11.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor-tinymce/plugins.md b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor-tinymce/plugins.md new file mode 100644 index 00000000000..fa585b480a3 --- /dev/null +++ b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor-tinymce/plugins.md @@ -0,0 +1,200 @@ +--- +description: Information on how to work with TinyMCE plugins in the rich text editor. +--- + +# Plugins + +The Rich Text Editor (RTE) in Umbraco is based on the open source editor [TinyMCE](https://www.tiny.cloud/). TinyMCE is a highly customizable editor, and it is possible to extend the functionality of the editor by adding plugins. + +TinyMCE comes with a lot of plugins out of the box, but it is also possible to create your own plugins. This article will show you how to add a custom plugin to the rich text editor. + +## Open-Source Plugins + +TinyMCE has a lot of open-source plugins available. You can find a list of these plugins on the [TinyMCE website](https://www.tiny.cloud/docs/tinymce/6/plugins/#open-source-plugins). + +## Premium Plugins + +TinyMCE also has a number of [premium plugins](https://www.tiny.cloud/docs/tinymce/6/plugins/#premium-plugins) available. These plugins are only available for [paid TinyMCE subscriptions](https://www.tiny.cloud/pricing/). They can be added to the rich text editor by [adding a bit of configuration](#adding-a-premium-plugin). + +## Adding a Plugin + +To enable plugins in the rich text editor, you need to add an extension type called `tinyMcePlugin` in a manifest file. The manifest file is a JSON file that describes the plugin and how it should be loaded. You can add a plugin such as the open source [Word Count Plugin](https://www.tiny.cloud/docs/tinymce/6/wordcount/) to the rich text editor. You can also define your own custom plugin to extend the functionality of the editor. This way you can add custom buttons, dialogs, or other features to the editor. + +{% hint style="info" %} +The manifest file should be placed in a folder in `App_Plugins/{YourPackageName}`, with the name `umbraco-package.json`. +{% endhint %} + +{% code title="App_Plugins/YourPackageName/umbraco-package.json" lineNumbers="true" %} + +```json +{ + "name": "My TinyMCE Plugin", + "version": "1.0.0", + "extensions": [ + { + "type": "tinyMcePlugin", + "alias": "mytinymceplugin", + "name": "My TinyMCE Plugin", + "meta": { + "config": { + "plugins": ["wordcount"], + "statusbar": true + } + } + } + ] +} +``` + +{% endcode %} + +The example above shows how to add the open-source [Word Count Plugin](https://www.tiny.cloud/docs/tinymce/6/wordcount/) to the rich text editor. The plugin is added to the `Plugins` array in the configuration. The plugin itself will be shown in the statusbar of the rich text editor, so the `statusbar` option is also added to the `config` object. + +## Creating a Custom Plugin + +If you want to create your own plugin, you should in general follow the [TinyMCE documentation](https://www.tiny.cloud/docs/tinymce/latest/creating-a-plugin/). However, there are a few things you need to be aware of to load the plugin in Umbraco. See the example below. + +### Examples + +Load a custom plugin that gives you the ability to interact with the global `tinymce` editor object. + +Here we are loading a custom plugin called `myrteplugin` and adding a button to the editor called `myrtebutton`. When the button is clicked, it will insert the text `Hello World!` into the editor. + +
Rich text editor showing a custom button

The text "Hello World!" shows up after clicking the button

+ +1.**Add a manifest file** + +First we create an `umbraco-package.json` file which will contain the manifest for the plugin. This adds a button to the toolbar in the rich text editor, which editors can enable on the Data Type. We are also letting the rich text editor know it should load the plugin from the `plugin.js` file. + +{% code title="App_Plugins/MyRtePlugin/umbraco-package.json" lineNumbers="true" %} + +```json +{ + "name": "My TinyMCE Plugin", + "version": "1.0.0", + "extensions": [ + { + "type": "tinyMcePlugin", + "alias": "myrteplugin", + "name": "My TinyMCE Plugin", + "js": "/App_Plugins/MyRtePlugin/plugin.js", + "meta": { + "toolbar": [ + { + "alias": "myrtebutton", + "label": "My RTE Button", + "icon": "code-sample" + } + ] + } + } + ] +} +``` + +{% endcode %} + +2.**Add the plugin.js file** + +The `plugin.js` file should contain the JavaScript code for the plugin. The file is loaded as a JavaScript module and must export a default class that extends the `UmbTinyMcePluginBase` class. + +{% hint style="info" %} +The `UmbTinyMcePluginBase` class is a class provided by Umbraco that you can use to create your own plugins. The class is a wrapper around the TinyMCE plugin API. We can use the `args` object on the constructor to access the TinyMCE editor instance and other useful properties. +{% endhint %} + +{% code title="App_Plugins/MyTinyMCEPlugin/plugin.js" lineNumbers="true" %} + +```js +import { UmbTinyMcePluginBase } from '@umbraco-cms/backoffice/tiny-mce'; + +export default class UmbTinyMceMediaPickerPlugin extends UmbTinyMcePluginBase { + /** + @param args {import('@umbraco-cms/backoffice/tiny-mce').TinyMcePluginArguments} + */ + constructor(args: TinyMcePluginArguments) { + super(args); + + // Add your plugin code here + args.editor.ui.registry.addButton('myrtebutton', { + text: 'My RTE Button', + icon: 'code-sample', + onAction: () => { + args.editor.insertContent('Hello World!'); + } + }); + } +} +``` + +{% endcode %} + +The button must be added to the toolbar in the rich text editor configuration. + +
Rich text editor configuration showing available options

Enable the button in the rich text editor configuration

+ +You can go to any Document Type that uses the rich text editor and click the button to insert the text `Hello World!` after. + +You have full access to the `tinymce` editor object, so you can create any custom functionality you need. + +### Learn more + +* [TinyMCE documentation](https://www.tiny.cloud/docs/) +* [TinyMCE tutorial: Creating a plugin](https://www.tiny.cloud/docs/tinymce/latest/creating-a-plugin/) + +## Adding a premium plugin + +To add a premium plugin, you need to add the plugin name to the `plugins` array in the `config` object of a `tinyMcePlugin` extension. You also need to add a JavaScript module that can load up the cloud-hosted TinyMCE premium plugins bundle. + +{% hint style="info" %} +Premium plugins require a subscription at [TinyMCE Cloud](https://www.tiny.cloud/). You can go there and sign up for a free trial. You will get a Cloud API key that you can use to try out the premium plugins. +{% endhint %} + +1. Declaring the plugin + +Let us first add the [powerpaste](https://www.tiny.cloud/docs/tinymce/6/introduction-to-powerpaste/) plugin to the rich text editor. This plugin is a premium plugin that helps you paste content from Word documents and other sources. We will configure the plugin to allow local images and clean the HTML when pasting Word documents. + +{% code title="App_Plugins/MyRtePlugin/umbraco-package.json" lineNumbers="true" %} + +```json +{ + "name": "My TinyMCE Plugin", + "version": "1.0.0", + "extensions": [ + { + "type": "tinyMcePlugin", + "alias": "mytinymceplugin", + "name": "My TinyMCE Plugin", + "js": "/App_Plugins/MyRtePlugin/plugin.js", + "meta": { + "config": { + "plugins": ["powerpaste"], + "powerpaste_allow_local_images": "true", + "powerpaste_word_import": "clean" + } + } + } + ] +} +``` + +{% endcode %} + +2. Creating the plugin.js file + +The `plugin.js` file should contain the JavaScript code to load the cloud-hosted TinyMCE premium plugins bundle. You must replace `{Cloud API Key}` with your own Cloud API key. + +{% code title="App_Plugins/MyTinyMCEPlugin/plugin.js" lineNumbers="true" %} + +```js +import 'https://cdn.tiny.cloud/1/{Cloud API Key}/tinymce/6/plugins.min.js'; +``` + +{% endcode %} + +We have now enabled the `powerpaste` plugin. We have configured it to allow pasting in local images. It will prompt when pasting Word documents, but for HTML documents it will clean the HTML without prompting. + +{% hint style="info" %} +You can enable as many plugins as you want through the `plugins` array in the `config` object. You can even combine premium, open-source, and your own plugins as you see fit. +{% endhint %} + +See all the [available premium plugins](https://www.tiny.cloud/docs/tinymce/6/plugins/#premium-plugins) on the TinyMCE website. diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor-tinymce/styles.md b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor-tinymce/styles.md new file mode 100644 index 00000000000..67e925990e4 --- /dev/null +++ b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor-tinymce/styles.md @@ -0,0 +1,44 @@ +# Styles + +It is possible to define specific styles and fonts for the Rich Text Editor (RTE). Once you have defined the styles, and enabled them on the RTE Data Type, the styles can be accessed directly in the Content section. + +![Rich Text Editor Styles](images/rte-styles.png) + +## Creating RTE Styles + +The RTE styles are created and managed in the Umbraco backoffice. + +* Find the **Stylesheets** folder in the Settings section. +* Click **...** next to the **Stylesheets** folder and select **Create...**. +* Choose _New Rich Text Editor style sheet file_. +* Give the new stylesheet a name and **Save** it. + +This will generate an empty CSS file in your project. + +At this point, you can start adding specific styles, that your editors can then use in the Content section when creating and writing new content. + +![Add specific RTE styles](../../../../../../../10/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/rich-text-editor/images/rte-create-style.png) + +The image above is an example of how an RTE style can be configured. When working with these styles, the **Preview** feature will show you how the style will look when applied. + +Every style you add, will automatically be added to the CSS file. The file will be placed in the same location as the rest of your CSS files. It is possible to edit the file directly from the backoffice as well. Access it from the **Code** tab in the top-right corner. + +![Edit CSS file directly in the backoffice](../../../../../../../10/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/rich-text-editor/images/rte-code-tab.png) + +## Using RTE Styles + +In order for your editors to be able to use the styles when working with content, two things needs to be done. + +### 1. Enable "Style select" + +The styles will be accessed by the editors using the "Formats" dropdown in the toolbar of the RTE. In order the enable the dropdown, the "Style select" toolbar option needs to be checked. + +This can be done by accessing the RTE Data Type directly from the "Info" tab on the Content. You can also find the RTE Data Type in the Settings section of the backoffice and make the changes from there. Learn more about the configuration options in the [RTE Config](configuration.md) article. + +### 2. Select the styles + +You can have as many different styles for your RTEs as you want. You might want to have different styles for different RTE Data Types. + +When configuring your RTE Data Types, you can select which stylesheets your content editors should be able to use. + +![Choose stylesheets on the Data Type](../../../../../../../10/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/rich-text-editor/images/rte-choose-stylesheet.png) diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor/README.md b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor/README.md new file mode 100644 index 00000000000..2ec74dbffeb --- /dev/null +++ b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor/README.md @@ -0,0 +1,115 @@ +# Rich Text Editor + +`Schema Alias: Umbraco.RichText` `UI Alias: Umb.PropertyEditorUi.Tiptap` + +`Returns: HTML` + +{% hint style="warning" %} +In Umbraco 15, the Rich Text Editor has a new default property editor UI that introduces Tiptap as an alternative. + +You can continue to use the [TinyMCE UI for the Rich Text Editor](../rich-text-editor-tinymce/). This UI will be removed in Umbraco 16. + +**Current limitations** + +The Tiptap UI currently does not support using custom styles for your rich text. + +Resizing media images has not been implemented yet. + +{% endhint %} + +The Rich Text Editor (RTE) Tiptap property editor is highly configurable and based on [Tiptap](https://tiptap.dev/). Depending on the configuration setup, it provides editors a lot of flexibility when working with content. + +## [Configuration options](configuration.md) + +Customize everything from toolbar options to editor size to where pasted images are saved. + +## [Blocks](blocks.md) + +Use Blocks to define specific parts that can be added as part of the markup of the Rich Text Editor. + +## [Extensions](extensions.md) + +Extend the functionality of the Rich Text Editor with extensions. + +## Data Type Definition Example + +![Rich Text Editor - Data Type](images/rte-tiptap-datatypedefinition.png) + +## Content Example + +![Rich Text Editor - Content Example](images/rte-tiptap-contentexample.png) + +## MVC View Example + +### With Modelsbuilder + +```csharp +@{ + if (!string.IsNullOrEmpty(Model.RichText.ToString())) + { +

@Model.RichText

+ } +} +``` + +### Without Modelsbuilder + +```csharp +@{ + if (Model.HasValue("richText")){ +

@(Model.Value("richText"))

+ } +} +``` + +## Add values programmatically + +See the example below to see how a value can be added or changed programmatically. To update a value of a property editor you need the [Content Service](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Services.ContentService.html). + +{% hint style="info" %} +The example below demonstrates how to add values programmatically using a Razor view. However, this is used for illustrative purposes only and is not the recommended method for production environments. +{% endhint %} + +```csharp +@using Umbraco.Cms.Core.Services; +@inject IContentService Services; +@{ + // Get access to ContentService + var contentService = Services; + + // Create a variable for the GUID of the page you want to update + var guid = Guid.Parse("32e60db4-1283-4caa-9645-f2153f9888ef"); + + // Get the page using the GUID you've defined + var content = contentService.GetById(guid); // ID of your page + + // Create a variable for the desired value + var htmlValue = new HtmlString("Add some text here"); + + // Set the value of the property with alias 'richText'. + content.SetValue("richText", htmlValue); + + // Save the change + contentService.Save(content); +} +``` + +Although the use of a GUID is preferable, you can also use the numeric ID to get the page: + +```csharp +@{ + // Get the page using it's id + var content = contentService.GetById(1234); +} +``` + +If Modelsbuilder is enabled you can get the alias of the desired property without using a magic string. + +```csharp +@using Umbraco.Cms.Core.PublishedCache; +@inject IPublishedSnapshotAccessor _publishedSnapshotAccessor; +@{ + // Set the value of the property with alias 'richText' + content.SetValue(Home.GetModelPropertyType(_publishedSnapshotAccessor, x => x.RichText).Alias, "Add some text here"); +} +``` diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor/blocks.md b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor/blocks.md new file mode 100644 index 00000000000..28ac0dc3565 --- /dev/null +++ b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor/blocks.md @@ -0,0 +1,104 @@ +# Blocks + +{% hint style="warning" %} +This article is a work in progress and may undergo further revisions, updates, or amendments. The information contained herein is subject to change without notice. +{% endhint %} + +It is possible to insert Blocks into the markup of the Rich Text Editor. Block can be created directly inside the Rich Text editor when + +* You have defined the Block Type as part of the RTE Data Type, and +* Enabled the Blocks Toolbar Option. + +## Configure Blocks + +The Block List property editor is configured in the same way as any standard property editor, via the _Data Types_ admin interface. + +To set up your Block List Editor property, create a new _Data Type_ and select **Block List** from the list of available property editors. + +Then you will see the configuration options for a Block List as shown below. + +![Rich Text Editor - Data Type Block Fields](images/rte-data-type-block-fields.jpg) + +The Data Type editor allows you to configure the following properties: + +* **Available Blocks** - Here you will define the Block Types to be available for use in the property. Read more on how to set up Block Types below. +* **Blocks Live editing mode** - Enabling this will edit a Block directly to the Rich Text Editor, making changes appear as you type. + +## Setup Block Types + +Block Types are **Element Types** that need to be created before you can start configuring them as Block Types. This can be done either directly from the property editor setup process, or you can set them up beforehand. If they are set beforehand then they need to be added to the Rich Text Editor afterward. + +Once you add an Element Type as a Block Type on your Rich Text Editor Data Type you will have options to configure it further. + +![Rich Text Editor - Data Type Block Configuration](images/rte-data-type-block-type-editor.jpeg) + +Each Block has a set of properties that are optional to configure. They are described below. + +### Editor Appearance + +By configuring the properties in the group you can customize the experience for your editors when they work with blocks in the Content section. + +* **Label** - Define a label for the appearance of the Block in the editor. The label can use AngularJS template string syntax to display values of properties. +* **Display Inline with text** - When turned on the Block Element will be able to stay in line with text or other elements. If not the Block will stay on its own line. +* **Custom view** - Overwrite the AngularJS view for the block presentation in the Content editor. Use this to make a more visual presentation of the block or even make your own editing experience by adding your own AngularJS controller to the view. +* **Custom stylesheet** - Pick your own stylesheet to be used for this block in the Content editor. By adding a stylesheet the styling of this block will become scoped. Meaning that backoffice styles are no longer present for the view of this block. +* **Overlay editor size** - Set the size for the Content editor overlay for editing this block. + +### Data Models + +It is possible to use two separate Element Types for your Block Types. It's required to have one for Content and optional to add one for Settings. + +* **Content model** - This presents the Element Type used as a model for the content section of this Block. This cannot be changed, but you can open the Element Type to perform edits or view the properties available. Useful when writing your Label. +* **Settings model** - Add a Settings section to your Block based on a given Element Type. When picked you can open the Element Type or choose to remove the settings section again. + +### Catalogue appearance + +These properties refer to how the Block is presented in the Block catalog when editors choose which Blocks to use for their content. + +* **Background color** - Define a background color to be displayed beneath the icon or thumbnail. Eg. `#424242`. +* **Icon Color** - Change the color of the Element Type icon. Eg. `#242424`. +* **Thumbnail** - Pick an image or Scalable Vector Graphics (SVG) file to replace the icon of this Block in the catalog. + +The thumbnails for the catalog are presented in the format of 16:10, and we recommend a resolution of 400px width and 250px height. + +### Advanced + +These properties are relevant when you work with custom views. + +* **Force hide content editor** - If you made a custom view that enables you to edit the content part of a block and you are using default editing mode (not inline) you might want to hide the content editor from the block editor overlay. + +## Rendering Blocks + +To render Blocks in the frontend, you must create Partial Views for each Block. + +The Partial Views must be: + +* Named by the alias of the Element Type that is being used as the Content Model for the Block. +* Placed in the folder `Views/Partials/RichText/Components/`. + +For example, if the Element Type alias of the Content Model is `myBlockType`, a Partial View must be created at `Views/Partials/RichText/Components/MyBlockType.cshtml`. + +The Partial View will receive the model of `Umbraco.Cms.Core.Models.Blocks.RichTextBlockItem`. This gives you the option to access properties of the Content and Settings Models of your Block, as illustrated in the following sample: + +```csharp +@inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage + +@* Output the 'heading' field from the Content Model using the `backgroundColor` field from the Settings Model as background color *@ +

@Model.Content.Value("heading")

+``` + +If you use ModelsBuilder, you can specify the Content Model (and optionally the Settings Model) in the Partial View model. This allows for type-safe access to the Block data. + +The following example shows a Partial View of a Block with `MyBlockType` as the Content Model and `MyBlockSettingsType` as the Settings Model: + +```csharp +@inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage> +@using ContentModels = Umbraco.Cms.Web.Common.PublishedModels; + +@* Output the Heading of field with alias 'heading' from the 'MyBlockType' Content Model *@ +

@Model.Content.Heading

+``` + +## Build a Custom Backoffice View + +Building Custom Views for Block representations in Backoffice is the same for all Block Editors. [Read about building a Custom View for Blocks here](../../../../../tutorials/creating-custom-views-for-blocklist.md). diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor/change-rich-text-editor-ui.md b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor/change-rich-text-editor-ui.md new file mode 100644 index 00000000000..b7a0d938364 --- /dev/null +++ b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor/change-rich-text-editor-ui.md @@ -0,0 +1,57 @@ +# Change Rich Text Editor UI + +{% hint style="info" %} +Umbraco 15 includes two options for rich text editing: Tiptap and TinyMCE. TinyMCE will eventually be phased out of the CMS. + +This article will guide you through switching from TinyMCE UI to the new Tiptap UI. +{% endhint %} + +The following steps will guide you through changing the property editor used for rich text on an existing Document Type. + +## Create a new Data Type + +The first step in this guide is to create a new Rich Text Editor Data Type using the Tiptap property editor. + +As an alternative, you can create a new Data Type using the Tiptap property editor when updating the Document Type. + +{% hint style="info" %} +When swapping the UI used for the Rich Text Editor, you may need to reconfigure the toolbar. +{% endhint %} + +1. Navigate to the **Settings** section of the Umbraco Backoffice. +2. Select **+** next to the Data Types folder. +3. Select **New Data Type...**. + +![Click on + next to the Data Types folder to create a new Data Type](images/rte-swap-new-data-type.png) + +4. Give the new Data Type a name. +5. Click **Select a property editor**. +6. Choose **Rich Text Editor [Tiptap]**. + +![Search for and choose the Rich Text Editor Tiptap UI](images/rte-swap-select-ui.png) + +7. [Configure the Data Type](./configuration.md) to match your needs. +8. **Save** the Data Type. + +## Update the Document Type(s) + +Once you have prepared the new Data Type you need to update the Document Types that should use it. + +1. Expand the **Document Types** folder. +2. Select a Document Type that needs to be updated. +3. Click on the Property used for rich text. + +![Click on the property used for rich Text in the Document Type editor](images/rte-swap-change-property.png) + +4. Hover over the selected property editor and click **Change**. +5. Search for the newly created Data Type and select it. + +![Search for the new Data Type and select it](images/rte-swap-search-and-find-new-ui.png) + +6. **Submit** the changes. +7. **Save** the Document Type. +8. Repeat steps 2-7 for each Document Type that needs to use the new Data Type. + +## Verify your content + +When you have updated all the relevant Document Types, it is recommended to verify the content that uses the Rich Text Editor. diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor/configuration.md b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor/configuration.md new file mode 100644 index 00000000000..d44ba29d33c --- /dev/null +++ b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor/configuration.md @@ -0,0 +1,46 @@ +# Settings + +In this article you can learn about the different options you have for configuring the Rich Text Editor (RTE). + +## Toolbar + +You have full control over which options should be available on the RTE. + +![Toolbar: All options enabled](images/rte-tiptap-all-toolbar-items.png) + +In the example above, all 27 options have been enabled. These options include font styles like bold and italics, bullet lists, and options to embed videos and insert images. + +You can customize the look of the toolbar: + +* Enhance the capabilities of the toolbar by enabling or disabling extensions. +* Use the Toolbar designer to group together items and add additional rows if needed. + +![Enhance and customize the capabilities of the Rich Text Editor toolbar](images/rte-tiptap-capabilities-and-toolbar.png) + +## Dimensions + +Define `height` and `width` of the editor displayed in the content section. + +## Maximum size for inserted images + +Define the maximum size for images added through the Rich Text Editor. + +If inserted images are larger than the dimensions defined here, the images will be resized automatically. + +## Overlay Size + +Select the width of the link picker overlay. The overlay size comes in three sizes: Small, Medium, Large, and Full. + +## Available Blocks + +Blocks can be added as elements in the Rich Text Editor. Configuration and rendering of Blocks are described in the [Blocks in Rich Text Editor](blocks.md) article. + +## Image Upload Folder + +Images added through the RTE are by default added to the root of the Media library. + +Sometimes you might want to add the images to a specific folder. This folder can be configured using the "Image Upload Folder" setting. + +## Ignore User Start Nodes + +Some of the backoffice users might be restricted to a specific part of the content tree. When the "Ignore User Start Nodes" is checked, the users can pick any piece of content from the content tree, when adding internal links through the RTE. diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor/extensions.md b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor/extensions.md new file mode 100644 index 00000000000..3f5ab8cf623 --- /dev/null +++ b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor/extensions.md @@ -0,0 +1,99 @@ +--- +description: Information on how to work with Tiptap extensions in the rich text editor. +--- + +# Extensions + +The Rich Text Editor (RTE) in Umbraco is based on the open-source editor [Tiptap](https://tiptap.dev/). + +Out of the box, Tiptap has limited capabilities and everything is an extension by design. Basic text formatting features such as bold, italic, and underline are their own extensions. This offers great flexibility, making the rich text editor highly configurable. The implementation in Umbraco offers a wide range of built-in extensions to enhance the Tiptap editor capabilities. + +Using the same extension points, this article will show you how to add a custom extension to the rich text editor. + +## Native Tiptap extensions + +Tiptap has a library of supported native extensions. You can find a list of these extensions on the [Tiptap website](https://tiptap.dev/docs/editor/extensions/overview). While many of these are open source, there are also [pro extensions](https://tiptap.dev/docs/guides/pro-extensions) available for commercial subscriptions. + +### Tiptap extension types + +There are two types of extension: `tiptapExtension` and `tiptapToolbarExtension`. + +The `tiptapExtension` extension is used to register a native [Tiptap Extension](https://tiptap.dev/docs/editor/extensions/). These will enhance the capabilities of the rich text editor itself. For example, to enable text formatting, drag-and-drop functionality and spell-checking. + +The `tiptapToolbarExtension` extension adds a toolbar action that interacts with the Tiptap editor (and native Tiptap extensions). + + +## Adding a native extension + +{% hint style="info" %} +This example assumes that you will be creating an Umbraco package using the Vite/Lit/TypeScript setup. +You can learn how to do this [Vite Package Setup](../../../../../customizing/development-flow/vite-package-setup.md) article. +{% endhint %} + +In this example, you will take the native Tiptap open-source extension [Highlight](https://tiptap.dev/docs/editor/extensions/marks/highlight). Then register it with the rich text editor and add a toolbar button to invoke the Task List action. + +1. Install the Highlight extension from the npm registry. + +``` +npm install @tiptap/extension-highlight +``` + +2. Create the code to register the native Tiptap extensions in the rich text editor. + +```js +import { UmbTiptapExtensionApiBase } from '@umbraco-cms/backoffice/tiptap'; +import { Highlight } from '@tiptap/extension-highlight'; + +export default class UmbTiptapHighlightExtensionApi extends UmbTiptapExtensionApiBase { + getTiptapExtensions = () => [Highlight]; +} +``` + +3. Create the toolbar action to invoke the Highlight extension. + +```js +import { UmbTiptapToolbarElementApiBase } from '@umbraco-cms/backoffice/tiptap'; +import type { Editor } from '@umbraco-cms/backoffice/external/tiptap'; + +export default class UmbTiptapToolbarHighlightExtensionApi extends UmbTiptapToolbarElementApiBase { + override execute(editor?: Editor) { + editor?.chain().focus().toggleHighlight().run(); + } +} +``` + +Once you have the above code in place, they can be referenced using a [bundle extension type](../../../../../customizing/extending-overview/extension-types/bundle.md). + +{% code title="manifests.ts" lineNumbers="true" %} +```js +export const manifests: Array = [ + { + type: 'tiptapExtension', + kind: 'button', + alias: 'My Highlight Tiptap Extension', + name: 'My.Tiptap.Highlight', + api: () => import('./highlight.tiptap-api.js'), + meta:{ + icon: "icon-thumbnail-list", + label: "Highlight", + group: "#tiptap_extGroup_formatting" + } + }, + { + type: 'tiptapToolbarExtension', + kind: 'button', + alias: 'My.Tiptap.Toolbar.Highlight', + name: 'My Highlight Tiptap Toolbar Extension', + js: () => import('./highlight.tiptap-toolbar-api.js'), + forExtensions: ["My.Tiptap.Highlight"], + meta:{ + alias: "highlight", + icon: "icon-brush", + label: "Highlight" + } + } +] +``` +{% endcode %} + +Upon restarting Umbraco, the new extension and toolbar action will be available in the Tiptap Data Type configuration settings. diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor/images/rte-data-type-block-fields.jpg b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor/images/rte-data-type-block-fields.jpg new file mode 100644 index 00000000000..ed0ed6da5d4 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor/images/rte-data-type-block-fields.jpg differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor/images/rte-data-type-block-type-editor.jpeg b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor/images/rte-data-type-block-type-editor.jpeg new file mode 100644 index 00000000000..dd54171ac52 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor/images/rte-data-type-block-type-editor.jpeg differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor/images/rte-swap-change-property.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor/images/rte-swap-change-property.png new file mode 100644 index 00000000000..5916b405fb9 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor/images/rte-swap-change-property.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor/images/rte-swap-new-data-type.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor/images/rte-swap-new-data-type.png new file mode 100644 index 00000000000..0d5c77819f4 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor/images/rte-swap-new-data-type.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor/images/rte-swap-search-and-find-new-ui.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor/images/rte-swap-search-and-find-new-ui.png new file mode 100644 index 00000000000..f0cc5f36c26 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor/images/rte-swap-search-and-find-new-ui.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor/images/rte-swap-select-ui.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor/images/rte-swap-select-ui.png new file mode 100644 index 00000000000..a067f1928be Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor/images/rte-swap-select-ui.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor/images/rte-tiptap-all-toolbar-items.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor/images/rte-tiptap-all-toolbar-items.png new file mode 100644 index 00000000000..ce5d1b9e7a0 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor/images/rte-tiptap-all-toolbar-items.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor/images/rte-tiptap-capabilities-and-toolbar.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor/images/rte-tiptap-capabilities-and-toolbar.png new file mode 100644 index 00000000000..0ed995ef81e Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor/images/rte-tiptap-capabilities-and-toolbar.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor/images/rte-tiptap-contentexample.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor/images/rte-tiptap-contentexample.png new file mode 100644 index 00000000000..caed9635fec Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor/images/rte-tiptap-contentexample.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor/images/rte-tiptap-datatypedefinition.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor/images/rte-tiptap-datatypedefinition.png new file mode 100644 index 00000000000..827504d26e9 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor/images/rte-tiptap-datatypedefinition.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/slider.md b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/slider.md new file mode 100644 index 00000000000..6865dd46b97 --- /dev/null +++ b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/slider.md @@ -0,0 +1,140 @@ +# Slider + +`Schema Alias: Umbraco.Slider` + +`UI Alias: Umb.PropertyEditorUi.Slider` + +`Returns: decimal` or `Umbraco.Core.Models.Range` + +Pretty much like the name indicates this Data type enables editors to choose a value with a range using a slider. + +There are two flavors of the slider. One with a single value picker. One with a minimum and maximum value. + +## Data Type Definition Example + +![Slider Data Type Definition](images/Slider-Data-Type-Definition.png) + +## Content Example + +
+ +
+ +## MVC View Example + +### Without Modelsbuilder + +```csharp +@if (Model.HasValue("singleValueSlider")) +{ + var value = Model.Value("singleValueSlider"); +

@value

+} + +@if (Model.HasValue("multiValueSlider")) +{ + var value = Model.Value>("multiValueSlider"); +

@(value.Minimum) and @(value.Maximum)

+} +``` + +### With Modelsbuilder + +```csharp +// with a range off +@if (Model.SingleValueSlider != null) +{ + var value = Model.SingleValueSlider; +

@value

+} + +// with a range on +@if (Model.MultiValueSlider != null) +{ + var minValue = Model.MultiValueSlider.Minimum; + var maxValue = Model.MultiValueSlider.Maximum; +

@minValue and @maxValue

+} +``` + +## Add values programmatically + +See the example below to see how a value can be added or changed programmatically. To update a value of a property editor you need the [Content Service](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Services.ContentService.html). + +{% hint style="info" %} +The example below demonstrates how to add values programmatically using a Razor view. However, this is used for illustrative purposes only and is not the recommended method for production environments. +{% endhint %} + +### With a range off + +```csharp +@using Umbraco.Cms.Core.Services; +@inject IContentService Services; +@{ + // Get access to ContentService + var contentService = Services; + + // Create a variable for the GUID of the page you want to update + var guid = Guid.Parse("32e60db4-1283-4caa-9645-f2153f9888ef"); + + // Get the page using the GUID you've defined + var content = contentService.GetById(guid); // ID of your page + + // Set the value of the property with alias 'singleValueSlider'. + content.SetValue("singleValueSlider", 10); + + // Save the change + contentService.Save(content); +} +``` + +### With a range on + +```csharp +@using Umbraco.Cms.Core.Services; +@inject IContentService Services; +@{ + // Get access to ContentService + var contentService = Services; + + // Create a variable for the GUID of the page you want to update + var guid = Guid.Parse("32e60db4-1283-4caa-9645-f2153f9888ef"); + + // Get the page using the GUID you've defined + var content = contentService.GetById(guid); // ID of your page + + // Create a variable for the desired value of the 'multiValueSlider' property + var range = new Range {Minimum = 10, Maximum = 12}; + + // Set the value of the property with alias 'multiValueSlider'. + content.SetValue("multiValueSlider", range); + + // Save the change + contentService.Save(content); +} +``` + +Although the use of a GUID is preferable, you can also use the numeric ID to get the page: + +```csharp +@{ + // Get the page using it's id + var content = contentService.GetById(1234); +} +``` + +If Modelsbuilder is enabled you can get the alias of the desired property without using a magic string: + +{% include "../../../../.gitbook/includes/obsolete-warning-ipublishedsnapshotaccessor.md" %} + +```csharp +@using Umbraco.Cms.Core.PublishedCache; +@inject IPublishedSnapshotAccessor _publishedSnapshotAccessor; +@{ + // Set the value of the property with alias 'singleValueSlider' + content.SetValue(Home.GetModelPropertyType(_publishedSnapshotAccessor, x => x.SingleValueSlider).Alias, 10); + + // Set the value of the property with alias 'multiValueSlider' + content.SetValue(Home.GetModelPropertyType(_publishedSnapshotAccessor, x => x.MultiValueSlider).Alias, new Range {Minimum = 10, Maximum = 12}); +} +``` diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/tags.md b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/tags.md new file mode 100644 index 00000000000..ea4daff0403 --- /dev/null +++ b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/tags.md @@ -0,0 +1,125 @@ +# Tags + +`Schema Alias: Umbraco.Tags` + +`UI Alias: Umb.PropertyEditorUi.Tags` + +`Returns: IEnumerable` + +The Tags property editor allows you to add multiple tags to a node. + +## Data Type Definition Example + +![Tags Data Type Definition Example](images/tags-DataType.png) + +### Tag group + +The **Tag group** setting provides a way to categorize your tags in groups. So for each category you will create a new instance of the Tags property editor and setup the unique category name for each instance. Whenever a tag is added to an instance of the tags property editor it's added to the tag group, which means it will appear in the Typeahead list when you start to add another tag. Only tags that belong to the specified group will be listed. If you have a "Frontend" group and a "Backend" group the tags from the "Frontend" group will only be listed if you're adding a tag to the Tags property editor configured with the "Frontend" group name and vice versa. + +### Storage type + +Data can be saved in either Comma-Separated Values (CSV) format or in JSON format. By default data is saved in JSON format. The difference between using CSV and JSON is that with JSON you can save a tag, which includes comma separated values. + +There are built-in property value converters, which means you don't need to worry about writing them yourself or parse the JSON output when choosing "JSON" in the storage type field. Therefore [the last code example](tags.md#mvc-view-example---displays-a-list-of-tags) on this page will work out of the box without further ado. + +## Content Examples + +### CSV tags + +![CSV tags example](../built-in-property-editors/images/Csv-example-v8.png) + +### JSON tags + +![JSON tags example](../built-in-property-editors/images/Json-example-v8.png) + +### Tags typeahead + +Whenever a tag has been added it will be visible in the typeahead when you start typing on other pages. + +![Tags typeahead example](../built-in-property-editors/images/Typeahead-v8.png) + +## MVC View Example - displays a list of tags + +### Multiple items - with Modelsbuilder + +```csharp +@if(Model.Tags.Any()){ +
    + @foreach(var tag in Model.Tags){ +
  • @tag
  • + } +
+} +``` + +### Multiple items - without Modelsbuilder + +```csharp +@if(Model.HasValue("tags")) +{ + var tags = Model.Value>("tags"); +
    + @foreach(var tag in tags) + { +
  • @tag
  • + } +
+} +``` + +### Setting Tags Programmatically + +You can use the ContentService to create and update Umbraco content from c# code, when setting tags there is an extension method (SetTagsValue) on IContentBase that helps you set the value for a Tags property. Remember to add the using statement for `Umbraco.Core.Models` to take advantage of it. + +{% hint style="info" %} +The example below demonstrates how to add values programmatically using a Razor view. However, this is used for illustrative purposes only and is not the recommended method for production environments. +{% endhint %} + +```csharp +@using Umbraco.Cms.Core.Serialization +@using Umbraco.Cms.Core.Services +@inject IContentService Services; +@inject IJsonSerializer Serializer; +@{ + // Get access to ContentService + var contentService = Services; + + // Create a variable for the GUID of the page you want to update + var guid = Guid.Parse("9daf8585-6ab6-4ac2-98f0-28bf83aeea6e"); + + // Get the page using the GUID you've defined + var content = contentService.GetById(guid); // ID of your page + + // Set the value of the property with alias 'tags'. + content.SetValue("tags", Serializer.Serialize(new[] { "News", "Umbraco", "Example", "Setting Tags", "Helper" })); + + // Save the change + contentService.Save(content); +} +``` + +Although the use of a GUID is preferable, you can also use the numeric ID to get the page: + +```csharp +@{ + // Get the page using it's id + var content = contentService.GetById(1234); +} +``` + +If Modelsbuilder is enabled, you can get the alias of the desired property without using a magic string: + +{% include "../../../../.gitbook/includes/obsolete-warning-ipublishedsnapshotaccessor.md" %} + +```csharp +@using Umbraco.Cms.Core.PublishedCache; +@inject IPublishedSnapshotAccessor _publishedSnapshotAccessor; +@{ + // Set the value of the property with alias 'tags' + content.SetValue(Home.GetModelPropertyType(_publishedSnapshotAccessor, x => x.Tags).Alias, Serializer.Serialize(new[] { "News", "Umbraco", "Example", "Setting Tags" })); +} +``` + +### More on working with Tags + +More on working with Tags (query all of them) can be found at the [UmbracoHelper reference page](../../../../reference/querying/umbracohelper.md#working-with-tags) diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/textarea.md b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/textarea.md new file mode 100644 index 00000000000..e241e536a29 --- /dev/null +++ b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/textarea.md @@ -0,0 +1,98 @@ +# Textarea + +`Schema Alias: Umbraco.TextArea` + +`UI Alias: Umb.PropertyEditorUi.TextArea` + +`Returns: String` + +Textarea is an HTML textarea control for multiple lines of text. It can be configured to have a fixed character limit, as well as define how big the space for writing can be. By default, there is no character limit unless it's specifically set to a specific value like 200 for instance. If you don't specify the number of rows, 10 will be the amount of rows the textarea will be occupying, unless changed to a custom value. + +## Data Type Definition Example + +![Textarea Data Type Definition](images/Textarea-Setup.png) + +## Settings + +## Content Example + +### Without a character and rows limit + +![Textarea Content Example](../../../../../../10/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Textarea-Content-v8.png) + +### With a character limit and rows limit + +![Textbox Content Example With Limits](../../../../../../10/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Textarea-Content-Limit-v8.png) + +## MVC View Example + +### Without Modelsbuilder + +```csharp +@{ + if (Model.HasValue("description")){ +

@(Model.Value("description"))

+ } +} +``` + +### With Modelsbuilder + +```csharp +@if (!Model.HasValue(Model.Description)) +{ +

@Model.Description

+} +``` + +## Add value programmatically + +See the example below to learn how a value can be added or changed programmatically to a Textarea property. To update a value of a property editor you need the [Content Service](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Services.ContentService.html). + +{% hint style="info" %} +The example below demonstrates how to add values programmatically using a Razor view. However, this is used for illustrative purposes only and is not the recommended method for production environments. +{% endhint %} + +```csharp +@using Umbraco.Cms.Core.Services; +@inject IContentService Services; + +@{ + // Get access to ContentService + var contentService = Services; + + // Create a variable for the GUID of your page + var guid = new Guid("796a8d5c-b7bb-46d9-bc57-ab834d0d1248"); + + // Get the page using the GUID you've just defined + var content = contentService.GetById(guid); + // Set the value of the property with alias 'description' + content.SetValue("description", "This is some text for the text area!"); + + // Save the change + contentService.Save(content); +} +``` + +Although the use of a GUID is preferable, you can also use the numeric ID to get the page: + +```csharp +@{ + // Get the page using it's id + var content = contentService.GetById(1234); +} +``` + +If Modelsbuilder is enabled you can get the alias of the desired property without using a magic string: + +{% include "../../../../.gitbook/includes/obsolete-warning-ipublishedsnapshotaccessor.md" %} + +```csharp +@using Umbraco.Cms.Core.PublishedCache; +@inject IPublishedSnapshotAccessor _publishedSnapshotAccessor; +@{ + + // Set the value of the property with alias 'description' + content.SetValue(Home.GetModelPropertyType(_publishedSnapshotAccessor, x => x.Description).Alias, "This is some text for the text area!"); +} +``` diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/textbox.md b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/textbox.md new file mode 100644 index 00000000000..84cd4b41978 --- /dev/null +++ b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/textbox.md @@ -0,0 +1,106 @@ +--- +description: How to use the TextBox property editors in Umbraco CMS. +--- + +# Textbox + +`Schema Alias: Umbraco.TextBox` + +`UI Alias: Umb.PropertyEditorUi.TextBox` + +`Returns: String` + +Textbox is an HTML input control for text. It can be configured to have a fixed character limit. The default maximum amount of characters is 512 unless it's specifically changed to a lower amount. + +## Data Type Definition Example + +![Textbox Data Type Definition](images/Textbox-Setup.png) + +## Content Example + +### Without a character limit + +![Textbox Content Example](../../../../../../10/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Textbox-Content-v8.png) + +### With a character limit + +![Textbox Content Example Without a Character Limit](../../../../../../10/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Textbox-Content-Limit-v8.png) + +## MVC View Example + +### Without Modelsbuilder + +```csharp +@{ + // Perform an null-check on the field with alias 'pageTitle' + if (Model.HasValue("pageTitle")){ + // Print the value of the field with alias 'pageTitle' +

@(Model.Value("pageTitle"))

+ } +} +``` + +### With Modelsbuilder + +```csharp +@{ + // Perform an null-check on the field with alias 'pageTitle' + @if (!Model.HasValue(Model.PageTitle)) + { + // Print the value of the field with alias 'pageTitle' +

@Model.PageTitle

+ } +} +``` + +## Add values programmatically + +See the example below to see how a value can be added or changed programmatically. To update a value of a property editor you need the [Content Service](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Services.ContentService.html). + +{% hint style="info" %} +The example below demonstrates how to add values programmatically using a Razor view. However, this is used for illustrative purposes only and is not the recommended method for production environments. +{% endhint %} + +```csharp +@using Umbraco.Cms.Core.Services; +@inject IContentService Services; +@{ + // Get access to ContentService + var contentService = Services; + + // Create a variable for the GUID of the page you want to update + var guid = new Guid("32e60db4-1283-4caa-9645-f2153f9888ef"); + + // Get the page using the GUID you've defined + var content = contentService.GetById(guid); // ID of your page + + // Set the value of the property with alias 'pageTitle' + content.SetValue("pageTitle", "Umbraco Demo"); + + // Save the change + contentService.Save(content); +} +``` + +Although the use of a GUID is preferable, you can also use the numeric ID to get the page: + +```csharp +@{ + // Get the page using it's id + var content = contentService.GetById(1234); +} +``` + +If Modelsbuilder is enabled you can get the alias of the desired property without using a magic string: + +{% include "../../../../.gitbook/includes/obsolete-warning-ipublishedsnapshotaccessor.md" %} + +```csharp +@using Umbraco.Cms.Core.PublishedCache; +@inject IPublishedSnapshotAccessor _publishedSnapshotAccessor; +@{ + + // Set the value of the property with alias 'pageTitle' + content.SetValue(Home.GetModelPropertyType(_publishedSnapshotAccessor, x => x.PageTitle).Alias, "Umbraco Demo"); +} +``` diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/true-false.md b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/true-false.md new file mode 100644 index 00000000000..0eb89c9a21e --- /dev/null +++ b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/true-false.md @@ -0,0 +1,97 @@ +# Toggle + +`Schema Alias: Umbraco.TrueFalse` + +`UI Alias: Umb.PropertyEditorUi.Toggle` + +`Returns: Boolean` + +Toggle is a standard checkbox which saves either 0 or 1, depending on the checkbox being checked or not. + +## Data Type Definition Example + +![True/False Data Type Definition](images/Checkbox-Data-Type.png) + +The Toggle property has a setting which allows you to set the default value of the checkbox, either checked (true) or unchecked (false). + +It is also possible to define a label, that will be displayed next to the checkbox on the content. + +## Content Example + +![No Edit Content Example](../../../../../../10/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/Checkbox-Content.png) + +## MVC View Example + +### Without Modelsbuilder + +```csharp +@{ + if (!Model.Value("myCheckBox")) + { +

The Checkbox is not checked!

+ } +} +``` + +### With Modelsbuilder + +```csharp +@{ + if (!Model.MyCheckbox) + { +

The Checkbox is not checked!

+ } +} +``` + +## Add values programmatically + +See the example below to see how a value can be added or changed programmatically. To update a value of a property editor you need the [Content Service](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Services.ContentService.html). + +{% hint style="info" %} +The example below demonstrates how to add values programmatically using a Razor view. However, this is used for illustrative purposes only and is not the recommended method for production environments. +{% endhint %} + +```csharp +@using Umbraco.Cms.Core.Services; +@inject IContentService Services; +@{ + // Get access to ContentService + var contentService = Services; + + // Create a variable for the GUID of the page you want to update + var guid = new Guid("796a8d5c-b7bb-46d9-bc57-ab834d0d1248"); + + // Get the page using the GUID you've defined + var content = contentService.GetById(guid); // ID of your page + + // Set the value of the property with alias 'myCheckBox' + content.SetValue("myCheckBox", true); + + // Save the change + contentService.Save(content); +} +``` + +Although the use of a GUID is preferable, you can also use the numeric ID to get the page: + +```csharp +@{ + // Get the page using it's id + var content = contentService.GetById(1234); +} +``` + +If Modelsbuilder is enabled you can get the alias of the desired property without using a magic string: + +{% include "../../../../.gitbook/includes/obsolete-warning-ipublishedsnapshotaccessor.md" %} + +```csharp +@using Umbraco.Cms.Core.PublishedCache; +@inject IPublishedSnapshotAccessor _publishedSnapshotAccessor; +@{ + // Set the value of the property with alias 'myCheckBox' + content.SetValue(Home.GetModelPropertyType(_publishedSnapshotAccessor,x => x.MyCheckBox).Alias, true); + +} +``` diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/user-picker.md b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/user-picker.md new file mode 100644 index 00000000000..0c8320c1d93 --- /dev/null +++ b/16/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/user-picker.md @@ -0,0 +1,108 @@ +# User Picker + +`Schema Alias: Umbraco.UserPicker` + +`UI Alias: Umb.PropertyEditorUi.UserPicker` + +`Returns: IPublishedContent` + +The user picker opens a panel to pick a specific user from the Users section. The value saved is of type IPublishedContent. + +## Data Type Definition Example + +![Media Picker Data Type Definition](images/User-Picker-DataType.png) + +## Content Example + +![Member Picker Content](../../../../../../10/umbraco-cms/fundamentals/backoffice/property-editors/built-in-property-editors/images/User-Picker-Content-v8.png) + +## MVC View Example + +{% hint style="info" %} +Getting the Value of the property will return the user ID - properties of the User can be accessed by referencing UserService. +{% endhint %} + +### Without Modelsbuilder + +```csharp +@using Umbraco.Cms.Core.Services; +@inject IUserService UserService; +@{ + + if (Model.Value("userPicker") != null) + { + var us = UserService; + var username = us.GetUserById(Model.Value("userPicker")).Name; + +

This is the chosen person: @username

+

This returns the id value of chosen person: @Model.Value("userPicker")

+ } +} +``` + +### With Modelsbuilder + +```csharp +@using Umbraco.Cms.Core.Services; +@inject IUserService UserService; +@{ + if (Model.UserPicker != null) + { + + var us = UserService; + var user = us.GetUserById((int)Model.UserPicker); + +

This is the chosen person: @user.Name

+

This returns the id value of chosen person: @user.Id)

+ } +} +``` + +## Add values programmatically + +See the example below to see how a value can be added or changed programmatically. To update a value of a property editor you need the [Content Service](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Services.ContentService.html). + +{% hint style="info" %} +The example below demonstrates how to add values programmatically using a Razor view. However, this is used for illustrative purposes only and is not the recommended method for production environments. +{% endhint %} + +```csharp +@inject IContentService Services; +@{ + // Get access to ContentService + var contentService = Services; + + // Create a variable for the GUID of the page you want to update + var guid = new Guid("796a8d5c-b7bb-46d9-bc57-ab834d0d1248"); + + // Get the page using the GUID you've defined + var content = contentService.GetById(guid); // ID of your page + + // Set the value of the property with alias 'userPicker'. The value is the specific ID of the user + content.SetValue("userPicker", -1); + + // Save the change + contentService.Save(content); +} +``` + +Although the use of a GUID is preferable, you can also use the numeric ID to get the page: + +```csharp +@{ + // Get the page using it's id + var content = contentService.GetById(1234); +} +``` + +If Modelsbuilder is enabled you can get the alias of the desired property without using a magic string: + +{% include "../../../../.gitbook/includes/obsolete-warning-ipublishedsnapshotaccessor.md" %} + +```csharp +@inject IPublishedSnapshotAccessor _publishedSnapshotAccessor; +@{ + // Set the value of the property with alias 'userPicker' + content.SetValue(Home.GetModelPropertyType(_publishedSnapshotAccessor, x => x.UserPicker).Alias, "Umbraco Demo"); +} +``` diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/images/Media-picker-dataType-v9.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/images/Media-picker-dataType-v9.png new file mode 100644 index 00000000000..36cdbba266d Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/images/Media-picker-dataType-v9.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/images/collection-column-content-picker.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/images/collection-column-content-picker.png new file mode 100644 index 00000000000..f0e664a416b Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/images/collection-column-content-picker.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/images/collection-property-picker.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/images/collection-property-picker.png new file mode 100644 index 00000000000..375af70dc02 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/images/collection-property-picker.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/images/collection-view-cards-content-picker.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/images/collection-view-cards-content-picker.png new file mode 100644 index 00000000000..cfb2e51eb9f Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/images/collection-view-cards-content-picker.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/images/content-picker-picked-value.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/images/content-picker-picked-value.png new file mode 100644 index 00000000000..9726d5c05ea Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/images/content-picker-picked-value.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/images/content-picker-property.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/images/content-picker-property.png new file mode 100644 index 00000000000..a20251e8876 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/images/content-picker-property.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/property-editors/images/media-picker.png b/16/umbraco-cms/fundamentals/backoffice/property-editors/images/media-picker.png new file mode 100644 index 00000000000..0c52763cd41 Binary files /dev/null and b/16/umbraco-cms/fundamentals/backoffice/property-editors/images/media-picker.png differ diff --git a/16/umbraco-cms/fundamentals/backoffice/sections.md b/16/umbraco-cms/fundamentals/backoffice/sections.md new file mode 100644 index 00000000000..53fac242ebc --- /dev/null +++ b/16/umbraco-cms/fundamentals/backoffice/sections.md @@ -0,0 +1,131 @@ +--- +description: >- + In this article you can learn more about the various sections you can find + within the Umbraco Backoffice. +--- + +# Sections + +A section in Umbraco is where you perform specific tasks related to a particular area of Umbraco. For example, Content, Settings, and Users are all sections. You can navigate between the different sections by clicking the corresponding icon in the section menu positioned at the top of the Backoffice. + +![The Section menu is the horizontal menu located at the top of the Umbraco Backoffice.](images/highlight-sections-v14.png) + +Below is a short overview of the default sections in Umbraco CMS: + +## Content + +The Content section contains the content nodes that make up the website. Content is displayed as nodes in the Content tree. + +Nodes in Umbraco can display the following content states: + +* Grayed-out nodes are not published yet. +* Nodes that are currently locked using the Public Access feature. +* Content nodes that contain a collection of nodes. + +To create content, you must define it using Document Types. + +For more information, see the [Defining Content](../data/defining-content/) article. + +## Media + +The Media section contains the media for the website. You can create folders and upload media files, such as images and PDFs. Additionally, you can customize the existing Media Types or define your own from the Settings section. + +For more information, see the [Creating Media](../data/creating-media/) article. + +## Settings + +The Settings section allows you to manage website layout files, languages, media, and content types. It also gives you access to more advanced features such as the Log Viewer and extension insights. + +The Settings section consists of: + +**Structure** + +* Document Types +* Media Types +* Member Types +* Data Types +* Languages +* Document Blueprints + +**Templating** + +* Templates (`.cshtml` files) +* Partial views (`.cshtml` files) +* Stylesheets (`.css` files) +* Scripts (`.js` files) + +**Advanced** + +* Relations +* Log Viewer +* Extension Insights +* Webhooks + +The **Settings** section in the Umbraco backoffice has its own set of default dashboards. + +For more information, see the [Settings Dashboards](settings-dashboards.md) article. + +## Packages + +In this section, you can browse the different packages available for your Umbraco solution. You can also get an overview of all the packages you have installed or created. + +For more information, see the [Packages](../../extending/packages/) article. + +## Users + +The Users section allows administrators to manage user accounts, assign permissions, set user roles, and monitor user activity within the backoffice. It provides control over who can access and modify content, media, and settings in the CMS. + +For more information, see the [Users](../data/users/README.md) article. + +## Members + +The Members section allows to create and manage member profiles, set up member groups, and control Member's access to restricted content on the website. + +For more information, see the [Members](../data/members.md) article. + +## Dictionary + +The Dictionary section is where you create and manage Dictionary Items. By managing these dictionary items, you can ensure consistent and efficient content translation and maintenance across different languages. + +For more information, see the [Dictionary Items](../data/dictionary-items.md) article. + +## Add-Ons + +To enhance Umbraco's functionality, you can integrate plugins and extensions tailored to specific needs. These add-ons expand Umbraco's capabilities, allowing for a more customized and powerful content management experience. + +For example, you can start with core Umbraco features and later decide to integrate additional products. Currently, Umbraco supports add-on products like: + +* **Forms:** Simplifies the creation and management of Forms. +* **Deploy:** Facilitates smooth deployment processes. +* **Workflow:** Enhances content workflows and approval processes. +* **Commerce:** Adds e-commerce capabilities to your site. +* **UI Builder:** Helps in designing and customizing the user interface. + +When you add an add-on product to Umbraco, it appears in the Backoffice as a new section, seamlessly extending your content management capabilities. + +![Add-Ons Section](images/Add-ons.png) + +If you wish to explore the unique features and use cases of Umbraco products, see the [Exploring the Umbraco Products](https://docs.umbraco.com/welcome/getting-started/exploring-the-umbraco-products) article. + +For more information about extending the Umbraco platform through packages and integrations, see the [Umbraco DXP](https://docs.umbraco.com/umbraco-dxp) documentation. + +## Help Section + +The Help section in Umbraco provides documentation and resources to assist in understanding and effectively using the Umbraco CMS. It typically includes the following in the _Getting Started_ Dashboard: + +* **Documentation**: Comprehensive guides, tutorials, and references covering different aspects of Umbraco. +* **Community Forums**: Access to forums where you can ask questions, share knowledge, and seek assistance from other Umbraco community members. +* **Resources**: Stay updated with the latest news, access documentation, watch free video tutorials, and register for live demos. +* **Training**: Learn how to effectively use Umbraco through structured courses, webinars, and hands-on tutorials designed to enhance your proficiency with the CMS. + +The Help section serves as a valuable resource hub in navigating and leveraging the capabilities of the Umbraco CMS effectively. + +## Custom Sections + +Along with the default sections that come with Umbraco, you can create your own [Custom Sections](../../customizing/extending-overview/extension-types/sections/section.md). + +## Access based on User Group + +A User can access a particular section based on the User Group permissions. + +Learn more about how to configure the permissions in the article about [backoffice users](../data/users/README.md). diff --git a/16/umbraco-cms/fundamentals/backoffice/settings-dashboards.md b/16/umbraco-cms/fundamentals/backoffice/settings-dashboards.md new file mode 100644 index 00000000000..edfc19505c0 --- /dev/null +++ b/16/umbraco-cms/fundamentals/backoffice/settings-dashboards.md @@ -0,0 +1,112 @@ +--- +description: >- + A guide displaying the options available in the Settings section in Umbraco + CMS backoffice. +--- + +# Settings Dashboards + +The **Settings** section of the Umbraco backoffice has its own set of default dashboards. In this article, you can get an overview of each dashboard available in the **Settings** section: + +
+ +Welcome + +The Welcome dashboard is the first dashboard in the Settings section. Like all dashboards, it has a customizable view and links to different resources for developing your Umbraco website. + +For more information about creating custom dashboards, see the [Dashboards](../../customizing/extending-overview/extension-types/dashboard.md) article. + +
+ +
+ +Examine Management + +The Examine Management dashboard provides an overview of the Examine functionality available directly within the Umbraco backoffice. The Umbraco backoffice allows you to view details about your Examine indexes and searchers - all in one place. You can see which fields are being indexed and rebuild the indexes if there's a problem. You can also test keywords to see what results will be returned. + +For more information about Examine Management, see the [Examine Management](../../reference/searching/examine/examine-management.md) article. + +
+ +
+ +Published Status + +The Published Status dashboard displays the status of your site in the Published Cache Status section alongside the Content and Media nodes value. The Caches section provides three options: Memory Cache, Database Cache, and Internals. + +* Memory Cache - Reloads the in-memory cache by entirely reloading it from the database cache. Use it when you think that the memory cache has not been properly refreshed. +* Database Cache - Rebuilds the database cache that is the content of the `cmsContentNu` table. Use it when reloading the Memory Cache is not enough and you think that the database cache has not been properly generated. +* Internals - Lets you trigger a NuCache snapshots collection. + +{% hint style="info"%} +As of Umbraco 15 `IPublishedSnapshot`, `IPublishedSnapshotAccessor`, and `SnapshotCache` are all obsolete. +{%endhint%} + +
+ +
+ +Models Builder + +Models builder is a tool that can generate a complete set of strongly-typed published content models for Umbraco. Models are available in both controllers and views. When using the Models Builder, the content cache does not return `IPublishedContent` objects anymore but returns strongly typed models implementing `IPublishedContent`. + +The Models Builder dashboard displays the following information: + +* Details on how Models Builder is configured, that is: `InMemoryAuto`, `Nothing`, `SourceCodeAuto`, and `SourceCodeManual`. +* Provides a button to generate models (if the models mode is `SourceCodeManual` mode only). +* Reports the last error (if any) that would have prevented models from being properly generated. + +For more information about Models Builder, see the [Models Builder](../../reference/templating/modelsbuilder/) article. + +
+ +
+ +Health Check + +Health Checks are used to determine the status of your Umbraco project. It is a handy list of checks to see if your Umbraco installation is configured according to best practices. It's possible to add your custom-built health checks. + +For more information about Health Checks, see the [Health Check](../../extending/health-check/) articles. + +
+ +
+ +Profiling + +You can use the built-in performance profiler to assess the performance when rendering pages. To activate the profiler for a specific page rendering, add `umbDebug=true` to the querystring when requesting the page. + +The Profiling dashboard provides a toggle option - `Activate the profiler by default` to keep the profiler active by default for all page renderings. You can use this option without having to set `umbDebug=true` on each page request. The toggle button sets a cookie named `UMB-DEBUG` in your browser, which then activates the profiler automatically. + +For more information about MiniProfiler, see the [MiniProfiler](../code/debugging/#miniprofiler) section in the [Debugging](../code/debugging/) article. + +
+ +
+ +Telemetry Data + +The Telemetry Data dashboard is a consent screen that is used for collecting system and usage information from your installation. Here, you can see what type of data is being collected and even adjust the level of reporting. Currently, there are three levels available: **Minimal**, **Basic**, and **Detailed**. + +**Detailed** is the default option where the data sent contains: + +* Anonymized site ID, Umbraco version, and packages installed. +* Number of: Root nodes, Content nodes, Media, Document Types, Templates, Languages, Domains, User Group, Users, Members, and Property Editors in use. +* System information: Webserver, server OS, server framework, server OS language, and database provider. +* Configuration settings: Modelsbuilder mode, if custom Umbraco path exists, ASP environment, and if you are in debug mode. + +**Basic** contains: + +* Anonymized site ID, Umbraco version, and packages installed. + +**Minimal** contains: + +* Anonymized site ID only + +You can see the specific data being sent on each of the levels directly in the **Telemetry Data** Dashboard. + +Additionally, Telemetry Data also sends anonymized, analytical data on package usage in Umbraco. Having solid data on package usage is important for both package developers and the Umbraco ecosystem. + +For more information about Package Telemetry, see the [Package Telemetry](https://umbraco.com/blog/umbraco-92-release/) section in the Umbraco 9.2 Release Blog Post. + +
diff --git a/16/umbraco-cms/fundamentals/backoffice/sidebar.md b/16/umbraco-cms/fundamentals/backoffice/sidebar.md new file mode 100644 index 00000000000..9afdd23c903 --- /dev/null +++ b/16/umbraco-cms/fundamentals/backoffice/sidebar.md @@ -0,0 +1,23 @@ +--- +description: >- + This section explains how the concept of infinite editing using the Sidebar in the Umbraco + backoffice works. +--- + +# Sidebar + +{% hint style="info" %} +Sidebar was previously called Infinite editor. +{% endhint %} + +This feature enables you to work with your content without losing the context of what you are doing. + +Document Types are in different sections than content but the sidebar enables you to make changes to them directly from the content you are editing. + +![Sidebar](images/update-dropdown-options.gif) + +In the example showcased above, new options are being added to a Data Type, without losing the context of the content. The example also shows how you can edit images, without being sent to the 'Media' section. + +## Customize + +The sidebar is a feature that comes out of the box with Umbraco. The feature can be customized, which enables you as a developer to improve the workflow for your editors. diff --git a/16/umbraco-cms/fundamentals/backoffice/variants.md b/16/umbraco-cms/fundamentals/backoffice/variants.md new file mode 100644 index 00000000000..c908541958a --- /dev/null +++ b/16/umbraco-cms/fundamentals/backoffice/variants.md @@ -0,0 +1,107 @@ +--- +description: >- + Learn how to use language variants to output your content in multiple + languages. +--- + +# Language Variants + +Language Variants allows you to vary content by culture, so you can allow a content node to exist in multiple languages. + +This article will cover the different aspects of enabling and working with language variants on your Umbraco website. + +## Contents + +* [Video tutorial](variants.md#video-tutorial) +* [How to enable Language Variants](variants.md#how-to-enable-language-variants) +* [Enabling Language Variants on Document Types](variants.md#enabling-language-variants-on-document-types) +* [Working with Language Variants on content](variants.md#working-with-language-variants-on-content) +* [Test your language variants](variants.md#test-your-language-variants) +* [Control User Group permissions on language variants](variants.md#control-user-group-permissions-on-language-variants) +* [Related Links](variants.md#related-links) + +## Video tutorial + +{% embed url="https://www.youtube.com/watch?ab_channel=UmbracoLearningBase&v=TWLqt-jVdyQ" %} +How to use Language Variants in Umbraco +{% endembed %} + +## How to enable Language Variants + +To work with Language Variants you need to have more than one language enabled. This can be done from the `Settings` section: + +![Adding a language](images/adding-a-language.png) + +{% hint style="info" %} +You will always have one default language but each language can be set to mandatory. +{% endhint %} + +## Enabling Language Variants on Document Types + +Now that there are two languages to vary the content with, it needs to be enabled on the Document Types. To do so: + +1. Go to the Document Type in the **structure** section. +2. Open the **settings** page. +3. Toggle **Allow vary by culture**. + +
+ +To allow a property on the Document Type to be varied it will have to be enabled for the property: + +![Allowing Variance on properties](images/varying-properties.png) + +## Working with Language Variants on content + +When you return to your content node you will notice two things: + +1. At the top of the Content tree there will now be a dropdown so you can show the Content tree in the language of your choice. +2. To the right of the content name there is now a dropdown where you can select a language. You can also open a split view so you can see two languages at once. + + ![Allowing Variance on properties](images/Allowing-Variance-on-properties.png) + +Each Property Editor that does not allow variants (an Invariant Property) will by default need to be unlocked in order to be edited. The lock exists to make it clear that this change will affect more languages. Since the value of the invariant properties are shared between all variants on the website. + +![How an invariant property looks when it is locked](../../../../10/umbraco-cms/fundamentals/backoffice/images/invariant-property-locked.png) + +{% hint style="info" %} +Whether or not the lock is enabled on the invariant properties depends on the `AllowEditInvariantFromNonDefault` setting in the `appsettings.json` file. + +For projects created on Umbraco version 10.2 or later, the setting is `true`, by default. If the project is upgraded to version 10.2 or later, the setting will by default be `false`. + +Learn more about the `AllowEditInvariantFromNonDefault` setting in the [Security Settings](../../reference/configuration/securitysettings.md) article. +{% endhint %} + +To read about how you render variant content in Templates, check out the [rendering content section](../design/rendering-content.md). + +## Test your language variants + +Culture and hostnames must be added to your language sites before the content can be tested for variants: + +1. Click **...** next to the Home node and select **Culture and Hostnames**. +2. Add a specific URL per language and save. For eg: An English language variant with English (United States) as the language can be given a specific URL `https://yourwebsite.com/en-us` and a Danish language variant can be given a specific URL `https://yourwebsite.com/dk`. + +The Info content app should now show specific URLs for your language variants. + +## Control User Group permissions on language variants + +{% hint style="info" %} +This feature is available from Umbraco version 10.2. +{% endhint %} + +When you are working with a multilingual site you might want to control who can edit the different variations of the content on the website. + +This can be controlled on a User Group level. All default User Groups, except the Sensitive data group, have access to all languages out of the box. + +When "Allow access to all languages" is not checked, languages can be added and/or removed. This is to determine which variants the users in the user group have access to. + +![Assign access to all or individial languages on the User Group](images/Assign-Access-Languages.png) + +{% hint style="info" %} +Even though the language permissions have been set, a user will still be able to view and browse all the language variations. The permission setting will ensure that only the added languages are editable by users of the User Group. +{% endhint %} + +## Related Links + +* [Umbraco 8: Language Variants (official blog post from Umbraco HQ)](https://umbraco.com/blog/umbraco-8-language-variants/) +* [Language variations](../../reference/language-variation.md) +* [Render varied content in Templates](../design/rendering-content.md) diff --git a/16/umbraco-cms/fundamentals/code/README.md b/16/umbraco-cms/fundamentals/code/README.md new file mode 100644 index 00000000000..f39d3031a5f --- /dev/null +++ b/16/umbraco-cms/fundamentals/code/README.md @@ -0,0 +1,21 @@ +# Code + +## [Using Umbraco's Service APIs](umbraco-services.md) + +Create and update entities in Umbraco from code. + +## [Subscribing to Notifications](subscribing-to-notifications.md) + +Subscribe to notifications to execute custom code on a number of operations. + +## [Creating Forms](creating-forms.md) + +Create, submit and handle HTML forms with controllers. + +## [Debugging your Umbraco site](debugging/README.md) + +Using Tracing and MiniProfiler to debug your code. + +## [Source/Version Control](source-control.md) + +General advice on source controlling your Umbraco implementation. diff --git a/16/umbraco-cms/fundamentals/code/creating-forms.md b/16/umbraco-cms/fundamentals/code/creating-forms.md new file mode 100644 index 00000000000..c7eb94a7728 --- /dev/null +++ b/16/umbraco-cms/fundamentals/code/creating-forms.md @@ -0,0 +1,126 @@ +--- +description: "Information on creating forms in Umbraco" +--- + + +# Creating Forms + +Creating forms requires that you know your way around .NET Core MVC. So if you are familiar with adding view models, views and controllers you are ready to make your first form. + +{% hint style="info" %} +You can also use [Umbraco forms](https://umbraco.com/products/umbraco-forms/). It lets you and/or your editors create and handle forms in the backoffice. This includes setting up validation, redirecting and storing and sending form data. Great UI, extendable and supported by Umbraco HQ. +{% endhint %} + +In this example we'll create a basic contact form containing a name, email and message field. + +## Creating the view model + +First, we're going to create the model for the contact form by adding a new class to the `/Models` folder. If the folder doesn't already exist, create it at the root of your website. Let's call it `ContactFormViewModel.cs` + +```csharp +namespace MyFirstForm.Models; + +public class ContactFormViewModel +{ + public string Name { get; set; } + public string Email { get; set; } + public string Message { get; set; } +} +``` + +Build your solution after adding the model. + +### Creating the view + +Next, we add the view for the form to the `/View/Partials` folder. Because we've added the model and built the solution we can add it as a strongly typed view. + +Name your view "ContactForm". + +The view can be built with standard MVC helpers: + +```csharp +@using MyFirstForm.Controllers +@model MyFirstForm.Models.ContactFormViewModel + +@using (Html.BeginUmbracoForm(nameof(ContactFormController.Submit))) +{ +
+ + +
+
+ + +
+
+ + +
+
+ +} +``` + +### Adding the controller + +Finally, we're going to add the controller. Create a new empty class in the `/Controllers` folder (if the folder doesn't already exist, create it at the root of the website). Name it `ContactFormController` and make it inherit from `SurfaceController`. Inheriting from `SurfaceController` requires that you call its base constructor. If you are using an IDE: Integrated Development Environment, this can be done automatically. + +```csharp +using Microsoft.AspNetCore.Mvc; +using MyFirstForm.Models; +using Umbraco.Cms.Core.Cache; +using Umbraco.Cms.Core.Logging; +using Umbraco.Cms.Core.Routing; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Web; +using Umbraco.Cms.Infrastructure.Persistence; +using Umbraco.Cms.Web.Website.Controllers; + +namespace MyFirstForm.Controllers; + +public class ContactFormController : SurfaceController +{ + public ContactFormController( + IUmbracoContextAccessor umbracoContextAccessor, + IUmbracoDatabaseFactory databaseFactory, + ServiceContext services, + AppCaches appCaches, + IProfilingLogger profilingLogger, + IPublishedUrlProvider publishedUrlProvider) + : base(umbracoContextAccessor, databaseFactory, services, appCaches, profilingLogger, publishedUrlProvider) + {} + + [HttpPost] + public IActionResult Submit(ContactFormViewModel model) + { + if (!ModelState.IsValid) + { + return CurrentUmbracoPage(); + } + + // Work with form data here + + return RedirectToCurrentUmbracoPage(); + } +} +``` + +If the model state is invalid, `CurrentUmbracoPage()` will send the user back to the form. If valid, you can work with the form data, for example, sending an email to site admin and then `RedirectToCurrentUmbracoPage();`. + +## Adding the form to a template + +You can add the form to a template by rendering the partial view: + +```csharp +@using MyFirstForm.Models; + +@{ + Html.RenderPartial("~/Views/Partials/ContactForm.cshtml", new ContactFormViewModel()); +} +``` + +### More information + +- [Surface Controllers](../../reference/routing/surface-controllers/README.md) +- [Custom controllers](../../reference/routing/custom-controllers.md) +- [Routing](../../reference/routing/README.md) diff --git a/16/umbraco-cms/fundamentals/code/debugging/README.md b/16/umbraco-cms/fundamentals/code/debugging/README.md new file mode 100644 index 00000000000..317cc2df3dd --- /dev/null +++ b/16/umbraco-cms/fundamentals/code/debugging/README.md @@ -0,0 +1,128 @@ +# Debugging + +During the development of your Umbraco site you can debug and profile the code you have written to analyse and discover bottlenecks in your code. + +To perform proper debugging on your site you need to set your application to have debug enabled. This can be done by setting `Umbraco:CMS:Hosting:Debug="true"` for example in the `appsettings.json` file: + +```json +{ + "Umbraco": { + "CMS": { + "Hosting": { + "Debug": true + } + } + } +} +``` + +{% hint style="warning" %} +Debug should always be set to false in production. +{% endhint %} + +## Tracing + +Tracing and trace logging are two names for the same technique. You need to configure which log messages you want to log. + +### Enabling Trace Logging + +{% hint style="warning" %} +Do not enable trace logging in your production environment! It reveals an awful lot of (sensitive) information about your production environment. +{% endhint %} + +We recommend at least logging the following namespace at minimum (Verbose) level to enable valuable trace logging. Thereby you will have information about all endpoints that have been executed. + +```json +{ + "Serilog": { + "MinimumLevel": { + "Override": { + "Microsoft.AspNetCore.Mvc": "Verbose" + } + } + } +} +``` + +The logged messages can as always be monitored in the log viewer in backoffice + +## MiniProfiler + +Umbraco includes the Mini Profiler project in its core (see [https://miniprofiler.com](https://miniprofiler.com) for more details). The MiniProfiler profiles your code method calls, giving you a greater insight into code duration and query time for (for example) underlying SQL queries. It's great for tracking down performance issues in your site's implementation. + +### Displaying the MiniProfiler + +To display the profiler ensure that the configuration `Umbraco:CMS:Hosting:Debug` is set to `true` in the appSettings.json file. Thereafter you can add `?umbDebug=true` to the query string of any request. + +Also, ensure your template calls `@Html.RenderProfiler()` as one of the last things. + +
+ +If you click 'Show Trivial' you can seen the kind of detail the MiniProfiler makes available to you about the execution path of your page: + +
+ +and any underlying SQL Statements that are being executed for a part of the execution: + +
+ +![Underlying SQL query details](../../../../../10/umbraco-cms/fundamentals/code/debugging/images/v8-miniprofiler-sql-details.png) + +### Writing to the MiniProfiler + +If you feel like a part of your application is slow you can use the MiniProfiler in your code to test the speed of it. + +All you have to do is inject the IProfiler interface and add a step around your logic: + +```csharp +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.ViewEngines; +using Microsoft.Extensions.Logging; +using Umbraco.Cms.Core.Logging; +using Umbraco.Cms.Core.Web; +using Umbraco.Cms.Web.Common.Controllers; + +namespace MyCustomUmbracoProject; + +public class RootController : RenderController +{ + private readonly IProfiler _profiler; + + public RootController( + IProfiler profiler, + ILogger logger, + ICompositeViewEngine compositeViewEngine, + IUmbracoContextAccessor umbracoContextAccessor) + : base(logger, compositeViewEngine, umbracoContextAccessor) + { + _profiler = profiler; + } + + public override IActionResult Index() + { + // Perform a step + using (_profiler.Step("Sleep")) + { + System.Threading.Thread.Sleep(1000); + } + + return base.Index(); + } +} +``` + +and now in the profiler you can see: + +![Show Trivial](../../../../../10/umbraco-cms/fundamentals/code/debugging/images/v8-miniprofiler-write.png) + +## Umbraco Productivity Tool - Chrome Extension + +If you are using the Google Chrome browser you can install this [Umbraco Productivity Tool Chrome Extension](https://chrome.google.com/webstore/detail/umbraco-productivity/kepkgaeokeknlghbiiipbhgclikjgkdp?hl=en). + +This will allow you to quickly switch between debugging with the MiniProfiler, Trace viewer and normal mode. + +![Umbraco Productivity Tool](../../../../../10/umbraco-cms/fundamentals/code/debugging/images/chrome-tool.png) + +## [Logging](logging.md) + +Learn how Umbraco writes log files and how you can write to them. diff --git a/16/umbraco-cms/fundamentals/code/debugging/images/application-trace.png b/16/umbraco-cms/fundamentals/code/debugging/images/application-trace.png new file mode 100644 index 00000000000..45ab8c78f27 Binary files /dev/null and b/16/umbraco-cms/fundamentals/code/debugging/images/application-trace.png differ diff --git a/16/umbraco-cms/fundamentals/code/debugging/images/chrome-tool.png b/16/umbraco-cms/fundamentals/code/debugging/images/chrome-tool.png new file mode 100644 index 00000000000..922074898af Binary files /dev/null and b/16/umbraco-cms/fundamentals/code/debugging/images/chrome-tool.png differ diff --git a/16/umbraco-cms/fundamentals/code/debugging/images/miniprofile-warning.png b/16/umbraco-cms/fundamentals/code/debugging/images/miniprofile-warning.png new file mode 100644 index 00000000000..db8db49a1d2 Binary files /dev/null and b/16/umbraco-cms/fundamentals/code/debugging/images/miniprofile-warning.png differ diff --git a/16/umbraco-cms/fundamentals/code/debugging/images/show-trivial.png b/16/umbraco-cms/fundamentals/code/debugging/images/show-trivial.png new file mode 100644 index 00000000000..44926efa9b1 Binary files /dev/null and b/16/umbraco-cms/fundamentals/code/debugging/images/show-trivial.png differ diff --git a/16/umbraco-cms/fundamentals/code/debugging/images/trace-logs.png b/16/umbraco-cms/fundamentals/code/debugging/images/trace-logs.png new file mode 100644 index 00000000000..d6795286e4f Binary files /dev/null and b/16/umbraco-cms/fundamentals/code/debugging/images/trace-logs.png differ diff --git a/16/umbraco-cms/fundamentals/code/debugging/images/trace-request-details.png b/16/umbraco-cms/fundamentals/code/debugging/images/trace-request-details.png new file mode 100644 index 00000000000..f698d1f2994 Binary files /dev/null and b/16/umbraco-cms/fundamentals/code/debugging/images/trace-request-details.png differ diff --git a/16/umbraco-cms/fundamentals/code/debugging/images/umb-debug-equals-true.png b/16/umbraco-cms/fundamentals/code/debugging/images/umb-debug-equals-true.png new file mode 100644 index 00000000000..abd3463ac8b Binary files /dev/null and b/16/umbraco-cms/fundamentals/code/debugging/images/umb-debug-equals-true.png differ diff --git a/16/umbraco-cms/fundamentals/code/debugging/images/umbraco-productivity-chrome-extension.png b/16/umbraco-cms/fundamentals/code/debugging/images/umbraco-productivity-chrome-extension.png new file mode 100644 index 00000000000..6eba3609a87 Binary files /dev/null and b/16/umbraco-cms/fundamentals/code/debugging/images/umbraco-productivity-chrome-extension.png differ diff --git a/16/umbraco-cms/fundamentals/code/debugging/images/underlying-sql-queries.png b/16/umbraco-cms/fundamentals/code/debugging/images/underlying-sql-queries.png new file mode 100644 index 00000000000..2ca19c19b50 Binary files /dev/null and b/16/umbraco-cms/fundamentals/code/debugging/images/underlying-sql-queries.png differ diff --git a/16/umbraco-cms/fundamentals/code/debugging/images/v8-miniprofiler-sql-details.png b/16/umbraco-cms/fundamentals/code/debugging/images/v8-miniprofiler-sql-details.png new file mode 100644 index 00000000000..9535949c1e3 Binary files /dev/null and b/16/umbraco-cms/fundamentals/code/debugging/images/v8-miniprofiler-sql-details.png differ diff --git a/16/umbraco-cms/fundamentals/code/debugging/images/v8-miniprofiler-sql-trigger.png b/16/umbraco-cms/fundamentals/code/debugging/images/v8-miniprofiler-sql-trigger.png new file mode 100644 index 00000000000..ea1a4c208c1 Binary files /dev/null and b/16/umbraco-cms/fundamentals/code/debugging/images/v8-miniprofiler-sql-trigger.png differ diff --git a/16/umbraco-cms/fundamentals/code/debugging/images/v8-miniprofiler-trivial.png b/16/umbraco-cms/fundamentals/code/debugging/images/v8-miniprofiler-trivial.png new file mode 100644 index 00000000000..7b36e615056 Binary files /dev/null and b/16/umbraco-cms/fundamentals/code/debugging/images/v8-miniprofiler-trivial.png differ diff --git a/16/umbraco-cms/fundamentals/code/debugging/images/v8-miniprofiler-view.png b/16/umbraco-cms/fundamentals/code/debugging/images/v8-miniprofiler-view.png new file mode 100644 index 00000000000..db39a3caab1 Binary files /dev/null and b/16/umbraco-cms/fundamentals/code/debugging/images/v8-miniprofiler-view.png differ diff --git a/16/umbraco-cms/fundamentals/code/debugging/images/v8-miniprofiler-write.png b/16/umbraco-cms/fundamentals/code/debugging/images/v8-miniprofiler-write.png new file mode 100644 index 00000000000..cd40b9f107c Binary files /dev/null and b/16/umbraco-cms/fundamentals/code/debugging/images/v8-miniprofiler-write.png differ diff --git a/16/umbraco-cms/fundamentals/code/debugging/images/v8-trace-details.PNG b/16/umbraco-cms/fundamentals/code/debugging/images/v8-trace-details.PNG new file mode 100644 index 00000000000..245d2be7418 Binary files /dev/null and b/16/umbraco-cms/fundamentals/code/debugging/images/v8-trace-details.PNG differ diff --git a/16/umbraco-cms/fundamentals/code/debugging/images/v8-trace.PNG b/16/umbraco-cms/fundamentals/code/debugging/images/v8-trace.PNG new file mode 100644 index 00000000000..5b8f095497b Binary files /dev/null and b/16/umbraco-cms/fundamentals/code/debugging/images/v8-trace.PNG differ diff --git a/16/umbraco-cms/fundamentals/code/debugging/images/writing-to-miniprofiler.png b/16/umbraco-cms/fundamentals/code/debugging/images/writing-to-miniprofiler.png new file mode 100644 index 00000000000..7f9cbcfa123 Binary files /dev/null and b/16/umbraco-cms/fundamentals/code/debugging/images/writing-to-miniprofiler.png differ diff --git a/16/umbraco-cms/fundamentals/code/debugging/logging.md b/16/umbraco-cms/fundamentals/code/debugging/logging.md new file mode 100644 index 00000000000..467cbec5f41 --- /dev/null +++ b/16/umbraco-cms/fundamentals/code/debugging/logging.md @@ -0,0 +1,153 @@ +# Logging + +In Umbraco we use the underlying logging framework of [Serilog](https://serilog.net/). + +Out of the box, we write a JSON log file that contains a more detailed logfile. This allows tools to perform searches and correlations on log patterns more efficiently. + +The default location of this file is written to `umbraco/Logs` and contains the Machine name, along with the date too: + +* `umbraco/Logs/UmbracoTraceLog.DELLBOOK.20210809.json` + +## Video overview + +{% embed url="" %} +Watch this video to get an overview of how to view and manage logs and logfiles for your Umbraco CMS website. +{% endembed %} + +## Structured logging + +Serilog is a logging framework that allows us to do structured logging or write log messages using the message template format. This allows us to have a more detailed log message, rather than the traditional text message in a long txt file. + +```cs +2021-08-10 09:33:23,677 [P25776/D1/T22] INFO Umbraco.Cms.Core.Services.Implement.ContentService - Document Home (id=1062) has been published. +``` + +Here is an example of the same log message represented as JSON. More information is available and allows you to search and filter logs based on these properties with an appropriate logging system. + +```json +{ + "@t":"2021-08-10T08:33:23.6778640Z", + "@mt":"Document {ContentName} (id={ContentId}) has been published.", + "ContentName":"Home", + "ContentId":1062, + "SourceContext":"Umbraco.Cms.Core.Services.Implement.ContentService", + "ActionId":"7726d745-d502-4b2d-b55e-97731308041b", + "ActionName":"Umbraco.Cms.Web.BackOffice.Controllers.ContentController.PostSave (Umbraco.Web.BackOffice)", + "RequestId":"8000000c-0012-fb00-b63f-84710c7967bb", + "RequestPath":"/umbraco/backoffice/umbracoapi/content/PostSave", + "ProcessId":25776, + "ProcessName":"iisexpress", + "ThreadId":22, + "AppDomainId":1, + "AppDomainAppId":"2f4961977e5c252fa708f7d83915c269b53a620c", + "MachineName":"DELLBOOK", + "Log4NetLevel":"INFO ", + "HttpRequestId":"318b6dd4-b127-4da3-8339-37701f4d1416", + "HttpRequestNumber":4, + "HttpSessionId":"0cea7395-ba29-e6c6-93ee-7c08a2fd7219" +} +``` + +To learn more about structured logging and message templates you can read more about it over on the [https://messagetemplates.org](https://messagetemplates.org) website. Alternatively watch this video from the Serilog creator - [https://www.youtube.com/watch?v=OhmNp8UPEEg](https://www.youtube.com/watch?v=OhmNp8UPEEg) + +## Writing to the log + +Umbraco writes log messages, but you are also able to use the Umbraco logger to write the log file as needed. This allows you to gain further insights and details about your implementation. + +Here is an example of using the logger to write an Information message to the log. It will contain one property, **Name**, which will output the name variable that is passed into the method. + +```csharp +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; + +namespace Umbraco.Cms.Web.UI.NetCore; + +[ApiController] +[Route("/umbraco/api/myapi")] +public class MyApiController : Controller +{ + private readonly ILogger _logger; + + public MyApiController(ILogger logger) + { + _logger = logger; + } + + /// /umbraco/api/MyApi/SayHello?name=John + [HttpGet("sayhello")] + public string SayHello(string name) + { + _logger.LogInformation("We are saying hello to {Name}", name); + return $"Hello {name}"; + } +} +``` + +{% hint style="info" %} +If you are Logging and using the MiniProfiler, you can inject `IProfilingLogger` that has a reference to both ILogger and IProfiler. +{% endhint %} + +The incorrect way to log the message would be use string interpolation or string concatenation such as + +```csharp +//GOOD - Do use :) +_logger.LogInformation("We are saying hello to {Name}", name); + +//BAD - Do not use :( +_logger.LogInformation($"We are saying hello to {name}"); + +//BAD - Do not use :( +_logger.LogInformation("We are saying hello to " + name); +``` + +The bad examples above will write to the log file, but we will not get a separate property logged with the message. This means we can't find them by searching for log messages that use the message template `We are saying hello to {Name}` + +## Log Levels + +Serilog uses levels as the primary means for assigning importance to log events. The levels in increasing order of importance are: + +1. **Verbose** - tracing information and debugging minutiae; generally only switched on in unusual situations +2. **Debug** - internal control flow and diagnostic state dumps to facilitate pinpointing of recognised problems +3. **Information** - events of interest or that have relevance to outside observers; the default enabled minimum logging level +4. **Warning** - indicators of possible issues or service/functionality degradation +5. **Error** - indicating a failure within the application or connected system +6. **Fatal** - critical errors causing complete failure of the application + +## Configuration + +Serilog can be configured and extended by using the .NET Core configuration such as the AppSetting.json files or environment variables. For more information, see the [Serilog config](../../../reference/configuration/serilog.md) article. + +## The logviewer dashboard + +Learn more about the [logviewer dashboard](../../backoffice/logviewer.md) in the backoffice and how it can be extended. + +## The logviewer desktop app + +This is a tool for viewing & querying JSON log files from disk in the same way as the built in log viewer dashboard. + +[![English badge](https://developer.microsoft.com/store/badges/images/English\_get-it-from-MS.png)](https://www.microsoft.com/store/apps/9N8RV8LKTXRJ?cid=storebadge\&ocid=badge) + +## Serilog project/references shipped + +Umbraco ships with the following Serilog projects, where you can find further information & details with the GitHub readme files as needed. + +* [Serilog](https://github.com/serilog/serilog) +* [Serilog.AspNetCore](https://github.com/serilog/serilog-aspnetcore) +* [Serilog.Enrichers.Process](https://github.com/serilog/serilog-enrichers-process) +* [Serilog.Enrichers.Thread](https://github.com/serilog/serilog-enrichers-thread) +* [Serilog.Expressions](https://github.com/serilog/serilog-expressions) +* [Serilog.Formatting.Compact](https://github.com/serilog/serilog-formatting-compact) +* [Serilog.Formatting.Compact.Reader](https://github.com/serilog/serilog-formatting-compact-reader) +* [Serilog.Settings.Configuration](https://github.com/serilog/serilog-settings-configuration) +* [Serilog.Sinks.Console](https://github.com/serilog/serilog-sinks-console) +* [Serilog.Sinks.File](https://github.com/serilog/serilog-sinks-file) + +## Further Resources + +If you are interested in learning more then the following resources will beneficial: + +* [Serilog](https://serilog.net/) +* [Serilog Community Gitter Chatroom](https://gitter.im/serilog/serilog) +* [Nicholas Blumhardt Blog, creator of Serilog](https://nblumhardt.com/) +* [Serilog Pluralsight Course](https://www.pluralsight.com/courses/modern-structured-logging-serilog-seq) +* [Seq](https://getseq.net/) This is **free** for a single machine such as your own local development computer diff --git a/16/umbraco-cms/fundamentals/code/images/app-data-folders-version8.png b/16/umbraco-cms/fundamentals/code/images/app-data-folders-version8.png new file mode 100644 index 00000000000..cc29d42414f Binary files /dev/null and b/16/umbraco-cms/fundamentals/code/images/app-data-folders-version8.png differ diff --git a/16/umbraco-cms/fundamentals/code/images/app-data-folders.png b/16/umbraco-cms/fundamentals/code/images/app-data-folders.png new file mode 100644 index 00000000000..dee62c605f5 Binary files /dev/null and b/16/umbraco-cms/fundamentals/code/images/app-data-folders.png differ diff --git a/16/umbraco-cms/fundamentals/code/images/file-structure-v10.png b/16/umbraco-cms/fundamentals/code/images/file-structure-v10.png new file mode 100644 index 00000000000..afc7495f8a7 Binary files /dev/null and b/16/umbraco-cms/fundamentals/code/images/file-structure-v10.png differ diff --git a/16/umbraco-cms/fundamentals/code/images/folder-structure-v9.jpg b/16/umbraco-cms/fundamentals/code/images/folder-structure-v9.jpg new file mode 100644 index 00000000000..71612ed5203 Binary files /dev/null and b/16/umbraco-cms/fundamentals/code/images/folder-structure-v9.jpg differ diff --git a/16/umbraco-cms/fundamentals/code/images/log-message.png b/16/umbraco-cms/fundamentals/code/images/log-message.png new file mode 100644 index 00000000000..a14351f98d1 Binary files /dev/null and b/16/umbraco-cms/fundamentals/code/images/log-message.png differ diff --git a/16/umbraco-cms/fundamentals/code/images/log-messages-v14.png b/16/umbraco-cms/fundamentals/code/images/log-messages-v14.png new file mode 100644 index 00000000000..b1d64325736 Binary files /dev/null and b/16/umbraco-cms/fundamentals/code/images/log-messages-v14.png differ diff --git a/16/umbraco-cms/fundamentals/code/images/log-messages.png b/16/umbraco-cms/fundamentals/code/images/log-messages.png new file mode 100644 index 00000000000..ce3872a7669 Binary files /dev/null and b/16/umbraco-cms/fundamentals/code/images/log-messages.png differ diff --git a/16/umbraco-cms/fundamentals/code/images/log-viewer-v14.png b/16/umbraco-cms/fundamentals/code/images/log-viewer-v14.png new file mode 100644 index 00000000000..c0724c980d0 Binary files /dev/null and b/16/umbraco-cms/fundamentals/code/images/log-viewer-v14.png differ diff --git a/16/umbraco-cms/fundamentals/code/images/log-viewer.png b/16/umbraco-cms/fundamentals/code/images/log-viewer.png new file mode 100644 index 00000000000..d2010ec4570 Binary files /dev/null and b/16/umbraco-cms/fundamentals/code/images/log-viewer.png differ diff --git a/16/umbraco-cms/fundamentals/code/images/typical-umbraco-project-folders-version8.png b/16/umbraco-cms/fundamentals/code/images/typical-umbraco-project-folders-version8.png new file mode 100644 index 00000000000..0ddebc9768e Binary files /dev/null and b/16/umbraco-cms/fundamentals/code/images/typical-umbraco-project-folders-version8.png differ diff --git a/16/umbraco-cms/fundamentals/code/images/typical-umbraco-project-folders.png b/16/umbraco-cms/fundamentals/code/images/typical-umbraco-project-folders.png new file mode 100644 index 00000000000..87561e23c53 Binary files /dev/null and b/16/umbraco-cms/fundamentals/code/images/typical-umbraco-project-folders.png differ diff --git a/16/umbraco-cms/fundamentals/code/source-control.md b/16/umbraco-cms/fundamentals/code/source-control.md new file mode 100644 index 00000000000..7b78737f58a --- /dev/null +++ b/16/umbraco-cms/fundamentals/code/source-control.md @@ -0,0 +1,148 @@ +--- +description: >- + In this article you can learn more about how to effectively source control + your Umbraco site. +--- + +# Source Control + +## Umbraco Cloud + +When you are running your site on Umbraco Cloud, source control is a part of the experience. Have a look at the ['Technical overview of an Umbraco Cloud Environment'](https://docs.umbraco.com/umbraco-cloud/getting-started/environments) and the information on ['Working with your Umbraco Cloud project'](https://docs.umbraco.com/umbraco-cloud/setup/set-up#working-with-your-umbraco-cloud-project) for a steer on Source/Version Control good practices. + +## Outside of Umbraco Cloud + +If you are hosting your Umbraco implementation outside of Umbraco Cloud, it's generally considered good practice to set up source/version control for your site implementation files. This is especially a good idea when you are working with a team as it can help you track changes and manage conflicts with other developer's work. + +So if you've made the decision to try to attempt to source/version control your Umbraco implementation work, perhaps setting up a ['Git Repository'](https://en.wikipedia.org/wiki/Git) - then a frequently asked question is: + +### What folders and files should I **exclude** from my source control repository? + +There are lots of different possible variations within your working environment that will affect the best way to set up version control. It depends on whether you are: + +* Working with a team of developers. +* How your development environment is set up. +* Source control repository. +* And also how you intend to build and deploy your solution to your target production environment (build servers, Web Deploy or good old File Transfer Protocol (FTP), etc). + +However, Umbraco ships with a `.gitignore` file with a custom Umbraco section, which will make git ignore the files for you. The Umbraco specific section looks like this: + +``` +## +## Umbraco CMS +## + +# JSON schema file for appsettings.json +appsettings-schema.json + +# Packages created from the backoffice (package.xml/package.zip) +/umbraco/Data/CreatedPackages/ + +# Temp folder containing Examine indexes, MediaCache, etc. +/umbraco/Data/TEMP/ + +# SQLite database files +/umbraco/Data/*.sqlite.db +/umbraco/Data/*.sqlite.db-shm +/umbraco/Data/*.sqlite.db-wal + +# Log files +/umbraco/Logs/ + +# Media files +/wwwroot/media/ +``` + +For most projects, this gitignore will be enough, and this article will not be an exhaustive list of how to version control Umbraco in all possible scenarios. + +However, we will go through the different files in order to give you an insight into the anatomy of an Umbraco website and therefore which parts to include in version control and which parts not to. + +![Typical set of Umbraco Project Folders](../../../../10/umbraco-cms/fundamentals/code/images/file-structure-v10.png) + +#### The Umbraco Folder + +The main folder where the Umbraco CMS resides is the `/umbraco` one inside your project. + +Most of the files and folders within the Umbraco folder, is already added to the default gitignore file. As most of the Umbraco CMS core files are embedded, the `/umbraco` folder contains primarily temporary files and log files, which are all added as Umbraco is installed. + +We recommend that you follow the structure of the default gitignore file, and do not include any temporary files, log files or cache files to git. + +Below are a set of general recommendations regarding the files within the `/umbraco` folder. + +* `/umbraco/data/TEMP` - This folder contains examine indexes, NuCache files, and so on, these are temporary and should not be committed. +* `/umbraco/Logs` - Umbraco currently uses _Serilog_, and a file will be generated in this folder containing trace logs of your application, one JSON file for each day. +* `/umbraco/mediacache` - _ImageSharp_ ships with Umbraco and when an image is requested via the processor, for example, to be resized or cropped, a cached version of the transformed image will be stored in this folder. (The [Imaging settings section](../../reference/configuration/imagingsettings.md) allows you to determine where this cache is stored) + +#### Umbraco Models Builder + +The strategy here will depend a little on which mode ['Umbraco Models Builder'](../../reference/templating/modelsbuilder/) you have opted to work with. + +* **InMemoryAuto** (default), The models are generated in memory, no source control is required. +* **SourceCodeManual** and **SourceCodeAuto**, The models are generated in the `/umbraco/models` folder of your project (or can be configured to be in a different folder or project), allowing you to track changes to the models in source/version control. + +#### Media + +The Media section of Umbraco (unless configured otherwise) stores files in the `/wwwroot/media` folder. These can be updated by editors, in the Umbraco backoffice, so generally speaking, you would not source control these files. + +These are by default ignored by git. + +#### Packages and Plugins + +The **App\_Plugins** folder is the home for all third-party packages installed on your site. + +Depending on how you installed the plugin it will affect how you choose to version control a particular third-party plugin: + +Since plugins are installed via NuGet the installed files for individual plugins shouldn't need to be source controlled (and your deployment process should pull the packages implementation files from NuGet during the build and deployment process). + +{% hint style="info" %} +Each plugin could be different depending on its implementation and functionality. It may contain files that it would be useful to track via Source control, and also files that should be ignored: check with the plugin's supporting website/developer for more information. +{% endhint %} + +### What folders and files should I **include** in my source control repository? + +#### Front-end build + +A lot depends on how you maintain the front-end build of your website, e.g. are you using CSS preprocessors such as Sassy Cascading Style Sheets (SCSS)/ Leaner CSS (LESS) etc - gulp/grunt tasks to combine and minify script resources. + +But generally, you will need to source control all your website's static assets: JavaScript, CSS, Fonts, Page Furniture Images, etc. + +#### Views/Templates/Partials + +Umbraco site templates/views can be edited via the Umbraco Backoffice. They also reside in the `/Views` folder on disk. As these views/templates often include code, it can make a lot of sense to have their changes tracked under source/version control. + +However, this can pose a problem if the templates are updated via the backoffice outside of source control on the production environment. + +This is not an advisable approach since often this will cause breaking changes to your website. + +You would need to manually merge these files before considering a deployment. + +Umbraco Cloud is a good solution in these scenarios, as changes via the backoffice are tracked in a Git repository automatically. + +#### Controllers/Classes/Custom Code + +Any supporting custom code for your application should be in version control, eg any of the following files + +* C# implementation, + * Surface Controllers + * API Controllers + * ViewModels + * Helpers / Extension Methods + * Services etc. +* Supporting class library projects, +* Models generated by Modelsbuilder in SourceCodeManual or SourceCodeAuto mode. + +#### Config + +Your site's `appsettings.json` and `appsettings.Development.json` files contain the configuration for your Umbraco site. + +In general, it is recommended to add these to source control. When you do this, be sure that the file(s) doesn't contain any secrets, like API keys and connection strings. These can be added as needed, but omitted from any commits made to source control. + +#### DocumentType - Backoffice Structure Changes + +When you create and edit eg. Document Types, Media Types, and Data Types in the Umbraco Backoffice these values are stored in the Umbraco Database, making them difficult to source control in a 'file based' version control system. + +There are a series of add-on packages that can help add source control to these structure changes: + +* [_The uSync package (free)_](https://our.umbraco.com/projects/developer-tools/usync/) - which can be configured to serialize these changes to files on disk, in a folder called /uSync - enabling you to source/version control these changes and synchronise them to other environments. +* [_uSync Snapshots (licensed)_](https://our.umbraco.com/packages/developer-tools/usyncsnapshots/) - an extension to uSync, for taking 'before' and 'after' snapshots of an Umbraco site, for managing a release of a 'set of changes' between environments. +* [_Umbraco Deploy on Premise_](https://umbraco.com/products/umbraco-deploy/umbraco-deploy-on-premises/) - the on premise version of the package used by Umbraco Cloud. diff --git a/16/umbraco-cms/fundamentals/code/subscribing-to-notifications.md b/16/umbraco-cms/fundamentals/code/subscribing-to-notifications.md new file mode 100644 index 00000000000..f2fbfdc8d12 --- /dev/null +++ b/16/umbraco-cms/fundamentals/code/subscribing-to-notifications.md @@ -0,0 +1,213 @@ +--- +description: >- + Subscribing to notifications allows you to listen to specific events and run custom code in response. +--- + +# Subscribing To Notifications + +Subscribing to notifications allows you to run custom code in response to specific events, such as when the content is created, updated, or deleted. This feature enables you to automate tasks, validate data, log actions, and implement other custom functionalities to enhance your content management system. + +To follow this guide, ensure you have an Umbraco installation with content, such as the Umbraco starter kit. In this article, we will walk you through the process of logging a message every time a document is published in Umbraco. + +## Create a Notification Handler + +We will add a string of text to the log whenever a document is published. This log is useful for debugging, as different parts of the Umbraco codebase log key events, warnings, and errors. + +1. Add a new C# class file to your project. For example: **~/App_Plugins/Notifications/LogWhenPublishedHandler.cs**. +2. Implement the `INotificationHandler` interface to identify this class as a handler for content publication events. +3. Add the following `using` statements at the top of your file: + + ```csharp + using Umbraco.Cms.Core.Events; + using Umbraco.Cms.Core.Notifications; + ``` + +Your class should now look like this: + +{% code title="LogWhenPublishedHandler.cs" overflow="wrap" lineNumbers="true" %} + +```csharp +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Notifications; + +namespace MyProject; + +public class LogWhenPublishedHandler : INotificationHandler +{ + // Here we will handle a notification. +} +``` + +{% endcode %} + +## Implement the Handle Method + +The `INotificationHandler` interface requires a `Handle` method to be implemented. + +Use the code snippet below to implement the `Handle` method, which takes a `ContentPublishedNotification` parameter. This method will contain the custom logic that runs after content is published. + +{% code title="LogWhenPublishedHandler.cs" overflow="wrap" lineNumbers="true" %} + +```csharp +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Notifications; + +namespace MyProject; + +public class LogWhenPublishedHandler : INotificationHandler +{ + public void Handle(ContentPublishedNotification notification) + { + // The custom code to fire every time content is published goes here! + throw new System.NotImplementedException(); + } +} +``` + +{% endcode %} + +## Inject a Logger for Logging + +To log messages, we need to inject a `Microsoft ILogger` into the handler. + +1. Add a `using` statement for the `Microsoft.Extensions.Logging` namespace to your file. +2. Add a constructor to the handler class that accepts an `ILogger` instance. + +Your updated class should look like this: + +{% code title="LogWhenPublishedHandler.cs" overflow="wrap" lineNumbers="true" %} + +```csharp +using Microsoft.Extensions.Logging; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Notifications; + +namespace MyProject; + +public class LogWhenPublishedHandler : INotificationHandler +{ + private readonly ILogger _logger; + + public LogWhenPublishedHandler(ILogger logger) + { + _logger = logger; + } + + public void Handle(ContentPublishedNotification notification) + { + // The custom code to fire every time content is published goes here! + throw new System.NotImplementedException(); + } +} +``` + +{% endcode %} + +## Log the Content Publication + +Now that we have a logger, let us use it to log a message every time content is published. + +Use the code snippet below to replace the `NotImplementedException` with the code that logs the publication event. + +{% code title="LogWhenPublishedHandler.cs" overflow="wrap" lineNumbers="true" %} + +```csharp +public void Handle(ContentPublishedNotification notification) +{ + // The custom code to fire every time content is published goes here! + _logger.LogInformation("Something has been published."); + foreach (var publishedItem in notification.PublishedEntities) + { + _logger.LogInformation("{ContentName} was published", publishedItem.Name); + } +} +``` + +{% endcode %} + +
+ +See the entire handler class: LogWhenPublishedHandler.cs + +{% code title="LogWhenPublishedHandler.cs" overflow="wrap" lineNumbers="true" %} + +```csharp +using Microsoft.Extensions.Logging; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Notifications; + +namespace MyProject; + +public class LogWhenPublishedHandler : INotificationHandler +{ + private readonly ILogger _logger; + + public LogWhenPublishedHandler(ILogger logger) + { + _logger = logger; + } + + public void Handle(ContentPublishedNotification notification) + { + _logger.LogInformation("{ContentName} was published", publishedItem.Name); + } +} +``` + +{% endcode %} + +
+ +## Register the Notification Handler + +Umbraco needs to know that our handler exists and that it handles `ContentPublishedNotification`. We need to register it in the **Program.cs** file. + +{% hint style="info" %} +Registering dependencies and extensions like this can be done using different methods. Which method to use in each situation depends on whether the extension is added to the Umbraco site or a package. + +Learn more about registering dependencies in the [Dependency Injection](../../reference/using-ioc.md) article. +{% endhint %} + +1. Open the **Program.cs** file at the root of the project. +2. Add the `using Umbraco.Cms.Core.Notifications;` statement. + + ```csharp + using Umbraco.Cms.Core.Notifications; + ``` + +3. Register the handler in the builder configuration by adding the `.AddNotificationHandler()` method call. + + The registration should look like this: + + ```csharp + builder.CreateUmbracoBuilder() + .AddBackOffice() + .AddWebsite() + .AddDeliveryApi() + .AddComposers() + .AddNotificationHandler() + .Build(); + ``` + +## Publishing Content and Verifying Custom Log Messages + +1. Access the Umbraco backoffice and publish a piece of content. +2. Check the log messages in the **Log Viewer** under the **Settings** section. + + ![Log Viewer](images/log-viewer-v14.png) + +3. Search **All Logs**. + +If everything is set up correctly you will see your custom log messages. + +![Messages in Log](images/log-messages-v14.png) + +## Additional Notes + +* The code in this article logs a message after content is published because we subscribed to `ContentPublishedNotification`. +* If you need to run code before content is published, you can subscribe to `ContentPublishingNotification` instead. +* This pattern applies to other events as well, such as **Saving**, **Saved**, **Copying**, **Copied** and so on. + +## More Information + +* For further details on Notifications in Umbraco, see the [Using Notifications](../../reference/notifications/README.md) article. diff --git a/16/umbraco-cms/fundamentals/code/umbraco-services.md b/16/umbraco-cms/fundamentals/code/umbraco-services.md new file mode 100644 index 00000000000..9c2535c8b88 --- /dev/null +++ b/16/umbraco-cms/fundamentals/code/umbraco-services.md @@ -0,0 +1,224 @@ +# Service APIs + +_Whenever you need to modify an entity that Umbraco stores in the database, there are service APIs available to help you. This means that you can create, update and delete any of the core Umbraco entities directly from your custom code._ + +## Accessing the Umbraco services + +Services are typically defined using interfaces. Umbraco has them in the `Umbraco.Cms.Core.Services` namespace, while the specific implementations can be found under the `Umbraco.Cms.Core.Services.Implement` namespace. To use the service APIs you must first access them. Owing to the built-in dependency injection (DI) in ASP.NET Core, configured services are made available throughout Umbraco's codebase. This can be achieved via injecting the specific service you require - the service type or an interface. + +### Access via a Controller + +If you are accessing Umbraco services inside your own controller class, you can add the Umbraco services that you need as constructor parameters. An instance of every service will be provided at runtime from the service container. By saving each one to a local field, you can make use of them within the scope of your class: + +```csharp +public class CustomController +{ + private readonly IContentService _contentService; + + + public ContentController(IContentService contentService) + { + _contentService = contentService; + } + + + public ActionResult PerformAction() + { + var someContent = _contentService.GetById(1234); + } +} +``` + +### Access via a Razor View Template + +Inside a Razor View template, you can make use of a service injection into a view using the `@inject` directive. It works similarly to adding a property to the view, and populating the property using DI: + +```csharp +@using Umbraco.Cms.Core.Services +@inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage +@inject IPublicAccessService PublicAccessService +@{ + Layout = "master.cshtml"; + bool isPageProtected = PublicAccessService.IsProtected(Model.Path); +} +@if (isPageProtected) +{ +

Secret Page - shhshshsh!

+} +``` + +### Access in a Custom Class via dependency injection + +If we wish to subscribe to notifications on one of the services, we'd create a Composer C# class, where you will add a custom `NotificationHandler`. In this custom `NotificationHandler` we would inject the service we need into the public constructor of the class and Umbraco's. The underlying dependency injection framework will do the rest. + +In this example we will wire up to the ContentService 'Saved' event. We will create a new folder in the Media section whenever a new LandingPage is created in the content section to store associated media. Therefore we will need the MediaService available to create the new folder. + +```csharp +public class CustomComposer : IComposer +{ + public void Compose(IUmbracoBuilder builder) + { + builder.AddNotificationHandler(); + } +} +``` + +```csharp +using System.Linq; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; + +namespace Umbraco.Cms.Core.Events; + +public class CustomNotificationHandler : INotificationHandler +{ + // access to the MediaService by injection + private readonly IMediaService _mediaService; + private readonly IRuntimeState _runtimeState; + + public CustomNotificationHandler(IMediaService mediaService, IRuntimeState runtimeState) + { + _mediaService = mediaService; + _runtimeState = runtimeState; + } + + public void Handle(ContentSavedNotification notification) + { + if (_runtimeState.Level != RuntimeLevel.Run) + { + return; + } + + foreach (var contentItem in notification.SavedEntities) + { + // if this is a new landing page create a folder for associated media in the media section + if (contentItem.ContentType.Alias == "landingPage") + { + // we have injected in the mediaService in the constructor for the component see above. + bool hasExistingFolder = _mediaService.GetByLevel(1).Any(f => f.Name == contentItem.Name); + if (!hasExistingFolder) + { + // let's create one (-1 indicates the root of the media section) + IMedia newFolder = _mediaService.CreateMedia(contentItem.Name, -1, "Folder"); + _mediaService.Save(newFolder); + } + } + } + } +} +``` + +#### Custom Class example + +When you're creating your own class, in order to make use of the dependency injection framework, you need register the `ICustomNewsArticleService` service with the type `CustomNewsArticleService`. The `AddScoped()` method registers the service with the lifetime of a single request. + +There are different ways that you can achieve the same outcome: + +Register directly into the **Program.cs** class. + +```csharp +builder.CreateUmbracoBuilder() + .AddBackOffice() + .AddWebsite() + .AddDeliveryApi() + .AddComposers() + .Build(); + +builder.Services.AddScoped(); +``` + +Another approach is to create an extension method to `IUmbracoBuilder` and add it to the startup pipeline. + +```csharp +using Microsoft.Extensions.DependencyInjection; +using Umbraco.Cms.Core.DependencyInjection; + +namespace DefaultNamespace; + +public static class UmbracoBuilderServiceExtensions +{ + public static IUmbracoBuilder AddCustomServices(this IUmbracoBuilder builder) + { + public static IUmbracoBuilder AddCustomServices(this IUmbracoBuilder builder) + { + builder.Services.AddScoped(); + + return builder; + } + } +} +``` + +```csharp +builder.CreateUmbracoBuilder() + .AddBackOffice() + .AddWebsite() + .AddDeliveryApi() + .AddComposers() + .AddCustomServices() + .Build(); +``` + +When creating Umbraco packages you don't have access to the Startup class, therefore it's recommended to use a `IComposer` instead. A Composer gives you access to the `IUmbracoBuilder`. + +If you don't have access to the Startup class + +```csharp +public class CustomComposer : IComposer +{ + public void Compose(IUmbracoBuilder builder) + { + builder.Services.AddScoped(); + } +} +``` + +Then your custom class eg. `CustomNewsArticleService` can take advantage of the same injection to access services eg: + +```csharp +using System.Linq; +using Microsoft.Extensions.Logging; +using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.PublishedCache; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Web; + +namespace Umbraco.Cms.Infrastructure.Services.Implement; + +public class CustomNewsArticleService: ICustomNewsArticleService +{ + private readonly IMediaService _mediaService; + private readonly ILogger _logger; + private readonly IUmbracoContextFactory _contextFactory; + + public CustomNewsArticleService(ILogger logger, IUmbracoContextFactory contextFactory, IMediaService mediaService) + { + _logger = logger; + _contextFactory = contextFactory; + _mediaService = mediaService; + } + + public void DoSomethingWithNewsArticles() + { + using (var contextReference = _contextFactory.EnsureUmbracoContext()) + { + IPublishedContentCache contentCache = contextReference.UmbracoContext.Content; + IPublishedContent newsSection = contentCache.GetAtRoot().FirstOrDefault().Children().FirstOrDefault(f => f.ContentType.Alias == "newsSection"); + if (newsSection == null) + { + _logger.LogDebug("News Section Not Found"); + } + } + // etc + } +} +``` + +### More information + +* [Services in Umbraco](../../reference/management/) +* [Umbraco Notifications reference](../../reference/notifications/) +* [Routes and controllers](../../reference/routing/) diff --git a/16/umbraco-cms/fundamentals/data/README.md b/16/umbraco-cms/fundamentals/data/README.md new file mode 100644 index 00000000000..b3a0948ea0b --- /dev/null +++ b/16/umbraco-cms/fundamentals/data/README.md @@ -0,0 +1,55 @@ +--- +description: This section focuses on how to create data using the Umbraco backoffice +--- + +# Data + +_This section focuses on how to create data using the Umbraco backoffice._ + +There are three kinds of content in Umbraco: + +* Your normal website content exists in the content section. +* Media content such as images, videos, and PDFs are stored in the Media section. +* Finally, Members, are used for user profiles and frontend authentication which you can find in the Members section. + +A fundamental principle in Umbraco is that all content types have a definition (Document Types, Media Types, Member Types). These definitions are highly customizable, meaning you can add properties and have complete control over how the data is organized. + +## [Defining Content](defining-content/) + +Defining Document Types, adding properties, and creating content. + +## [Creating Media](creating-media/) + +Defining Media Types and uploading files to the media section, using upload fields and image cropper. + +## [Creating Members](members.md) + +Defining Member Types and creating members for authentication and user profiles. + +## [Customizing Data Types](data-types/) + +Creating and editing Data Types. + +## [Scheduled Publishing](scheduled-publishing.md) + +Schedule when content should be published / unpublished automatically. + +## [Adding Tabs](adding-tabs.md) + +Overview of how to add and reorder tabs, convert a group to a tab, and manage the “Generic” tab + +## [Users](users/) + +Control who has access to the Umbraco backoffice and what permissions they have. + +## [Relations](relations.md) + +An introduction to Relations and Relation Types, creating, and managing relationships between different entities in Umbraco. + +## [Dictionary Items](dictionary-items.md) + +Using Dictionary Items, you can store a value for each language. Dictionary Items have a unique key that is used to fetch the value of the Dictionary Item. + +## [Content Version Cleanup](content-version-cleanup.md) + +How to keep the noise down whilst ensuring your important content versions stick around indefinitely. diff --git a/16/umbraco-cms/fundamentals/data/adding-tabs.md b/16/umbraco-cms/fundamentals/data/adding-tabs.md new file mode 100644 index 00000000000..b998fa4e8e6 --- /dev/null +++ b/16/umbraco-cms/fundamentals/data/adding-tabs.md @@ -0,0 +1,64 @@ +# Using Tabs + +In this section, an overview is given of how to add and reorder tabs, convert a group to a tab and manage the “Generic” tab. + +## Adding a tab + +Using tabs, you can organize properties in the backoffice to provide a tailored and efficient workflow for editors creating and maintaining Content, Media and Members. + +Tabs allow you to add horizontal organization in your Document Types, Media Types and Member Types. This is handy for types that need a more defined hierarchy or have many properties and groups. + +To add a tab, follow these steps: + +1. Go to **Settings**. +2. Create or select a **Document Type/Media Type/Member Type** and click **Add tab**. + + ![Add tab](images/Add-tab.png.png) + +{% hint style="info" %} +When adding the first tab, all existing groups are automatically added to the tab. +{% endhint %} + +## Reordering tabs + +To reorder tabs, follow these steps: + +1. Go to **Settings**. +2. Select a **Document Type/Media Type/Member Type**. +3. Select **Reorder**. +4. You can drag the tab where you want, manually add a numeric value next to the tab name or use the arrows to set a value. + + This is important when using compositions, as you want to always display a tab/group at a certain position by setting a manual numeric value. + + ![Reorder tabs](images/Reorder-tabs.gif) +5. Select **I am done reordering**. +6. Click **Save**. + +## Convert a group to a tab + +To convert a group to a tab, follow these steps: + +1. Go to **Settings**. +2. Select a **Document Type/Media Type/Member Type**. +3. Select **Reorder**. +4. You can drag the group to the **Convert to tab** option. +5. Select **I am done reordering**. +6. Click **Save**. + +{% hint style="info" %} +Converting a tab back into a group is not possible, as tabs can contain groups, and nested groups are unsupported. To overcome this, create a new group and transfer all tab properties into it, then delete the empty tab. +{% endhint %} + +## Managing the “Generic” tab + +Once you start adding tabs, you might see a “Generic” tab appear. This is done to hold groups and properties that are not assigned to a tab. For example, a group of properties coming from a composition that has no tab. In order to display the groups and properties correctly and have a solid data structure, they will be displayed under the “Generic” tab. + +![Generic-tab](images/Generic-tab.png) + +To manage the **Generic** tab on a **Document Type/Media Type**: + +1. Go to the **Composition** Document Type/Media Type. +2. Click **Add tab** and enter the **Name** for the tab. All existing groups and properties are added to the tab. +3. Go to the **Document Type/Media Type**, the **Generic** tab will now be replaced by the tab from the composition. + + ![Composition Add Tab](images/Composition-add-tab.gif) diff --git a/16/umbraco-cms/fundamentals/data/content-version-cleanup.md b/16/umbraco-cms/fundamentals/data/content-version-cleanup.md new file mode 100644 index 00000000000..9cd7197359b --- /dev/null +++ b/16/umbraco-cms/fundamentals/data/content-version-cleanup.md @@ -0,0 +1,64 @@ +# Content Version Cleanup + +A new version is created whenever you save and publish a content item in Umbraco. This is how you can roll back to a previous version. Every saved version stores a record in the database, not only for the version but also for each content item property for that version. In a multi-lingual site, further rows are added for every culture variation. Over time this amount of data can build and swallow up the capacity of your SQL Server and slow the performance of the Umbraco backoffice. + +## How it works + +The default cleanup policy will: + +* Not delete any versions created over the previous 4 days. The recent version history is preserved. See the `KeepAllVersionsNewerThanDays` setting. +* 'Prune' versions 4 days after they are created. The last version of a content item saved on a particular day will be kept but earlier versions from that day will be deleted. +* Delete all versions older than 90 days. See the `KeepLatestVersionPerDayForDays` setting. +* Never delete any versions that are currently 'published'. +* Never delete any specific versions marked as 'Prevent Cleanup' in the Backoffice version history. + +{% hint style="info" %} + +Based on the default cleanup policy, you can roll back content to the latest version saved on a particular day as long as it was + +* Created within the last 90 days, or +* Marked as "Prevent Cleanup" in the Backoffice version history. + +The **History** section, which acts as an audit log, is not cleared out, and will continue to show logs for versions older than 90 days. +{% endhint %} + +The feature can be configured in the `appSettings.json`: + +```json +{ + "Umbraco": { + "CMS": { + "Content": { + "ContentVersionCleanupPolicy": { + "EnableCleanup": true, + "KeepLatestVersionPerDayForDays": 90, + "KeepAllVersionsNewerThanDays": 4 + } + } + } + } +} +``` + +For sites with stricter requirements, it is possible to opt-out of both options globally, see [ContentSettings](../../reference/configuration/contentsettings.md#contentversioncleanuppolicy) and by Document Type. + +Additionally, it is possible to keep the feature enabled but mark specific versions to keep forever. + +It is worth noting that whilst we delete rows, we do not shrink database files or rebuild indexes. For upgraded sites with a lot of history you may wish to perform these tasks. If they are not part of your regular database maintenance plan already. + +## Overriding global settings + +It is possible to override the global settings per Document Type in the backoffice to prevent unwanted cleanup. This can be managed in the "permissions" Content App for each Document Type. + +
Content Version Cleanup - Document Type overrides

Content Version Cleanup - Document Type overrides

+ +## Prevent cleanup of important versions + +It is possible to mark important content versions as "prevent cleanup" to ensure they are never removed. This happens via the new and improved rollback modal which can be found on the "info" content app for each document. + +1. Open rollback modal. + +
+2. Click **Prevent cleanup** button for each important version. + +
diff --git a/16/umbraco-cms/fundamentals/data/creating-media/README.md b/16/umbraco-cms/fundamentals/data/creating-media/README.md new file mode 100644 index 00000000000..0cf0dbdf0e0 --- /dev/null +++ b/16/umbraco-cms/fundamentals/data/creating-media/README.md @@ -0,0 +1,215 @@ +--- +description: >- + Learn how to work with different types of Media content on your Umbraco + website. +--- + +# Creating Media + +Media in Umbraco CMS is handled the same way as content. You define **Media Types** that act as a base for media items. The following default Media Types are available: + +* Article - used for uploading and storing documents. +* Audio - used for uploading and storing digital audio files. +* File - used for uploading and storing different types of files in the Media section. +* Folder - a container for organizing media items in the Media section tree. +* Image - used for uploading and storing images. +* Vector Graphics (SVG) - used for uploading and storing Scalable Vector Graphics (SVG) files which are text files containing source code to draw the desired image. +* Video - used for uploading and storing video files. + +The default Media Types aim to cover most needs for media on a website. You do not need to define your Media Types to start using the Media section. The tools for organizing and uploading the media are already in place. + +{% hint style="info" %} +If you have upgraded from an older version than 8.14 the Media Types listed above are not added automatically. You can add those types manually yourselves by following the steps below ['Creating a new Media Type'](./#creating-a-media-type). On the [default media types page](default-media-types.md), you will find a detailed overview of all Media Types. +{% endhint %} + +## Uploading Media + +You can upload media in two different ways: + +* [Through the Media section](./#add-media-through-the-media-section) and +* [Through the Content section](./#add-media-through-the-content-section) + +### Add media through the Media section + +From the **Media** section in the Umbraco backoffice, you can add new media items by following either of the approaches defined below: + +* Use the **Create** dialog to create a new Media item in the Media section + + * The Media item will be created based on the type you choose. + * Upload the image or file, give the Media item a name, and click **Save**. + +
Upload Media - Create Button

Upload Media - Create Button

+* Use the Drag and drop feature to add your files to the Media section. + + * Umbraco will automatically detect the Media Type and create the Media item. + * You can drop entire folder structures to recreate that same structure in the Media section. + +
Upload Media - Media section

Upload Media - Media section

+ +### Add media through the Content section + +New media items can be added to your site without interrupting the content creation flow. This can be done following either of the two approaches outlined below. + +* Drag and drop the image(s) from your file explorer directly into the Media Picker property on the Content page. + * Images added this way is automatically added to the user's start node in the Media section of the Umbraco backoffice. + +![Drag and drop images directly into the content](../../../../../10/umbraco-cms/fundamentals/data/creating-media/images/upload-images-from-content.gif) + +* Select the "+" icon to open the "Select media" dialog where you can add images from your file explorer directly or using drag and drop. + +![Add images from the "Select media" dialog](../../../../../10/umbraco-cms/fundamentals/data/creating-media/images/add-image-from-dialog.gif) + +## Creating a folder + +It is always a good idea to start by creating a folder for your media items. It can be a good idea to align these folders with the content on your website. This will give the editors a better overview of the files and enable them to upload media items in the correct place. + +Follow these steps to create a folder in the Media section: + +1. Go to the **Media** section. +2. Select **...** next to **Media**. +3. Select **Create**. +4. Select **Folder**. +5. Enter a name for the folder and select **Save** in the bottom-right corner. + +## Media Type properties + +The **Image** Media Type has 5 properties: **Upload Image**, **Width**, **Height**, **Size**, and **Type**. These are populated once the image is uploaded. The properties can be viewed in the **Media** section and accessed in your Templates. + +Except for the **Folder** Media Type, the other Media Types have 3 properties: **Upload Image**, **Type**, and **Size**. + +Learn more about each Media Type in [the article about default Media Types](default-media-types.md). + +## Organizing and editing media items + +The default view for the Media section is a card view that lets you preview the different files that have been uploaded. + +
Media Section - Cardview

Media Section - Cardview

+ +By selecting multiple media items it is possible to perform bulk operations like moving or deleting the items. + +To edit properties on a single media item, click the name of the item, which you will see once you hover over the item. + +![Edit media item](../../../../../10/umbraco-cms/fundamentals/data/creating-media/images/hover-over.png) + +From the top-right corner of the Media section, you can toggle between the list and grid view. There is also an option to search for the items in the Media section. +![Media Section - List view](images/switch-view-v14.png) + +## Using media items in the Content section + +By adding a **Media Picker** property to a Document Type the editor will have the ability to select media items when creating content. + +## Creating a Media Type + +You can create custom Media Types and control the structure of the Media tree as you would with Document Types. This means you can store information that is specific to the media on the item itself. + +### Video tutorial + +{% embed url="https://youtu.be/aS39zygmJcQ" %} +Watch this tutorial and learn how to create your own Media Types in Umbraco CMS. +{% endembed %} + +A Media Type is created in the **Settings** section using the Media Type editor. + +1. Go to the **Settings** section. +2. Click **...** next to **Media Types**. +3. Click **Create** > **New Media Type**. +4. Name the new Media Type **Employee Image**. +5. Choose an icon by selecting the icon left of the name field. + +You will now see the Media Type editor. It is similar to the editor used for creating Document Types. + +![Creating a Media Type](images/create-new-media-type-v14.png) + +{% hint style="info" %} +Having different folders for different Media Types makes it possible to restrict where media items can be created and added. Only allowing PDF uploads in a certain folder and employee images in another make it easier to keep the Media section organized. +{% endhint %} + +### Adding groups + +Before we start adding properties to the Media Type we need to add a group to put these in. + +1. Click on **Add group**. +2. Call the group _Image_. + +### Adding properties + +We need to add the same properties as on the default **Image** Media Type. These are: + +* `umbracoFile` +* `umbracoWidth` +* `umbracoHeight` +* `umbracoBytes` +* `umbracoExtension` + +Follow the steps outlined below to add the properties to the Media Type: + +1. Click **Add property**. +2. Name it _Upload image_. +3. Change the alias to _umbracoFile_. +4. Click **Select property editor**. +5. Select **Image cropper**. +6. Rename the editor _Employee Image Cropper_. +7. Add two new crops called _Thumbnail_ (200px x 350px) and _wideThumbnail_ (350px x 200px). + + ![Defining crops](images/new-data-type-v14.png) +8. Click **Save**. +9. Click **Add**. +10. Name the remaining four properties _Width_, _Height_, _Size_, and _Type_, and give them the aliases as mentioned above. They should all use the **Label** editor. + +As mentioned before these properties will automatically be populated once an image has been uploaded. + +![Adding properties](images/finished-new-media-type-v14.png) + +## Defining a Media Type folder + +Next up, we will create a folder to hold the employee images. We could use the existing **Folder** Media Type but that would mean editors can upload employee images to any folder of that type. If we create a folder specifically for employee images there is only one place to put them. + +1. Go back to the **Settings** section and create a new Media Type. +2. Name it _Employee Images_. +3. Select the folder icon by clicking the icon to the left of the name. +4. Navigate to the **Structure** tab. +5. Click **Configure as a Collection** under **Presentation.** +6. Choose **List view - Media.** + + ![Configure Collection](images/configure-collection-v14.png) +7. Click **Save**. + +The new folder is created under the Media Types folder. We also need to only allow the Employee Image Media Type in our new folder. Both of these configurations can be set on the **Structure** tab. + +1. Go to the **Structure** tab of the _Employee Images_ folder. +2. Toggle the **Allow at root**. +3. Click **Choose** in the **Allowed Child Node Types**. +4. Select **Employee Image**. +5. Click **Choose**. + +![Permissions](images/employee-images-permissions.png) + +### Creating the folder and media items + +1. Go to the **Media** section. +2. Select **...** next to Media. +3. Click **Create** > **Employee Images** folder. + ![Employee Images](images/employee-images-folder.png) +4. Name it _Employee Images_. +5. Click **Save**. + +{% hint style="info" %} +Uncheck the **Allow at root** option on the **Employee Images** Media Type to prevent the creation of multiple folders of this type. This will only disable the creation of new ones and not affect existing folders. +{% endhint %} + +### Cropping the images + +If you select an image that has been uploaded to the folder you will see the full image and the two defined crops. + +Moving the focal point circle on the image will update the crops to focus accordingly. You can also edit the individual crops by selecting them and moving the image or adjusting the slider to zoom. + +![Cropping images](images/crops-and-focal-point-geo.png) + +## More information + +* [Rendering Media](../../design/rendering-media.md) +* [Customizing Data Types](../data-types/) + +## Related Services + +* [MediaService](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Services.MediaService.html) diff --git a/16/umbraco-cms/fundamentals/data/creating-media/default-media-types.md b/16/umbraco-cms/fundamentals/data/creating-media/default-media-types.md new file mode 100644 index 00000000000..e2a4d9fd098 --- /dev/null +++ b/16/umbraco-cms/fundamentals/data/creating-media/default-media-types.md @@ -0,0 +1,79 @@ +# Default Data/Media Types + +On this page you will find the media types and Data Types in Umbraco. These types are not created automatically after an upgrade. If you want to use the new types, you can create them yourself. + +## Data Types + +### UploadArticle + +The `UploadArticle` Data Type has the following configuration: + +* Property editor: `FileUpload` +* Accepted file extensions: `pdf`, `docx`, `doc` + +### UploadAudio + +The `UploadAudio` Data Type has the following configuration: + +* Property editor: `FileUpload` +* Accepted file extensions: `mp3`, `weba`, `oga`, `opus` + +### UploadVectorGraphics + +The `UploadVectorGraphics` Data Type has the following configuration: + +* Property editor: `FileUpload` +* Accepted file extensions: `svg` + +### UploadVideo + +The `UploadVideo` Data Type has the following configuration: + +* Property editor: `FileUpload` +* Accepted file extensions: `mp4`, `webm`, `ogv` + +## Media Types + +### UmbracoMediaArticle + +The `UmbracoMediaArticle` media type has the following properties: + +* `umbracoFile` - Upload File +* `umbracoExtension` - Label (string) +* `umbracoBytes` - Label (bigint) + +![MediaArticle](images/umbraco-media-article-media-type.png) + +### UmbracoMediaAudio + +The `UmbracoMediaAudio` media type has the following properties: + +* `umbracoFile` Upload Audio +* `umbracoExtension` Label (string) +* `umbracoBytes` Label (bigint) + +![MediaAudio](images/umbraco-media-audio-media-type.png) + +### UmbracoMediaVectorGraphics + +The `UmbracoMediaVectorGraphics` media type has the following properties: + +* `umbracoFile` - Upload Vector Graphics +* `umbracoExtension` Label (string) +* `umbracoBytes` Label (bigint) + +![MediaVectorGraphics](images/umbraco-media-vector-graphicsmedia-type.png) + +### UmbracoMediaVideo + +The `UmbracoMediaVideo` media type has the following properties: + +* `umbracoFile` - Upload Video +* `umbracoExtension` - Label (string) +* `umbracoBytes` - Label (bigint) + +![MediaVideo](images/umbraco-media-video-media-type.png) + +{% hint style="info" %} +You can also create localization files for Media Types. You can read more about this in the [Document Type Localization](../defining-content/document-type-localization.md) article. +{% endhint %} diff --git a/16/umbraco-cms/fundamentals/data/creating-media/images/Creating-Media-Cardview.jpg b/16/umbraco-cms/fundamentals/data/creating-media/images/Creating-Media-Cardview.jpg new file mode 100644 index 00000000000..760ac1a7b27 Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/creating-media/images/Creating-Media-Cardview.jpg differ diff --git a/16/umbraco-cms/fundamentals/data/creating-media/images/Creating-Media-Compositions.jpg b/16/umbraco-cms/fundamentals/data/creating-media/images/Creating-Media-Compositions.jpg new file mode 100644 index 00000000000..6d0052c270c Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/creating-media/images/Creating-Media-Compositions.jpg differ diff --git a/16/umbraco-cms/fundamentals/data/creating-media/images/Creating-Media-Create-740.jpg b/16/umbraco-cms/fundamentals/data/creating-media/images/Creating-Media-Create-740.jpg new file mode 100644 index 00000000000..9668165f375 Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/creating-media/images/Creating-Media-Create-740.jpg differ diff --git a/16/umbraco-cms/fundamentals/data/creating-media/images/Creating-Media-Create.jpg b/16/umbraco-cms/fundamentals/data/creating-media/images/Creating-Media-Create.jpg new file mode 100644 index 00000000000..ea2f4646669 Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/creating-media/images/Creating-Media-Create.jpg differ diff --git a/16/umbraco-cms/fundamentals/data/creating-media/images/Creating-Media-Cropping-740.jpg b/16/umbraco-cms/fundamentals/data/creating-media/images/Creating-Media-Cropping-740.jpg new file mode 100644 index 00000000000..325f6d4dab8 Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/creating-media/images/Creating-Media-Cropping-740.jpg differ diff --git a/16/umbraco-cms/fundamentals/data/creating-media/images/Creating-Media-Cropping.jpg b/16/umbraco-cms/fundamentals/data/creating-media/images/Creating-Media-Cropping.jpg new file mode 100644 index 00000000000..e277d81d4a2 Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/creating-media/images/Creating-Media-Cropping.jpg differ diff --git a/16/umbraco-cms/fundamentals/data/creating-media/images/Creating-Media-Crops-740.jpg b/16/umbraco-cms/fundamentals/data/creating-media/images/Creating-Media-Crops-740.jpg new file mode 100644 index 00000000000..71b27a6d46b Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/creating-media/images/Creating-Media-Crops-740.jpg differ diff --git a/16/umbraco-cms/fundamentals/data/creating-media/images/Creating-Media-Crops.jpg b/16/umbraco-cms/fundamentals/data/creating-media/images/Creating-Media-Crops.jpg new file mode 100644 index 00000000000..8a4a1048d06 Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/creating-media/images/Creating-Media-Crops.jpg differ diff --git a/16/umbraco-cms/fundamentals/data/creating-media/images/Creating-Media-Edit.jpg b/16/umbraco-cms/fundamentals/data/creating-media/images/Creating-Media-Edit.jpg new file mode 100644 index 00000000000..7da4356a08c Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/creating-media/images/Creating-Media-Edit.jpg differ diff --git a/16/umbraco-cms/fundamentals/data/creating-media/images/Creating-Media-Icon.jpg b/16/umbraco-cms/fundamentals/data/creating-media/images/Creating-Media-Icon.jpg new file mode 100644 index 00000000000..fdc42003a05 Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/creating-media/images/Creating-Media-Icon.jpg differ diff --git a/16/umbraco-cms/fundamentals/data/creating-media/images/Creating-Media-Individual-Crop.jpg b/16/umbraco-cms/fundamentals/data/creating-media/images/Creating-Media-Individual-Crop.jpg new file mode 100644 index 00000000000..5f6b3e3e02f Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/creating-media/images/Creating-Media-Individual-Crop.jpg differ diff --git a/16/umbraco-cms/fundamentals/data/creating-media/images/Creating-Media-Listview.jpg b/16/umbraco-cms/fundamentals/data/creating-media/images/Creating-Media-Listview.jpg new file mode 100644 index 00000000000..7ec1eae7dbe Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/creating-media/images/Creating-Media-Listview.jpg differ diff --git a/16/umbraco-cms/fundamentals/data/creating-media/images/Creating-Media-Permissions.jpg b/16/umbraco-cms/fundamentals/data/creating-media/images/Creating-Media-Permissions.jpg new file mode 100644 index 00000000000..56f272424e2 Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/creating-media/images/Creating-Media-Permissions.jpg differ diff --git a/16/umbraco-cms/fundamentals/data/creating-media/images/Creating-Media-Properties-740.jpg b/16/umbraco-cms/fundamentals/data/creating-media/images/Creating-Media-Properties-740.jpg new file mode 100644 index 00000000000..8f4d2600b9c Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/creating-media/images/Creating-Media-Properties-740.jpg differ diff --git a/16/umbraco-cms/fundamentals/data/creating-media/images/Creating-Media-Properties.jpg b/16/umbraco-cms/fundamentals/data/creating-media/images/Creating-Media-Properties.jpg new file mode 100644 index 00000000000..04e5f1c43cb Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/creating-media/images/Creating-Media-Properties.jpg differ diff --git a/16/umbraco-cms/fundamentals/data/creating-media/images/Creating-Media-Structure.jpg b/16/umbraco-cms/fundamentals/data/creating-media/images/Creating-Media-Structure.jpg new file mode 100644 index 00000000000..b8262a0c516 Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/creating-media/images/Creating-Media-Structure.jpg differ diff --git a/16/umbraco-cms/fundamentals/data/creating-media/images/Creating-Media-Tabs.jpg b/16/umbraco-cms/fundamentals/data/creating-media/images/Creating-Media-Tabs.jpg new file mode 100644 index 00000000000..2eae88d1ce0 Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/creating-media/images/Creating-Media-Tabs.jpg differ diff --git a/16/umbraco-cms/fundamentals/data/creating-media/images/Creating-Media-Upload-740.jpg b/16/umbraco-cms/fundamentals/data/creating-media/images/Creating-Media-Upload-740.jpg new file mode 100644 index 00000000000..844927c090c Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/creating-media/images/Creating-Media-Upload-740.jpg differ diff --git a/16/umbraco-cms/fundamentals/data/creating-media/images/Creating-Media-Upload.jpg b/16/umbraco-cms/fundamentals/data/creating-media/images/Creating-Media-Upload.jpg new file mode 100644 index 00000000000..9db5ba2901e Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/creating-media/images/Creating-Media-Upload.jpg differ diff --git a/16/umbraco-cms/fundamentals/data/creating-media/images/add-image-from-dialog.gif b/16/umbraco-cms/fundamentals/data/creating-media/images/add-image-from-dialog.gif new file mode 100644 index 00000000000..1b44a15a995 Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/creating-media/images/add-image-from-dialog.gif differ diff --git a/16/umbraco-cms/fundamentals/data/creating-media/images/configure-collection-v14.png b/16/umbraco-cms/fundamentals/data/creating-media/images/configure-collection-v14.png new file mode 100644 index 00000000000..bdc9ac877de Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/creating-media/images/configure-collection-v14.png differ diff --git a/16/umbraco-cms/fundamentals/data/creating-media/images/create-new-employee.png b/16/umbraco-cms/fundamentals/data/creating-media/images/create-new-employee.png new file mode 100644 index 00000000000..0290519affc Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/creating-media/images/create-new-employee.png differ diff --git a/16/umbraco-cms/fundamentals/data/creating-media/images/create-new-media-type-v14.png b/16/umbraco-cms/fundamentals/data/creating-media/images/create-new-media-type-v14.png new file mode 100644 index 00000000000..c553d42cca0 Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/creating-media/images/create-new-media-type-v14.png differ diff --git a/16/umbraco-cms/fundamentals/data/creating-media/images/create-new-media-type.png b/16/umbraco-cms/fundamentals/data/creating-media/images/create-new-media-type.png new file mode 100644 index 00000000000..34f39e79ed6 Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/creating-media/images/create-new-media-type.png differ diff --git a/16/umbraco-cms/fundamentals/data/creating-media/images/create-new-media-type_new.png b/16/umbraco-cms/fundamentals/data/creating-media/images/create-new-media-type_new.png new file mode 100644 index 00000000000..26023a71cbd Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/creating-media/images/create-new-media-type_new.png differ diff --git a/16/umbraco-cms/fundamentals/data/creating-media/images/crops-and-focal-point-geo.png b/16/umbraco-cms/fundamentals/data/creating-media/images/crops-and-focal-point-geo.png new file mode 100644 index 00000000000..ba6a9b601e5 Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/creating-media/images/crops-and-focal-point-geo.png differ diff --git a/16/umbraco-cms/fundamentals/data/creating-media/images/employee-images-folder.png b/16/umbraco-cms/fundamentals/data/creating-media/images/employee-images-folder.png new file mode 100644 index 00000000000..f1e09a83d86 Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/creating-media/images/employee-images-folder.png differ diff --git a/16/umbraco-cms/fundamentals/data/creating-media/images/employee-images-permissions.png b/16/umbraco-cms/fundamentals/data/creating-media/images/employee-images-permissions.png new file mode 100644 index 00000000000..fd2bb2ffdb3 Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/creating-media/images/employee-images-permissions.png differ diff --git a/16/umbraco-cms/fundamentals/data/creating-media/images/enable-listview.png b/16/umbraco-cms/fundamentals/data/creating-media/images/enable-listview.png new file mode 100644 index 00000000000..790ff9c94ba Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/creating-media/images/enable-listview.png differ diff --git a/16/umbraco-cms/fundamentals/data/creating-media/images/finished-new-media-type-v14.png b/16/umbraco-cms/fundamentals/data/creating-media/images/finished-new-media-type-v14.png new file mode 100644 index 00000000000..7ae7b5eb115 Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/creating-media/images/finished-new-media-type-v14.png differ diff --git a/16/umbraco-cms/fundamentals/data/creating-media/images/finished-new-media-type.png b/16/umbraco-cms/fundamentals/data/creating-media/images/finished-new-media-type.png new file mode 100644 index 00000000000..8ae9b33a6bc Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/creating-media/images/finished-new-media-type.png differ diff --git a/16/umbraco-cms/fundamentals/data/creating-media/images/finished-new-media-type_new.png b/16/umbraco-cms/fundamentals/data/creating-media/images/finished-new-media-type_new.png new file mode 100644 index 00000000000..f90a6f93128 Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/creating-media/images/finished-new-media-type_new.png differ diff --git a/16/umbraco-cms/fundamentals/data/creating-media/images/folder-composition.png b/16/umbraco-cms/fundamentals/data/creating-media/images/folder-composition.png new file mode 100644 index 00000000000..84dba14eb8c Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/creating-media/images/folder-composition.png differ diff --git a/16/umbraco-cms/fundamentals/data/creating-media/images/folder-composition_new.png b/16/umbraco-cms/fundamentals/data/creating-media/images/folder-composition_new.png new file mode 100644 index 00000000000..e3a64d14060 Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/creating-media/images/folder-composition_new.png differ diff --git a/16/umbraco-cms/fundamentals/data/creating-media/images/four-ways-of-uploading.png b/16/umbraco-cms/fundamentals/data/creating-media/images/four-ways-of-uploading.png new file mode 100644 index 00000000000..7695490160b Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/creating-media/images/four-ways-of-uploading.png differ diff --git a/16/umbraco-cms/fundamentals/data/creating-media/images/hover-over.png b/16/umbraco-cms/fundamentals/data/creating-media/images/hover-over.png new file mode 100644 index 00000000000..5e5a75ec5d7 Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/creating-media/images/hover-over.png differ diff --git a/16/umbraco-cms/fundamentals/data/creating-media/images/media-section-11.png b/16/umbraco-cms/fundamentals/data/creating-media/images/media-section-11.png new file mode 100644 index 00000000000..a2f9502fd60 Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/creating-media/images/media-section-11.png differ diff --git a/16/umbraco-cms/fundamentals/data/creating-media/images/new-data-type-v14.png b/16/umbraco-cms/fundamentals/data/creating-media/images/new-data-type-v14.png new file mode 100644 index 00000000000..f3d1ddd61d8 Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/creating-media/images/new-data-type-v14.png differ diff --git a/16/umbraco-cms/fundamentals/data/creating-media/images/new-data-type.png b/16/umbraco-cms/fundamentals/data/creating-media/images/new-data-type.png new file mode 100644 index 00000000000..33d36cfec27 Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/creating-media/images/new-data-type.png differ diff --git a/16/umbraco-cms/fundamentals/data/creating-media/images/new-data-type_new.png b/16/umbraco-cms/fundamentals/data/creating-media/images/new-data-type_new.png new file mode 100644 index 00000000000..5a923f881b0 Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/creating-media/images/new-data-type_new.png differ diff --git a/16/umbraco-cms/fundamentals/data/creating-media/images/select-child-nodes.png b/16/umbraco-cms/fundamentals/data/creating-media/images/select-child-nodes.png new file mode 100644 index 00000000000..3ac9bda9788 Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/creating-media/images/select-child-nodes.png differ diff --git a/16/umbraco-cms/fundamentals/data/creating-media/images/switch-view-11.png b/16/umbraco-cms/fundamentals/data/creating-media/images/switch-view-11.png new file mode 100644 index 00000000000..ec998111774 Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/creating-media/images/switch-view-11.png differ diff --git a/16/umbraco-cms/fundamentals/data/creating-media/images/switch-view-v14.png b/16/umbraco-cms/fundamentals/data/creating-media/images/switch-view-v14.png new file mode 100644 index 00000000000..6fbc1af8ed8 Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/creating-media/images/switch-view-v14.png differ diff --git a/16/umbraco-cms/fundamentals/data/creating-media/images/toggle-listview.png b/16/umbraco-cms/fundamentals/data/creating-media/images/toggle-listview.png new file mode 100644 index 00000000000..daa898459a7 Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/creating-media/images/toggle-listview.png differ diff --git a/16/umbraco-cms/fundamentals/data/creating-media/images/umbraco-media-article-media-type.png b/16/umbraco-cms/fundamentals/data/creating-media/images/umbraco-media-article-media-type.png new file mode 100644 index 00000000000..fff82eeba5b Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/creating-media/images/umbraco-media-article-media-type.png differ diff --git a/16/umbraco-cms/fundamentals/data/creating-media/images/umbraco-media-audio-media-type.png b/16/umbraco-cms/fundamentals/data/creating-media/images/umbraco-media-audio-media-type.png new file mode 100644 index 00000000000..194c4dc201a Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/creating-media/images/umbraco-media-audio-media-type.png differ diff --git a/16/umbraco-cms/fundamentals/data/creating-media/images/umbraco-media-vector-graphicsmedia-type.png b/16/umbraco-cms/fundamentals/data/creating-media/images/umbraco-media-vector-graphicsmedia-type.png new file mode 100644 index 00000000000..e5957f78e79 Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/creating-media/images/umbraco-media-vector-graphicsmedia-type.png differ diff --git a/16/umbraco-cms/fundamentals/data/creating-media/images/umbraco-media-video-media-type.png b/16/umbraco-cms/fundamentals/data/creating-media/images/umbraco-media-video-media-type.png new file mode 100644 index 00000000000..a0cdd81bcf0 Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/creating-media/images/umbraco-media-video-media-type.png differ diff --git a/16/umbraco-cms/fundamentals/data/creating-media/images/upload-images-from-content.gif b/16/umbraco-cms/fundamentals/data/creating-media/images/upload-images-from-content.gif new file mode 100644 index 00000000000..9ffe47785c5 Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/creating-media/images/upload-images-from-content.gif differ diff --git a/16/umbraco-cms/fundamentals/data/creating-media/images/v9-media-types-create1.png b/16/umbraco-cms/fundamentals/data/creating-media/images/v9-media-types-create1.png new file mode 100644 index 00000000000..5ae33eb69c7 Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/creating-media/images/v9-media-types-create1.png differ diff --git a/16/umbraco-cms/fundamentals/data/creating-media/images/v9-media-types-media-section.png b/16/umbraco-cms/fundamentals/data/creating-media/images/v9-media-types-media-section.png new file mode 100644 index 00000000000..fc4077de8b6 Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/creating-media/images/v9-media-types-media-section.png differ diff --git a/16/umbraco-cms/fundamentals/data/creating-media/images/v9-media-types-upload-media.png b/16/umbraco-cms/fundamentals/data/creating-media/images/v9-media-types-upload-media.png new file mode 100644 index 00000000000..c2f0750f6df Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/creating-media/images/v9-media-types-upload-media.png differ diff --git a/16/umbraco-cms/fundamentals/data/creating-media/images/v9-media-types.png b/16/umbraco-cms/fundamentals/data/creating-media/images/v9-media-types.png new file mode 100644 index 00000000000..db993c3f1e7 Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/creating-media/images/v9-media-types.png differ diff --git a/16/umbraco-cms/fundamentals/data/data-types/README.md b/16/umbraco-cms/fundamentals/data/data-types/README.md new file mode 100644 index 00000000000..f9ce2a8d302 --- /dev/null +++ b/16/umbraco-cms/fundamentals/data/data-types/README.md @@ -0,0 +1,69 @@ +--- +description: Learn about the data types in Umbraco. +--- + +# Data Types + +_A Data Type defines the type of input for a property. So when adding a property (on Document Types, Media Types and Members) and selecting the Type you are selecting a Data Type. There are preconfigured Data Types available in Umbraco and more can be added in the Settings section._ + +## What is a Data Type? + +A Data Type can be something basic such as TextString, Number, True/False and so on. Or it can be more complex such as Multi Node Tree Picker, Image Cropper, Block Grid and so on. + +The Data Type references a Property Editor and if the Property Editor has settings these are configured on the Data Type. This means you can have multiple Data Types referencing the same Property Editor. + +An example of this could be to have two dropdown Data Types both referencing the same dropdown Property Editor. One configured to show a list of cities, the other a list of countries. + +## Creating a new Data Type + +Follow these steps to create a new Dropdown Data Type: + +1. Go to the **Settings** section within the backoffice. +2. Select the **+** icon to the right of the **Data Types** folder. +3. Choose **New Data Type...**. +4. Name the Data Type. +5. Click on **Select a property editor**. +6. Find and click on the **Dropdown** editor. +7. Click **Select**. +8. Choose whether to enable multiple selections. +9. Add **options**. +10. **Save** the Data Type once you have added the required configuration. + +![Dropdown List](images/dropdown-data-type-sample.png) + +{% hint style="info" %} +**Data Type configuration** + +**Property Editor** This is where you pick the Property Editor UI that the Data Type will be referencing. By default, Umbraco ships with a wide selection to choose from. Learn more about each of them in the [Default Data Types](default-data-types.md) article. + +In the **Settings** box below, the configuration options specific to the chosen Property Editor UI will be available. Some Property Editors have many configuration options while some only have a few. +{% endhint %} + +When you're happy with the list press **Save**. It is now possible to select this Data Type for a property on Document Types, Media Types, and Members. Doing this will then create a dropdown list for the editor to choose from and save the choice as a string. + +## Customizing Data Types + +To customize an existing Data Type go to the **Settings** section, expand the **Data Types** folder and select the **Data Type** you want to edit. + +Besides the Data Types that are available out of the box there are some additional **Property Editors**. For example, think of the **Slider** and **Block List**. + +## Viewing Data Type References + +To view the Data Type reference, go to the **Settings** section and expand the **Data Types** folder. Select the **Data Type** you wish to view the reference for and click the **Info** tab. + +![Content Picker References](../../../../../10/umbraco-cms/fundamentals/data/data-types/images/viewing-data-type-reference.png) + +This gives you an overview of the Types that currently use the Data Type. + +Learn more about viewing references or implementing tracking in the [Tracking References](../../../customizing/property-editors/tracking.md) article. + +### More information + +* [List of available Data Types](default-data-types.md) +* [Property Editors](../../backoffice/property-editors/) + +### Related Services + +* [DataTypeService](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Services.IDataTypeService.html) + +### Umbraco Learning Base Channel diff --git a/16/umbraco-cms/fundamentals/data/data-types/default-data-types.md b/16/umbraco-cms/fundamentals/data/data-types/default-data-types.md new file mode 100644 index 00000000000..8c9678065f3 --- /dev/null +++ b/16/umbraco-cms/fundamentals/data/data-types/default-data-types.md @@ -0,0 +1,132 @@ +--- +description: Learn about the default data types in Umbraco. +--- + +# Default Data Types + +Here's a list of the default Data Types that come installed with Umbraco. There are plenty more that you can create based on the installed [Property Editors](../../backoffice/property-editors/). + +![Umbraco Data Type List](../../../../../10/umbraco-cms/fundamentals/data/data-types/images/default-data-types-9.png) + +## Approved Color + +Adds a list of approved colors. The approved colors are added as hex values by using the color picker. Optionally, you can enable labels to give the colors different names. + +## Checkbox List + +Displays a list of preset options as a list of checkbox controls. The preset options are added when configuring a Property Editor using the Data Type. Alternatively, the options can also be updated in the **Settings** section under **Data Types**. The value saved is a comma-separated string of IDs. + +## Content Picker + +The Content Picker opens a modal to pick a specific page from the content structure. The value saved is the selected page's ID. + +## Date Picker + +Displays a calendar UI for selecting date and time. The value saved is a standard DateTime value but does not contain time information. + +## Date Picker with time + +Displays a calendar UI for selecting date and time. The value saved is a standard DateTime value. + +## Dropdown + +Displays a list of preset options as a list where only a single value can be selected. The default Data Type does not contain any predefined options. The value saved is the selected value as a string. + +## Dropdown multiple + +Displays a list of preset options as a list where multiple values can be selected. The default Data Type does not contain any predefined options. The value saved is a comma-separated string of IDs. + +## Image Cropper + +Allows to upload and crop images by using a focal point. Specific crop definitions can also be added. This Data Type is used by default on the Image Media Type. + +## Image Media Picker + +The Image Media Picker opens a modal to pick images from the **Media** tree or images from your Computer. The value saved is the selected media node UDI. + +## Label + +Is a non-editable control and can be used to _only_ display the value. It can also be used in the **Media** section to load in values related to the node, such as width, height and file size. + +There are six Label Data Types: + +* Label (bigint) - Allows to save a big integer value for a Label. +* Label (datetime) - Allows to set a DateTime value for a Label. +* Label (decimal) - Allows to set a decimal value for a Label. +* Label (integer) - Allows to set an integer value for a Label. +* Label (string) - Allows to set a long string value for a Label. +* Label (time) - Allows to set time for a Label + +## List View - Content + +This Data Type is used by **Document Types** that are set to display as a Collection. + +## List View - Media + +This Data Type is used by **Media Types** that is set to display as a Collection. + +## List View - Members + +This Data Type is used by **Member Types** that is set to display as a Collection. + +## Media Picker + +The picker opens a modal to pick a specific media item from the Media tree. The value saved is the selected media node UDI. + +## Member Picker + +Displays a dropdown with all the available members. A single member can be selected. The value saved is the ID of the member. + +## Multi URL Picker + +This Data Type allows an editor to add an array of links. These can either be internal Umbraco pages external URLs or links to media in the Media section. The Data Type can be configured by limited number of links it is possible to add. + +## Multiple Image Media Picker + +The picker opens a modal to pick multiple images from the **Media** tree. The value saved is a comma separated string of media node UDIs. + +## Multiple Media Picker + +The picker opens a modal to pick multiple media items from the **Media** tree. The value saved is a comma separated string of media node UDIs. + +## Numeric + +A textbox to input a numeric value. + +## Radiobox + +This Data type enables editors to choose from a list of radiobuttons. + +## Richtext Editor + +A TipTap-based What You See Is What You Get (WYSIWYG) editor. This is the standard editor used to edit a larger amount of text. The editor has a lot of settings, which can be changed on the Richtext editor Data Type in the Settings section. + +Learn more about the configuration options in the [Rich Text Editor articles](../../backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor/README.md). + +## Tags + +A textbox that allows you to use multiple tags on a **Document Type**. You can specify a Tag Group for the Data Type, if you need to use Tags on different sections of your site. + +## Textarea + +A textarea provides a multi-line plain-text editing control. You can set the maximum allowed characters for the textarea and the number of rows, if any. + +## Textstring + +A normal HTML input text field. + +## True/False + +A checkbox which saves either 0 or 1, depending on the checkbox being checked or not. A common use is to create a property with the 'umbracoNaviHide' alias and the Data Type True/False. This will provide editors with the option to hide nodes in the navigation menu on the website. + +## Upload + +Adds an upload field, which allows documents or images to be uploaded to Umbraco. This does not add them to the media library, they are added to the document data. + +There are five Upload Data Types: + +* Upload Article - Used for uploading and storing documents. +* Upload Audio - Used for uploading and storing digital audio files. +* Upload File - Used for uploading and storing different types of files in the Media section +* Upload Vector Graphics - Used for uploading and storing Scalable Vector Graphics (svg) files which are text files containing source code to draw the desired image. +* Upload Video - Used for uploading and storing video files. diff --git a/16/umbraco-cms/fundamentals/data/data-types/images/Data-Types-Create.jpg b/16/umbraco-cms/fundamentals/data/data-types/images/Data-Types-Create.jpg new file mode 100644 index 00000000000..13eda36abaf Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/data-types/images/Data-Types-Create.jpg differ diff --git a/16/umbraco-cms/fundamentals/data/data-types/images/creating-a-data-type-v10.png b/16/umbraco-cms/fundamentals/data/data-types/images/creating-a-data-type-v10.png new file mode 100644 index 00000000000..6223bf77e2f Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/data-types/images/creating-a-data-type-v10.png differ diff --git a/16/umbraco-cms/fundamentals/data/data-types/images/creating-a-data-type.png b/16/umbraco-cms/fundamentals/data/data-types/images/creating-a-data-type.png new file mode 100644 index 00000000000..5df8f2bca42 Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/data-types/images/creating-a-data-type.png differ diff --git a/16/umbraco-cms/fundamentals/data/data-types/images/default-data-types-8.png b/16/umbraco-cms/fundamentals/data/data-types/images/default-data-types-8.png new file mode 100644 index 00000000000..87060cc586a Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/data-types/images/default-data-types-8.png differ diff --git a/16/umbraco-cms/fundamentals/data/data-types/images/default-data-types-9.png b/16/umbraco-cms/fundamentals/data/data-types/images/default-data-types-9.png new file mode 100644 index 00000000000..f7341191ae5 Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/data-types/images/default-data-types-9.png differ diff --git a/16/umbraco-cms/fundamentals/data/data-types/images/default-data-types.png b/16/umbraco-cms/fundamentals/data/data-types/images/default-data-types.png new file mode 100644 index 00000000000..fa5455cf48a Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/data-types/images/default-data-types.png differ diff --git a/16/umbraco-cms/fundamentals/data/data-types/images/dropdown-data-type-sample.png b/16/umbraco-cms/fundamentals/data/data-types/images/dropdown-data-type-sample.png new file mode 100644 index 00000000000..741b15b8d3b Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/data-types/images/dropdown-data-type-sample.png differ diff --git a/16/umbraco-cms/fundamentals/data/data-types/images/viewing-data-type-reference.png b/16/umbraco-cms/fundamentals/data/data-types/images/viewing-data-type-reference.png new file mode 100644 index 00000000000..e911c377f9f Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/data-types/images/viewing-data-type-reference.png differ diff --git a/16/umbraco-cms/fundamentals/data/defining-content/README.md b/16/umbraco-cms/fundamentals/data/defining-content/README.md new file mode 100644 index 00000000000..1349348ac35 --- /dev/null +++ b/16/umbraco-cms/fundamentals/data/defining-content/README.md @@ -0,0 +1,315 @@ +--- +description: Here you'll find an explanation of how content is defined in Umbraco +--- + +# Defining Content + +Before a piece of content can be created in the Umbraco backoffice, first it needs to be defined. That is why, when opening a blank installation of Umbraco, it is not possible to create content in the **Content** section. + +All content needs a blueprint that holds information about what kind of data can be stored on the content node or which editors are used. + +Additionally, it also needs information on how it is organized, where in the structure it is allowed, and so forth. This blueprint or definition is called a **Document Type**. + +## What is a Document Type? + +Document Types define what kind of content can be created in the **Content** section and what an end-user sees and can interact with. + +It can define entire pages or more limited content that can be reused on other nodes ie. a Search Engine Optimization (SEO) group. This means that you are in complete control of what type of content can be created and where. + +Another example is if there is a "`Blog post`" Document Type that has some properties containing a thumbnail, a name, and an author image. Then all blog posts using the "`Blog post`" Document Type, will allow the end user to fill in a thumbnail, author name, and an author image. + +A Document Type contains fieldsets (or groups) where you can apply rules about where the content can be created, allowed template(s), backoffice icons, etc. + +## 1. Creating a Document Type + +A Document Type is created using the Document Type editor in the **Settings** section. + +* Go to the **Settings** section in the backoffice. +* On the **Document Types** node click the menu icon (•••) to bring up the context menu. +* Here choose **Document Type with Template**. This will create a new Document Type with a template. The Template can be found under **Templates** in the **Settings** section which will be assigned as the default template for the Document Type. + +![CreateDoctype](../images/CreateDoctype.png) + +You can also choose to create a **Document Type** without a template and create **Folders** to organize your Document Types. Other options are to create Compositions and Element types, which you can read more about in the [Default Document Types](default-document-types.md) section. + +## 2. Defining the root node + +### Name the Document Type + +First, we're prompted to give the Document Type a **name**. This first Document Type will be the root node for our content, name it "`Home`". + +![Name the Document Type](../images/homePage.png) + +{% hint style="info" %} +The alias of the Document Type is automatically generated based on the property name. If you want to change the auto-generated alias, click the "**lock**" icon. The alias must be in camel case. For example: _`homePage`_. +{% endhint %} + +Having a root node lets you quickly query content as you know everything will be under the root node. + +### Adding Icons to the Document Type + +Choosing appropriate icons for your content nodes is a good way to give editors a better overview of the content tree. + +To set an icon for the Document Type click the document icon in the top left corner. This will open the icon select dialog. Search for "`Home"`and select the icon. This icon will be used in the content tree. + +![Home icon](../images/docTypeIcon.png) + +### Setting Permissions + +This will allow this Document Type to be created as the first content in the **Content** section. + +* Go to the **Structure** tab +* Tick the **Allow as root** toggle +* Save the Document Type by clicking **save** in the bottom right corner. + +![Allow as root](../images/docTypePermissions.png) + +## 3. Creating the content + +Now that we have the Document Type in place, we can create the content. + +* Go to the **Content section** +* Click on the menu icon next to **Content** +* Select the "`Home`" Document Type. We'll name it "`Home`" +* Then click the **Save and Publish** button. + +![Create homepage](../images/createHomepage.png) + +As we haven't created our properties, all we can see on the "`Home`" node is the Properties tab. This tab contains the default properties that are available on all content nodes in Umbraco. + +Let's add some properties of our own. + +## 4. Groups and properties + +In order to add the option to create different content on the same Document Type, some groups and properties need to be added. + +**Groups** + +Groups are a way to organize and structure the properties within the content, making it more manageable. It also makes it more user-friendly for content editors when creating or editing content on a website. + +A name can be added to the group and after properties can be added. + +**Properties** + +Each field on a Document Type is called a property. The property is given a **name**, an **alias** (used to output the properties contained in a template), and an **editor**. + +The editor determines what type of data the property will store and the input method. There is a wide range of default [property editors available](../../backoffice/property-editors/built-in-umbraco-property-editors/) and you can [customize additional editors](../../../customizing/property-editors/). + +Some editors require configuration where a configured editor is saved as a Data Type and can be reused for multiple properties and document types. These can be seen in the **Settings** section under **Data Types**. + +* Go to the **Settings section** +* Expand **Document Types** by clicking the arrow to the left +* Select the "`Home`" Document Type. + +{% hint style="info" %} +**Keyboard Shortcuts** + +Keyboard shortcuts are available when you are working with the Document Type editor. To see which shortcuts are available, click **ALT + SHIFT + K**. +{% endhint %} + +### Adding groups + +Before we start adding properties to the Document Type we need to create a group to hold the property. + +* Click **Add group** and name the group "`Content`". + +
Creating groups

Creating groups

+ +{% hint style="info" %} +_If you have multiple groups and/or properties you can order them with drag and drop or by entering a numeric sort order value. This is done by clicking **Reorder**._ +{% endhint %} + +To convert a group to a tab, see the [Convert a group to a tab](../adding-tabs.md#convert-a-group-to-a-tab) section in the [Using Tabs](../adding-tabs.md) article. + +### Adding properties + +Now that we have created a group we can start adding properties. Let's add a Rich Text editor to the Content group. + +* Click the **Add property** link in the **Content** group. This opens the property settings dialog. Here you can set the metadata for each property (name, alias, description) +* **Choose** which Data Type/property editor to use, and add validation if needed. +* Give the property a **name.** The name will be shown to the editor to make it relevant and understandable. Notice the alias is automatically generated based on the name. We'll name this "`Body Text`". + +![Adding a property](../images/addproperty.png) + +#### Property Editors + +* Clicking **Select Editor** will open the Select Editor dialog. Here, you can choose between all the available editors on the **Create a new Configuration** tab. This will create a new configuration or already configured editors in the **Available Configurations** tab. +* To make it easier to find what you need use the **search field** to filter by typing "`Rich`". Filtering will display configured properties first (under **Available configurations**) and all available editors under that. +* Select the **Rich Text editor** under **Create new**. This will let you configure the editor settings - the Rich Text editor for this property. + +![Choosing the Rich Text editor](../images/selectEditor.png) + +{% hint style="info" %} +The name of the Data Type is based on the name of the Document Type, the name of the property, and the property editor. Flor example: _Home - Body Text - Rich Text editor_. +{% endhint %} + +* Let's **rename** it to "`Basic Rich Text editor`" and only select the most necessary options. + * `bold` + * `italic` + * `alignLeft` + * `alignCenter` + * `link` + * `umbMediaPicker` +* When you are happy with the settings click **Submit**. + +{% hint style="info" %} +Selecting the **Mandatory** toggle makes the property mandatory and the content cannot be saved if no value is entered (in this case, the Richtext editor). + +You have the option to add additional validation by selecting a predefined validation method under the **Custom validation** dropdown (such as email, number, or URL). Or by selecting a custom validation and adding a regular expression. +{% endhint %} + +* **Save** the Document Type. +* If you go to the **Content section** and click on the `Home node` you will now see the `Content`group with the `Body Text` property. + +#### Property descriptions + +The description of the property is not necessary, but it´s a best practice as it guides the editor to use the property correctly. The property description supports some markdown and one custom collapse syntax: + +
+ +Bold + +You can make text in the description bold by wrapping it with `**` + +```md +This is **bold** +``` + +
+ +
+ +Italic + +You can make text in the description italic by wrapping it with `*` + +```md +This is *italic* +``` + +
+ +
+ +Links + +You can make links by using the syntax: + +```md +[This is an absolute link](https://google.com) +[This is a relative link](/umbraco#/media) +``` + +**Note**: Links will always have the`target="_blank"` set. This is currently not configurable. + +
+ +
+ +Images + +You can embed images by using this syntax: + +```md +![Image alt text](https://media.giphy.com/media/bezxCUK2D2TuBCJ7r5/giphy.gif) +``` + +
+ +
+ +Collapsible description + +You can make the description collapsible by using this syntax: + +```md +
+ This is displayed + This is hidden. +
+``` + +
+ +Now if we put it all together we get something like this: + +```md +This is **bold** +This is *italic* +[This is an absolute link](https://google.com) +[This is a relative link](/umbraco#/media) +-- +![Image alt text](https://media.giphy.com/media/bezxCUK2D2TuBCJ7r5/giphy.gif) +``` + +![Makrdown description example](../../../../../15/umbraco-cms/fundamentals/data/images/md-description.gif) + +## 5. Defining child nodes + +Next up we'll create a text page Document Type that will be used for subpages on the site. + +* Go back to the **Settings section** +* **Create** a new Document Type +* **Name** it "`Text Page`". +* Add a **group** called "`Content`" +* This time we'll add two properties: + * First, make a property called "`Summary`" using the **Textarea** editor + * Secondly, create a property called "`Body Text`" and reuse the **Rich Text Editor** Data Type. + +### Creating child nodes + +Before creating a Text Page in **Content** section, allow the Text Page Document Type to be created as a child node to the Home node. + +* **Select** the "`Home`" Document Type +* Go to the **Structure** group. +* Click **Add child** +* **Select** "`Text Page`". + +
Allow Child page

Allow Child page

+ +* Go to the **Content** section +* Click the menu icon (•••) next to the "`Home`" node +* **Select** the "`Text page`" Document Type. We'll name the page "`About us`". We now have a basic content structure. + +
+ +Document Types are flexible and can be used for defining pieces of reusable content or an entire page, to act as a container or repository. + +## 6. Exporting/Importing the Document Type + +You can also export document types from an already existing project/installation and import them into another project/installation. + +* Go to the **Settings** section +* Click **...** next to the **Document type** +* Select **Export**. When you click on the **Export** button, the Document Type is saved as a \*.udt file. + +![Exporting a Document Type](../images/v8Screenshots/export-document-type.png) + +To import a Document Type: + +* Go to the **Settings** section +* Click **...** next to the **Document type** +* Select **Import Document Type** +* Click on the **Import** button and browse to the Document Type you exported. The **Name** and **Alias** of the Document Type are displayed. +* Click **Import** to complete the process. + +![Importing a Document Type](../images/import-document-type.png) + +{% hint style="info" %} +1. If your Document Type contains compositions or inherits from another Document Type, then you need to export/import the Composition/Document Type too. +2. You cannot export/import document types on Umbraco Cloud. +{% endhint %} + +## More information + +* [Rendering Content](../../design/rendering-content.md) +* [Customizing Data Types](../data-types/) + +## Related Services + +* [ContentService](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Services.ContentService.html) +* [ContentTypeService](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Services.ContentTypeService.html) + +## Tutorials + +* [Creating a basic website with Umbraco](../../../tutorials/creating-a-basic-website/) diff --git a/16/umbraco-cms/fundamentals/data/defining-content/default-document-types.md b/16/umbraco-cms/fundamentals/data/defining-content/default-document-types.md new file mode 100644 index 00000000000..c35e8106ff4 --- /dev/null +++ b/16/umbraco-cms/fundamentals/data/defining-content/default-document-types.md @@ -0,0 +1,41 @@ +--- +description: >- + On this page, you will find the default Document Types in Umbraco. If you want + to use these document types, you can create them in the Settings section. +--- + +# Default Document Types + +On this page, you will find the default Document Types in Umbraco. If you want to use these Document Types, you can create them in the Settings section. + +![Create Document Type](../images/CreateDoctype.png) + +## Document Type + +A Document Type defines the content structure and fields that can be used across different content items. When creating a Document Type without a template, you focus solely on structured content without tying it to a specific design or layout. This is ideal for content that doesn’t require direct front-end rendering, such as reusable blocks or items managed within a headless CMS setup. + +Use a Document Type without a template for structured, reusable content like metadata schemas, settings, or components such as product details and author profiles. + +## Document Type with Template + +A Document Type with a Template combines the content structure with a predefined visual presentation. This approach links your structured content with a specific page design, ensuring a consistent and cohesive look and feel across your site. It allows you to manage content and its appearance separately, which makes updates more efficient. + +Use a Document Type with a template for pages like blog posts, landing pages, or services that appear directly on the website. + +## Element Type + +An Element Type is a Document Type *without a template* designed for reusabale and repeatable set of properties. These are primarily used in editors like the Block List Editor or Block Grid Editor to create structured, nested content. + +Element Types are not part of the Content tree and cannot render directly on the front end. When created, the **Is an Element Type** flag in the **Permissions** tab is automatically set to **True**. + +![Element type](../images/element-type.png) + +Use an Element Type when defining building blocks for complex page layouts, such as grid blocks or call-to-action sections. They are an essential part of modular content design. + +## Folder + +The Folder in the Document Types section is used to organize and structure your Document Types within the Settings section. It serves purely as an organizational container, with no impact on the Content section or site functionality. + +Use a Folder to create logical groupings, like a folder named **Compositions** to hold all your Composition Document Types. This makes it easier to navigate and manage your Document Types, especially in larger projects. + +Folders are a powerful tool to keep your Document Types organized and your backoffice tidy. diff --git a/16/umbraco-cms/fundamentals/data/defining-content/document-type-localization.md b/16/umbraco-cms/fundamentals/data/defining-content/document-type-localization.md new file mode 100644 index 00000000000..317b0891eb2 --- /dev/null +++ b/16/umbraco-cms/fundamentals/data/defining-content/document-type-localization.md @@ -0,0 +1,103 @@ +--- +description: Here you will learn how to apply localization for Document Types in Umbraco. +--- + +# Document Type Localization + +{% hint style="warning" %} +This article is a work in progress and may undergo further revisions, updates, or amendments. The information contained herein is subject to change without notice. +{% endhint %} + +The Umbraco backoffice is localized to match the [user's configured language](../users/README.md). + +When defining a Document Type, you can apply localization to: + +* Document Type name and description. +* Property names and descriptions. +* Custom property validation messages. +* Tab and group names. + +Setting up localization for Document Types is a two-step process: + +* Create the localizations in [user defined backoffice localization file](../../../customizing/foundation/localization.md). +* Apply the localizations to the Document Type. + +{% hint style="info" %} +Everything in this article also applies to defining [Media Types](../creating-media/) and Member Types. +{% endhint %} + +## Creating localizations + +Once you have [registered a backoffice localization file](../../../customizing/extending-overview/extension-types/localization.md), you can add your localization texts for use in Document Types. The following localizations are used for the samples in this article: + +{% code title="doctype-en.js" lineNumbers="true" %} +```js +export default { + contentTypes: { + article: 'Article page', + 'article-desc': 'A textual, article-like page on the site. Use this as the main type of content.', + landing: 'Landing page', + 'landing-desc': 'An inviting, very graphical page. Use this as an entry point for a campaign, and supplement with Articles.' + }, + tabs: { + content: 'Page content', + seo: 'SEO configuration', + }, + groups: { + titles: 'Page titles' + }, + properties: { + title: 'Main title', + 'title-desc': 'This is the main title of the page.', + 'title-message': 'The main title is required for this page.', + subTitle: 'Sub title', + 'subTitle-desc': 'This is the sub title of the page.', + } +}; +``` +{% endcode %} + +{% hint style="info" %} +Umbraco must be restarted to register the localization manifest. Any subsequent localization text changes will need to be reloaded within the browser. +{% endhint %} + +## Applying localizations + +The localizations are applied by using the syntax `#{area alias}_{key alias}`. + +1. Create a **Document Type with template** called `#contentTypes_article` with **alias**: `articlePage`. +2. Under the newly created Document Type follow these steps: + +* Name the **description** to `#contentTypes_article-desc`. +* Create a new **tab** called `#tabs_content`. +* Add a new **group** called `#groups_titles`. +* Add a **property** called `#properties_title` with **alias** `title`. + * Set description to `{#properties_title-desc}`. + * Use a `TextString` editor. + * Enable to `Set this field as mandatory`. + * Under validation add `#properties_title-message`. + +{% hint style="info" %} +Property descriptions support [Umbraco Flavored Markdown](../../../reference/umbraco-flavored-markdown.md), which uses a different syntax (wrapped in brackets) to avoid conflicts with Markdown headers. +{% endhint %} + +![Applying localization to a property](../images/localization-document-type-editor-validation-v15.png) + +* Add a **property** called `#properties_subTitle` with **alias** `subTitle`. + * Set description to `{#properties_subTitle-desc}`. + * Use a `TextString` editor. +* Enable `Allow at root` in the **Structure** tab. + +![Applying localization to a Document Type](../images/localization-document-type-editor-v15.png) + +3. When creating and editing the content, you will see that the backoffice now uses the configured localizations. + +![Localized document creation dialog](../images/localization-document-editor-create.png) + +4. Create a new "Article" content: + +![Localized document editing](../images/localization-document-editor-v15.png) + +4. When trying to save the content without adding the mandatory content, you will see a warning as expected: + +![Localized property validation](../images/localization-document-editor-validation.png) diff --git a/16/umbraco-cms/fundamentals/data/dictionary-items.md b/16/umbraco-cms/fundamentals/data/dictionary-items.md new file mode 100644 index 00000000000..a0098c749eb --- /dev/null +++ b/16/umbraco-cms/fundamentals/data/dictionary-items.md @@ -0,0 +1,116 @@ +--- +description: Creating Dictionary Items in Umbraco +--- + +# Dictionary Items + +Depending on how your site is set up, not all content is edited through the **Content** section. There might be some text in your templates that needs translation. Using Dictionary Items, you can store a value for each language. Dictionary Items have a unique key that is used to fetch the value of the Dictionary Item. + +Dictionary Items can be managed from the **Translation** section. Let's take a look at an example. In this example, we will translate "Welcome to Umbraco" from within the template and add it to the dictionary: + +
+ +## Adding a Dictionary Item + +To add a Dictionary Item: + +1. Go to the **Translation** section. +2. Click on **Dictionary** in the **Translation** tree and select **Create**. +3. Enter the **Name** for the dictionary item. Let's say _Welcome_. +4. Enter the values for the different language versions. + +
+5. Click **Save**. + +### Grouping Dictionary Items + +To group dictionary items: + +1. Go to the **Translation** section. +2. Click on **Dictionary** in the **Translation** tree and select **Create**. +3. Enter the **Name** for the dictionary item. Let's say _Contact_. +4. Click **Create**. +5. Click on **Contact** and select **Create**. +6. Enter the **Name** of the item to be created under the **Contact** group. +7. Click **Create**. +8. Enter the values for the different language versions. + +
+9. Click **Save**. + +## Editing Dictionary Items + +To edit a dictionary item, follow these steps: + +1. Go to the **Translation** section. +2. Use the **Dictionary** tree to locate the item you need to update/edit. + * Alternatively, you can use the _search field_ in the top-right corner. +3. Make the edits you need to make. +4. Click **Save** to save the changes. + +{% hint style="info" %} +It will only be possible to edit the language(s) that the given user has access to. The value of the remaining languages will be _read-only_. + +Which language a user has access to is determined by the "Language permissions" set on the User Group. Learn more about this feature in the [Users](users/README.md#creating-a-user-group) article. +{% endhint %} + +## Fetching Dictionary Values in the Template + +To fetch dictionary values in the template, replace the text with the following snippet: + +```csharp +@Umbraco.GetDictionaryValue("Welcome") +``` + +![Rendering dictionary item](images/rendering-dictionary-item.png) + +Alternatively, you can specify an `altText` which will be returned if the dictionary value is empty. + +```csharp +@Umbraco.GetDictionaryValueOrDefault("Welcome", "Another amazing day in Umbraco") +``` + +![Rendering dictionary item](images/rendering-altvalue-dictionary-item.png) + +## Importing and exporting Dictionary Items + +In some cases, you might want to use the same Dictionary Items on multiple Umbraco websites. For this, you can use the export and import functionality to quickly copy the items from one website to another. + +### Exporting Dictionary Items + +1. Go to the **Translation** section in the Umbraco backoffice. +2. Locate the Dictionary Item (or group) you want to copy in the section tree. +3. Click **...** next to the Dictionary item (or group). +4. Select **Export...**. +5. Decide whether you want to also include descendants. +6. Click **Export**. + +This will download a `.udt` file which you can use to import the Dictionary items on another Umbraco website. + +![Options menu with the Export feature](images/export.png) + +### Importing Dictionary Items + +1. Go to the **Translation** section in the Umbraco backoffice. +2. Click **...** next to the **Dictionary** tree. +3. Select **Import...**. +4. Click on **Import**. +5. Find and select the `.udt` file containing the Dictionary Items. +6. Click **Open** in the file browser. +7. Review the Dictionary Items for import. +8. Choose where to import the items. +9. Click on **Import**. + +The Dictionary Items have now been added to your website. + +![Review the Dictionary Items for import before confirming](images/import.png) + +## Using Dictionary Item in a Multilingual website + +To use Dictionary Items in a multilingual website, see the [Creating a Multilingual Site](../../tutorials/multilanguage-setup.md) article. + +## Related Links + +* [API reference for the DictionaryItem](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Models.DictionaryItem.html) +* [Localization Service](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Services.ILocalizationService.html) +* [Creating a Multilingual Site](../../tutorials/multilanguage-setup.md) diff --git a/16/umbraco-cms/fundamentals/data/images/Add-tab.png b/16/umbraco-cms/fundamentals/data/images/Add-tab.png new file mode 100644 index 00000000000..778cf1f3ea1 Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/images/Add-tab.png differ diff --git a/16/umbraco-cms/fundamentals/data/images/Add-tab.png.png b/16/umbraco-cms/fundamentals/data/images/Add-tab.png.png new file mode 100644 index 00000000000..73e5077434e Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/images/Add-tab.png.png differ diff --git a/16/umbraco-cms/fundamentals/data/images/Composition-add-tab.gif b/16/umbraco-cms/fundamentals/data/images/Composition-add-tab.gif new file mode 100644 index 00000000000..e37c0a3fee9 Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/images/Composition-add-tab.gif differ diff --git a/16/umbraco-cms/fundamentals/data/images/Composition-hide-unavailable-options.PNG b/16/umbraco-cms/fundamentals/data/images/Composition-hide-unavailable-options.PNG new file mode 100644 index 00000000000..a1e0cb68f00 Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/images/Composition-hide-unavailable-options.PNG differ diff --git a/16/umbraco-cms/fundamentals/data/images/Create-dictionary-item.png b/16/umbraco-cms/fundamentals/data/images/Create-dictionary-item.png new file mode 100644 index 00000000000..487c8cce9d3 Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/images/Create-dictionary-item.png differ diff --git a/16/umbraco-cms/fundamentals/data/images/CreateDoctype.png b/16/umbraco-cms/fundamentals/data/images/CreateDoctype.png new file mode 100644 index 00000000000..2ff143255d4 Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/images/CreateDoctype.png differ diff --git a/16/umbraco-cms/fundamentals/data/images/Doc-Type-Composition-Create.png b/16/umbraco-cms/fundamentals/data/images/Doc-Type-Composition-Create.png new file mode 100644 index 00000000000..01a033a8322 Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/images/Doc-Type-Composition-Create.png differ diff --git a/16/umbraco-cms/fundamentals/data/images/Document-Type-Adding-Properties.jpg b/16/umbraco-cms/fundamentals/data/images/Document-Type-Adding-Properties.jpg new file mode 100644 index 00000000000..16877952fce Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/images/Document-Type-Adding-Properties.jpg differ diff --git a/16/umbraco-cms/fundamentals/data/images/Document-Type-Adding-Properties.png b/16/umbraco-cms/fundamentals/data/images/Document-Type-Adding-Properties.png new file mode 100644 index 00000000000..e0dd28eea5d Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/images/Document-Type-Adding-Properties.png differ diff --git a/16/umbraco-cms/fundamentals/data/images/Document-Type-Allow-At-Root.jpg b/16/umbraco-cms/fundamentals/data/images/Document-Type-Allow-At-Root.jpg new file mode 100644 index 00000000000..05eab4013e4 Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/images/Document-Type-Allow-At-Root.jpg differ diff --git a/16/umbraco-cms/fundamentals/data/images/Document-Type-Allow-At-Root.png b/16/umbraco-cms/fundamentals/data/images/Document-Type-Allow-At-Root.png new file mode 100644 index 00000000000..681f8abb02d Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/images/Document-Type-Allow-At-Root.png differ diff --git a/16/umbraco-cms/fundamentals/data/images/Document-Type-Allow-Child-Node.jpg b/16/umbraco-cms/fundamentals/data/images/Document-Type-Allow-Child-Node.jpg new file mode 100644 index 00000000000..379bd460cbf Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/images/Document-Type-Allow-Child-Node.jpg differ diff --git a/16/umbraco-cms/fundamentals/data/images/Document-Type-Allow-Child-Node.png b/16/umbraco-cms/fundamentals/data/images/Document-Type-Allow-Child-Node.png new file mode 100644 index 00000000000..5cf0eab4019 Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/images/Document-Type-Allow-Child-Node.png differ diff --git a/16/umbraco-cms/fundamentals/data/images/Document-Type-Child-Node-Created.jpg b/16/umbraco-cms/fundamentals/data/images/Document-Type-Child-Node-Created.jpg new file mode 100644 index 00000000000..d4228f79d86 Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/images/Document-Type-Child-Node-Created.jpg differ diff --git a/16/umbraco-cms/fundamentals/data/images/Document-Type-Child-Node-Created.png b/16/umbraco-cms/fundamentals/data/images/Document-Type-Child-Node-Created.png new file mode 100644 index 00000000000..1cd102415c7 Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/images/Document-Type-Child-Node-Created.png differ diff --git a/16/umbraco-cms/fundamentals/data/images/Document-Type-Choosing-Icon.jpg b/16/umbraco-cms/fundamentals/data/images/Document-Type-Choosing-Icon.jpg new file mode 100644 index 00000000000..cbd80fd877c Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/images/Document-Type-Choosing-Icon.jpg differ diff --git a/16/umbraco-cms/fundamentals/data/images/Document-Type-Choosing-Icon.png b/16/umbraco-cms/fundamentals/data/images/Document-Type-Choosing-Icon.png new file mode 100644 index 00000000000..78a3a88a926 Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/images/Document-Type-Choosing-Icon.png differ diff --git a/16/umbraco-cms/fundamentals/data/images/Document-Type-Create-Tab.jpg b/16/umbraco-cms/fundamentals/data/images/Document-Type-Create-Tab.jpg new file mode 100644 index 00000000000..5eeab314605 Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/images/Document-Type-Create-Tab.jpg differ diff --git a/16/umbraco-cms/fundamentals/data/images/Document-Type-Create-Tab.png b/16/umbraco-cms/fundamentals/data/images/Document-Type-Create-Tab.png new file mode 100644 index 00000000000..189c215b561 Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/images/Document-Type-Create-Tab.png differ diff --git a/16/umbraco-cms/fundamentals/data/images/Document-Type-Create.jpg b/16/umbraco-cms/fundamentals/data/images/Document-Type-Create.jpg new file mode 100644 index 00000000000..8ca1e52161d Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/images/Document-Type-Create.jpg differ diff --git a/16/umbraco-cms/fundamentals/data/images/Document-Type-Create.png b/16/umbraco-cms/fundamentals/data/images/Document-Type-Create.png new file mode 100644 index 00000000000..bf04c748479 Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/images/Document-Type-Create.png differ diff --git a/16/umbraco-cms/fundamentals/data/images/Document-Type-Keyboard-Shortcuts.jpg b/16/umbraco-cms/fundamentals/data/images/Document-Type-Keyboard-Shortcuts.jpg new file mode 100644 index 00000000000..f02269a61d7 Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/images/Document-Type-Keyboard-Shortcuts.jpg differ diff --git a/16/umbraco-cms/fundamentals/data/images/Document-Type-Name.jpg b/16/umbraco-cms/fundamentals/data/images/Document-Type-Name.jpg new file mode 100644 index 00000000000..3c97dcee728 Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/images/Document-Type-Name.jpg differ diff --git a/16/umbraco-cms/fundamentals/data/images/Document-Type-Rich-Text-Property.jpg b/16/umbraco-cms/fundamentals/data/images/Document-Type-Rich-Text-Property.jpg new file mode 100644 index 00000000000..a7df64bc69f Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/images/Document-Type-Rich-Text-Property.jpg differ diff --git a/16/umbraco-cms/fundamentals/data/images/Document-Type-Root-Node-Created.jpg b/16/umbraco-cms/fundamentals/data/images/Document-Type-Root-Node-Created.jpg new file mode 100644 index 00000000000..0132dcbf489 Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/images/Document-Type-Root-Node-Created.jpg differ diff --git a/16/umbraco-cms/fundamentals/data/images/Document-Type-Root-Node-Created.png b/16/umbraco-cms/fundamentals/data/images/Document-Type-Root-Node-Created.png new file mode 100644 index 00000000000..1ce4b43996c Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/images/Document-Type-Root-Node-Created.png differ diff --git a/16/umbraco-cms/fundamentals/data/images/Element-Type.png b/16/umbraco-cms/fundamentals/data/images/Element-Type.png new file mode 100644 index 00000000000..e568ea61e17 Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/images/Element-Type.png differ diff --git a/16/umbraco-cms/fundamentals/data/images/Generic-tab.png b/16/umbraco-cms/fundamentals/data/images/Generic-tab.png new file mode 100644 index 00000000000..8f0056f27c5 Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/images/Generic-tab.png differ diff --git a/16/umbraco-cms/fundamentals/data/images/Member-Groups-Assign.jpg b/16/umbraco-cms/fundamentals/data/images/Member-Groups-Assign.jpg new file mode 100644 index 00000000000..7b8256d2b9c Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/images/Member-Groups-Assign.jpg differ diff --git a/16/umbraco-cms/fundamentals/data/images/Member-Groups-Create.jpg b/16/umbraco-cms/fundamentals/data/images/Member-Groups-Create.jpg new file mode 100644 index 00000000000..46ae9ab3f19 Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/images/Member-Groups-Create.jpg differ diff --git a/16/umbraco-cms/fundamentals/data/images/Member-Type-Editor.png b/16/umbraco-cms/fundamentals/data/images/Member-Type-Editor.png new file mode 100644 index 00000000000..720997b7c7b Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/images/Member-Type-Editor.png differ diff --git a/16/umbraco-cms/fundamentals/data/images/Member-Type-Editor_new.png b/16/umbraco-cms/fundamentals/data/images/Member-Type-Editor_new.png new file mode 100644 index 00000000000..023273e0715 Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/images/Member-Type-Editor_new.png differ diff --git a/16/umbraco-cms/fundamentals/data/images/Member-Type-Editor_new1.PNG b/16/umbraco-cms/fundamentals/data/images/Member-Type-Editor_new1.PNG new file mode 100644 index 00000000000..a255cb732fc Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/images/Member-Type-Editor_new1.PNG differ diff --git a/16/umbraco-cms/fundamentals/data/images/Member-group.png b/16/umbraco-cms/fundamentals/data/images/Member-group.png new file mode 100644 index 00000000000..4605e90f13f Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/images/Member-group.png differ diff --git a/16/umbraco-cms/fundamentals/data/images/Member-group1.PNG b/16/umbraco-cms/fundamentals/data/images/Member-group1.PNG new file mode 100644 index 00000000000..f52e963f14a Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/images/Member-group1.PNG differ diff --git a/16/umbraco-cms/fundamentals/data/images/Members-Generic-Properties.jpg b/16/umbraco-cms/fundamentals/data/images/Members-Generic-Properties.jpg new file mode 100644 index 00000000000..df6a94a37ac Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/images/Members-Generic-Properties.jpg differ diff --git a/16/umbraco-cms/fundamentals/data/images/Members-Info.jpg b/16/umbraco-cms/fundamentals/data/images/Members-Info.jpg new file mode 100644 index 00000000000..e1ca5323694 Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/images/Members-Info.jpg differ diff --git a/16/umbraco-cms/fundamentals/data/images/Members-Tabs.jpg b/16/umbraco-cms/fundamentals/data/images/Members-Tabs.jpg new file mode 100644 index 00000000000..f9d254a230d Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/images/Members-Tabs.jpg differ diff --git a/16/umbraco-cms/fundamentals/data/images/Publish-Timezone-Difference.jpg b/16/umbraco-cms/fundamentals/data/images/Publish-Timezone-Difference.jpg new file mode 100644 index 00000000000..49bac0b0d90 Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/images/Publish-Timezone-Difference.jpg differ diff --git a/16/umbraco-cms/fundamentals/data/images/Relations-in-the-backoffice.png b/16/umbraco-cms/fundamentals/data/images/Relations-in-the-backoffice.png new file mode 100644 index 00000000000..2a55e85f9b0 Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/images/Relations-in-the-backoffice.png differ diff --git a/16/umbraco-cms/fundamentals/data/images/Reorder-tabs.gif b/16/umbraco-cms/fundamentals/data/images/Reorder-tabs.gif new file mode 100644 index 00000000000..f72d6585209 Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/images/Reorder-tabs.gif differ diff --git a/16/umbraco-cms/fundamentals/data/images/Scheduled-publishing.PNG b/16/umbraco-cms/fundamentals/data/images/Scheduled-publishing.PNG new file mode 100644 index 00000000000..2d459b614d5 Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/images/Scheduled-publishing.PNG differ diff --git a/16/umbraco-cms/fundamentals/data/images/User-Permissions.png b/16/umbraco-cms/fundamentals/data/images/User-Permissions.png new file mode 100644 index 00000000000..80b66e7080c Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/images/User-Permissions.png differ diff --git a/16/umbraco-cms/fundamentals/data/images/User-Type-Info.png b/16/umbraco-cms/fundamentals/data/images/User-Type-Info.png new file mode 100644 index 00000000000..44f415daa9a Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/images/User-Type-Info.png differ diff --git a/16/umbraco-cms/fundamentals/data/images/addproperty.png b/16/umbraco-cms/fundamentals/data/images/addproperty.png new file mode 100644 index 00000000000..0fa9fd96537 Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/images/addproperty.png differ diff --git a/16/umbraco-cms/fundamentals/data/images/api-user.png b/16/umbraco-cms/fundamentals/data/images/api-user.png new file mode 100644 index 00000000000..7f4be5d1379 Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/images/api-user.png differ diff --git a/16/umbraco-cms/fundamentals/data/images/assign-member-group.png b/16/umbraco-cms/fundamentals/data/images/assign-member-group.png new file mode 100644 index 00000000000..81ee0a8f6c3 Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/images/assign-member-group.png differ diff --git a/16/umbraco-cms/fundamentals/data/images/assign-member-group1.PNG b/16/umbraco-cms/fundamentals/data/images/assign-member-group1.PNG new file mode 100644 index 00000000000..f7341617c77 Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/images/assign-member-group1.PNG differ diff --git a/16/umbraco-cms/fundamentals/data/images/composition.png b/16/umbraco-cms/fundamentals/data/images/composition.png new file mode 100644 index 00000000000..9f82add0c8a Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/images/composition.png differ diff --git a/16/umbraco-cms/fundamentals/data/images/create-relation-type.png b/16/umbraco-cms/fundamentals/data/images/create-relation-type.png new file mode 100644 index 00000000000..c41346ce89c Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/images/create-relation-type.png differ diff --git a/16/umbraco-cms/fundamentals/data/images/create-user-group.png b/16/umbraco-cms/fundamentals/data/images/create-user-group.png new file mode 100644 index 00000000000..67e2004a8b6 Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/images/create-user-group.png differ diff --git a/16/umbraco-cms/fundamentals/data/images/createGroup_new.png b/16/umbraco-cms/fundamentals/data/images/createGroup_new.png new file mode 100644 index 00000000000..25b40cc09d7 Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/images/createGroup_new.png differ diff --git a/16/umbraco-cms/fundamentals/data/images/createHomepage.png b/16/umbraco-cms/fundamentals/data/images/createHomepage.png new file mode 100644 index 00000000000..d02e29e9be1 Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/images/createHomepage.png differ diff --git a/16/umbraco-cms/fundamentals/data/images/default-permissions.png b/16/umbraco-cms/fundamentals/data/images/default-permissions.png new file mode 100644 index 00000000000..4caefbc30b9 Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/images/default-permissions.png differ diff --git a/16/umbraco-cms/fundamentals/data/images/default-relation-types.png b/16/umbraco-cms/fundamentals/data/images/default-relation-types.png new file mode 100644 index 00000000000..d8e4fc4628f Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/images/default-relation-types.png differ diff --git a/16/umbraco-cms/fundamentals/data/images/dictionary-item-values.png b/16/umbraco-cms/fundamentals/data/images/dictionary-item-values.png new file mode 100644 index 00000000000..bea50d97f4e Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/images/dictionary-item-values.png differ diff --git a/16/umbraco-cms/fundamentals/data/images/dictionary-item.png b/16/umbraco-cms/fundamentals/data/images/dictionary-item.png new file mode 100644 index 00000000000..b69dbefae0e Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/images/dictionary-item.png differ diff --git a/16/umbraco-cms/fundamentals/data/images/display-dictionary-item.png b/16/umbraco-cms/fundamentals/data/images/display-dictionary-item.png new file mode 100644 index 00000000000..d9e5f2f259f Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/images/display-dictionary-item.png differ diff --git a/16/umbraco-cms/fundamentals/data/images/docTypeIcon.png b/16/umbraco-cms/fundamentals/data/images/docTypeIcon.png new file mode 100644 index 00000000000..9383f25f90e Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/images/docTypeIcon.png differ diff --git a/16/umbraco-cms/fundamentals/data/images/docTypePermissions.png b/16/umbraco-cms/fundamentals/data/images/docTypePermissions.png new file mode 100644 index 00000000000..03fcaca6107 Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/images/docTypePermissions.png differ diff --git a/16/umbraco-cms/fundamentals/data/images/export.png b/16/umbraco-cms/fundamentals/data/images/export.png new file mode 100644 index 00000000000..c9c209d1ce5 Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/images/export.png differ diff --git a/16/umbraco-cms/fundamentals/data/images/homePage.png b/16/umbraco-cms/fundamentals/data/images/homePage.png new file mode 100644 index 00000000000..7c3e05f31d5 Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/images/homePage.png differ diff --git a/16/umbraco-cms/fundamentals/data/images/import-document-type.png b/16/umbraco-cms/fundamentals/data/images/import-document-type.png new file mode 100644 index 00000000000..3450fc8287d Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/images/import-document-type.png differ diff --git a/16/umbraco-cms/fundamentals/data/images/import.png b/16/umbraco-cms/fundamentals/data/images/import.png new file mode 100644 index 00000000000..97c5508a8f8 Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/images/import.png differ diff --git a/16/umbraco-cms/fundamentals/data/images/localization-document-editor-create.png b/16/umbraco-cms/fundamentals/data/images/localization-document-editor-create.png new file mode 100644 index 00000000000..58b02eb6b1c Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/images/localization-document-editor-create.png differ diff --git a/16/umbraco-cms/fundamentals/data/images/localization-document-editor-v15.png b/16/umbraco-cms/fundamentals/data/images/localization-document-editor-v15.png new file mode 100644 index 00000000000..47ed19ed881 Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/images/localization-document-editor-v15.png differ diff --git a/16/umbraco-cms/fundamentals/data/images/localization-document-editor-validation.png b/16/umbraco-cms/fundamentals/data/images/localization-document-editor-validation.png new file mode 100644 index 00000000000..a81ce080608 Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/images/localization-document-editor-validation.png differ diff --git a/16/umbraco-cms/fundamentals/data/images/localization-document-editor.png b/16/umbraco-cms/fundamentals/data/images/localization-document-editor.png new file mode 100644 index 00000000000..c47f8f8bc71 Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/images/localization-document-editor.png differ diff --git a/16/umbraco-cms/fundamentals/data/images/localization-document-type-editor-v15.png b/16/umbraco-cms/fundamentals/data/images/localization-document-type-editor-v15.png new file mode 100644 index 00000000000..9f3533c9eab Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/images/localization-document-type-editor-v15.png differ diff --git a/16/umbraco-cms/fundamentals/data/images/localization-document-type-editor-validation-v15.png b/16/umbraco-cms/fundamentals/data/images/localization-document-type-editor-validation-v15.png new file mode 100644 index 00000000000..c14a6bb5ca3 Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/images/localization-document-type-editor-validation-v15.png differ diff --git a/16/umbraco-cms/fundamentals/data/images/localization-document-type-editor-validation.png b/16/umbraco-cms/fundamentals/data/images/localization-document-type-editor-validation.png new file mode 100644 index 00000000000..4ce31a3eea8 Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/images/localization-document-type-editor-validation.png differ diff --git a/16/umbraco-cms/fundamentals/data/images/localization-document-type-editor.png b/16/umbraco-cms/fundamentals/data/images/localization-document-type-editor.png new file mode 100644 index 00000000000..2b70a040459 Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/images/localization-document-type-editor.png differ diff --git a/16/umbraco-cms/fundamentals/data/images/md-description.gif b/16/umbraco-cms/fundamentals/data/images/md-description.gif new file mode 100644 index 00000000000..0c5f23159df Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/images/md-description.gif differ diff --git a/16/umbraco-cms/fundamentals/data/images/member-images.png b/16/umbraco-cms/fundamentals/data/images/member-images.png new file mode 100644 index 00000000000..0ebfc085e8e Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/images/member-images.png differ diff --git a/16/umbraco-cms/fundamentals/data/images/member-type-composition-setting.PNG b/16/umbraco-cms/fundamentals/data/images/member-type-composition-setting.PNG new file mode 100644 index 00000000000..c5f7e34b07d Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/images/member-type-composition-setting.PNG differ diff --git a/16/umbraco-cms/fundamentals/data/images/member-type-composition.PNG b/16/umbraco-cms/fundamentals/data/images/member-type-composition.PNG new file mode 100644 index 00000000000..ed681f1bc84 Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/images/member-type-composition.PNG differ diff --git a/16/umbraco-cms/fundamentals/data/images/member-type-property-settings.png b/16/umbraco-cms/fundamentals/data/images/member-type-property-settings.png new file mode 100644 index 00000000000..13c489b587d Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/images/member-type-property-settings.png differ diff --git a/16/umbraco-cms/fundamentals/data/images/member-type-property-settings_new.png b/16/umbraco-cms/fundamentals/data/images/member-type-property-settings_new.png new file mode 100644 index 00000000000..7be01dded3e Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/images/member-type-property-settings_new.png differ diff --git a/16/umbraco-cms/fundamentals/data/images/member-type-property-settings_new1.PNG b/16/umbraco-cms/fundamentals/data/images/member-type-property-settings_new1.PNG new file mode 100644 index 00000000000..268d377ea3c Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/images/member-type-property-settings_new1.PNG differ diff --git a/16/umbraco-cms/fundamentals/data/images/parent-siblings-children.png b/16/umbraco-cms/fundamentals/data/images/parent-siblings-children.png new file mode 100644 index 00000000000..1ace04fdb61 Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/images/parent-siblings-children.png differ diff --git a/16/umbraco-cms/fundamentals/data/images/per-doctype-override.png b/16/umbraco-cms/fundamentals/data/images/per-doctype-override.png new file mode 100644 index 00000000000..33d9c68ad6b Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/images/per-doctype-override.png differ diff --git a/16/umbraco-cms/fundamentals/data/images/prevent-cleanup-part-1.png b/16/umbraco-cms/fundamentals/data/images/prevent-cleanup-part-1.png new file mode 100644 index 00000000000..e50e559391b Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/images/prevent-cleanup-part-1.png differ diff --git a/16/umbraco-cms/fundamentals/data/images/prevent-cleanup-part-2.png b/16/umbraco-cms/fundamentals/data/images/prevent-cleanup-part-2.png new file mode 100644 index 00000000000..55051c7c9ac Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/images/prevent-cleanup-part-2.png differ diff --git a/16/umbraco-cms/fundamentals/data/images/relation-alias.png b/16/umbraco-cms/fundamentals/data/images/relation-alias.png new file mode 100644 index 00000000000..adb3724f63e Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/images/relation-alias.png differ diff --git a/16/umbraco-cms/fundamentals/data/images/relation-types-tree.png b/16/umbraco-cms/fundamentals/data/images/relation-types-tree.png new file mode 100644 index 00000000000..99f780d3cb6 Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/images/relation-types-tree.png differ diff --git a/16/umbraco-cms/fundamentals/data/images/rendering-altvalue-dictionary-item.png b/16/umbraco-cms/fundamentals/data/images/rendering-altvalue-dictionary-item.png new file mode 100644 index 00000000000..e43fc16234c Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/images/rendering-altvalue-dictionary-item.png differ diff --git a/16/umbraco-cms/fundamentals/data/images/rendering-dictionary-item.png b/16/umbraco-cms/fundamentals/data/images/rendering-dictionary-item.png new file mode 100644 index 00000000000..0e03646e5a2 Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/images/rendering-dictionary-item.png differ diff --git a/16/umbraco-cms/fundamentals/data/images/schedule.png b/16/umbraco-cms/fundamentals/data/images/schedule.png new file mode 100644 index 00000000000..0ecf9270a25 Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/images/schedule.png differ diff --git a/16/umbraco-cms/fundamentals/data/images/scheduled-publishing-8.png b/16/umbraco-cms/fundamentals/data/images/scheduled-publishing-8.png new file mode 100644 index 00000000000..d27a20c238f Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/images/scheduled-publishing-8.png differ diff --git a/16/umbraco-cms/fundamentals/data/images/selectEditor.png b/16/umbraco-cms/fundamentals/data/images/selectEditor.png new file mode 100644 index 00000000000..92e375529fc Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/images/selectEditor.png differ diff --git a/16/umbraco-cms/fundamentals/data/images/user-groups.png b/16/umbraco-cms/fundamentals/data/images/user-groups.png new file mode 100644 index 00000000000..0bee87e6a5e Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/images/user-groups.png differ diff --git a/16/umbraco-cms/fundamentals/data/images/v8Screenshots/addproperty.PNG b/16/umbraco-cms/fundamentals/data/images/v8Screenshots/addproperty.PNG new file mode 100644 index 00000000000..e1bea9ab123 Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/images/v8Screenshots/addproperty.PNG differ diff --git a/16/umbraco-cms/fundamentals/data/images/v8Screenshots/addproperty_new.png b/16/umbraco-cms/fundamentals/data/images/v8Screenshots/addproperty_new.png new file mode 100644 index 00000000000..90b6811430f Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/images/v8Screenshots/addproperty_new.png differ diff --git a/16/umbraco-cms/fundamentals/data/images/v8Screenshots/compositions.PNG b/16/umbraco-cms/fundamentals/data/images/v8Screenshots/compositions.PNG new file mode 100644 index 00000000000..f7659939395 Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/images/v8Screenshots/compositions.PNG differ diff --git a/16/umbraco-cms/fundamentals/data/images/v8Screenshots/createAboutUs.PNG b/16/umbraco-cms/fundamentals/data/images/v8Screenshots/createAboutUs.PNG new file mode 100644 index 00000000000..1a3dcaa872b Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/images/v8Screenshots/createAboutUs.PNG differ diff --git a/16/umbraco-cms/fundamentals/data/images/v8Screenshots/createDoctype.PNG b/16/umbraco-cms/fundamentals/data/images/v8Screenshots/createDoctype.PNG new file mode 100644 index 00000000000..38a1fc35656 Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/images/v8Screenshots/createDoctype.PNG differ diff --git a/16/umbraco-cms/fundamentals/data/images/v8Screenshots/createGroup.PNG b/16/umbraco-cms/fundamentals/data/images/v8Screenshots/createGroup.PNG new file mode 100644 index 00000000000..0296832fb4c Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/images/v8Screenshots/createGroup.PNG differ diff --git a/16/umbraco-cms/fundamentals/data/images/v8Screenshots/createGroup_new.png b/16/umbraco-cms/fundamentals/data/images/v8Screenshots/createGroup_new.png new file mode 100644 index 00000000000..d472b03643a Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/images/v8Screenshots/createGroup_new.png differ diff --git a/16/umbraco-cms/fundamentals/data/images/v8Screenshots/createHomepage.PNG b/16/umbraco-cms/fundamentals/data/images/v8Screenshots/createHomepage.PNG new file mode 100644 index 00000000000..f81f9f46f2b Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/images/v8Screenshots/createHomepage.PNG differ diff --git a/16/umbraco-cms/fundamentals/data/images/v8Screenshots/docTypeIcon.PNG b/16/umbraco-cms/fundamentals/data/images/v8Screenshots/docTypeIcon.PNG new file mode 100644 index 00000000000..8e69be56ee0 Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/images/v8Screenshots/docTypeIcon.PNG differ diff --git a/16/umbraco-cms/fundamentals/data/images/v8Screenshots/docTypePermissions.PNG b/16/umbraco-cms/fundamentals/data/images/v8Screenshots/docTypePermissions.PNG new file mode 100644 index 00000000000..f7db0099d66 Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/images/v8Screenshots/docTypePermissions.PNG differ diff --git a/16/umbraco-cms/fundamentals/data/images/v8Screenshots/export-document-type.png b/16/umbraco-cms/fundamentals/data/images/v8Screenshots/export-document-type.png new file mode 100644 index 00000000000..6aab567e14f Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/images/v8Screenshots/export-document-type.png differ diff --git a/16/umbraco-cms/fundamentals/data/images/v8Screenshots/homePage.PNG b/16/umbraco-cms/fundamentals/data/images/v8Screenshots/homePage.PNG new file mode 100644 index 00000000000..09f09c4a42a Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/images/v8Screenshots/homePage.PNG differ diff --git a/16/umbraco-cms/fundamentals/data/images/v8Screenshots/selectEditor.PNG b/16/umbraco-cms/fundamentals/data/images/v8Screenshots/selectEditor.PNG new file mode 100644 index 00000000000..8243f86002a Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/images/v8Screenshots/selectEditor.PNG differ diff --git a/16/umbraco-cms/fundamentals/data/images/v8Screenshots/selectEditor_new.png b/16/umbraco-cms/fundamentals/data/images/v8Screenshots/selectEditor_new.png new file mode 100644 index 00000000000..e9535792256 Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/images/v8Screenshots/selectEditor_new.png differ diff --git a/16/umbraco-cms/fundamentals/data/images/v8Screenshots/setPagePermissions.PNG b/16/umbraco-cms/fundamentals/data/images/v8Screenshots/setPagePermissions.PNG new file mode 100644 index 00000000000..6468e326822 Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/images/v8Screenshots/setPagePermissions.PNG differ diff --git a/16/umbraco-cms/fundamentals/data/images/view-relations.png b/16/umbraco-cms/fundamentals/data/images/view-relations.png new file mode 100644 index 00000000000..5dcd9ce63a0 Binary files /dev/null and b/16/umbraco-cms/fundamentals/data/images/view-relations.png differ diff --git a/16/umbraco-cms/fundamentals/data/members.md b/16/umbraco-cms/fundamentals/data/members.md new file mode 100644 index 00000000000..be68820eda1 --- /dev/null +++ b/16/umbraco-cms/fundamentals/data/members.md @@ -0,0 +1,119 @@ +--- +description: >- + Members are used for registering and authentication external / frontend users + of an Umbraco installation. This could be Forum members and Intranet members. +--- + +# Members + +Members are used for registering and authenticating external users of an Umbraco installation (ie. forum members, intranet users and so forth). + +This guide will explain how to define and create members in the backoffice. If you want to work with members using the service APIs, links can be found at the end of the document. + +There is a default Member Type that can be used to create members. You can customize this to fit your needs or create your own Member Type from scratch. + +## Creating a Member + +Go to the **Members** section and click **Create**. + +Members have a number of mandatory properties that need to be filled in before a member can be saved. Some of the properties are **Username**, **Email**, two **Password fields** and so on. + +There are also a number of default properties which are stored in the database in the tables `Member` and`TwoFactorLogin`: + +* `umbracoMemberFailedPasswordAttempts` +* `umbracoMemberApproved` +* `umbracoMemberLockedOut` +* `umbracoTwoFactorLogin` +* `umbracoMemberLastLockoutDate` +* `umbracoMemberLastLogin` +* `umbracoMemberLastPasswordChangeDate` + +Once the Member is created and saved you can access it by expanding the Members tree and clicking **All Members** to get a collection. You can also view members of a specific type by selecting the member type in the Members tree. + +## [Sensitive data](../../reference/security/sensitive-data-on-members.md) + +Sensitive properties on a members data will not be displayed to backoffice users unless they have appropriate permissions. In order to see the values of the default properties in the **Member** tab you need to have the Sensitive data User Group. By having this group added to a user they will also have the option to mark member type properties as sensitive. + +More information can be found under [security](../../reference/security/sensitive-data-on-members.md). + +## Creating a Member Type + +You can create your own Member Types and add tabs, groups and properties as you would with Document Types. + +Go to the **Settings** section, click **...** next to **Member Types** and select **Create**. You will now be taken to the Member Type editor that is used to define and edit the Member Type. Name the new Member Type and click **Save**. + +![Member Type Editor](images/member-type-composition.PNG) + +Once created, the Member Type will have no properties, so you have the freedom to add your own properties or compositions. + +### Assigning a Member Composition + +When creating a Member Type you can assign compositions. Compositions allow you to inherit tabs and properties from existing member types instead of creating them from scratch. + +For example on the member type that you have created, click on **Composition**. Then you can choose the existing **Member** type which then you will inherit its tabs, groups, and properties. + +

Composition Member Type

+ +The default **Member** type has a **Membership** group which includes `umbracoMemberComments` property along with the other default properties. The other properties can be seen only in the **Member** tab when creating a member. + +It is possible to add more groups and more properties to each of the Member Types you create, as well as the default Member Type. + +## Creating Member Groups + +Member Groups define roles for your members that can be used for role-based protection. A member can be in multiple groups. + +![Creating a Member Group](images/Member-group1.PNG) + +To create a new Member Group click the menu icon next to the **Member Groups** node in the Members section. Choose **Create**, name the group, and save the group. + +### Assigning a Member Group + +To assign a member to a specific group find the member you wish to assign and find the **Properties** group. Here you can see which groups the member is already part of. You can also add the member to more groups or remove the member from already assigned groups: + +![Assigning a Member Group](images/assign-member-group1.PNG) + +## Technical + +As a developer you are able to leverage your website when you build on the Members section of Umbraco. The member's section is by default in the Umbraco backoffice, but you can still use it to implement some work on your front end. Members are created using ASP.NET Core Identity, so there are some provider settings that can be set in appsettings.json - here are the defaults: + +```json +{ + "$schema": "appsettings-schema.json", + "Umbraco": { + "CMS": { + "Security": { + "AllowPasswordReset": true, + "AuthCookieDomain": "(No default, but takes a string)", + "AuthCookieName": "UMB_UCONTEXT", + "KeepUserLoggedIn": false, + "UsernameIsEmail": true, + "HideDisabledUsersInBackoffice": false, + "AllowedUserNameCharacters": "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._@+\\", + "MemberPassword": { + "RequiredLength": 10, + "RequireNonLetterOrDigit": false, + "RequireDigit": false, + "RequireLowercase": false, + "RequireUppercase": false, + "MaxFailedAccessAttemptsBeforeLockout": 5, + "HashAlgorithmType": "HMACSHA256" + } + } + } + } +} +``` + +You can find out more about the services methods in the reference section of the documentation by following the links below. + +## References + +* Video: [Adding a member](https://www.youtube.com/watch?v=gdvfrqQcAGY) +* Video: [Member Type Properties](https://www.youtube.com/watch?v=E_es3x_H5oU) +* Video: [Role-Based Protection](https://www.youtube.com/watch?v=wVR9OBnaNZQ) + +### Related Services + +* [MemberService](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Services.MemberService.html) +* [MemberType Service](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Services.MemberTypeService.html) +* [MemberGroup Service](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Services.IMemberGroupService.html) diff --git a/16/umbraco-cms/fundamentals/data/relations.md b/16/umbraco-cms/fundamentals/data/relations.md new file mode 100644 index 00000000000..1c09107870e --- /dev/null +++ b/16/umbraco-cms/fundamentals/data/relations.md @@ -0,0 +1,67 @@ +--- +description: Learn about relations and how to create and manage them. +--- + +# Relations + +Umbraco sections are built around the concept of 'trees' and there is an implicit relationship between items in a section tree. + +![Parent, Siblings & Children](../../../../10/umbraco-cms/fundamentals/data/images/parent-siblings-children.png) + +We refer to these relationships in the manner of a 'Family Tree'. One content item might be the 'Parent' of some content items, and those items would be referred to as the 'Children' of that parent. Items within the same branch of the tree can also be described as 'Ancestors' or 'Descendants' of an item. + +There are methods available to support querying content items by their relative position to the current page. This is possible using the following concepts: `Model.Ancestors()`, `Model.Children()`, or `Model.Descendants()`. + +In some cases there are no direct relationships between two items in a tree, but they are still somehow 'related'. This could be the alternate language translation pages of a content page. + +In other cases there is a 'relation' between different types of entities. This could be a relation between Content and Member, or Member and MediaFolder. You might need to be able to retrieve and display the uploaded images from a specific logged-in Member. + +These are the scenarios where the concept of **Umbraco Relations** provides a solution. + +## The Concept of Umbraco Relations + +Umbraco Relations allow you to relate almost any object in Umbraco to almost any other Umbraco object. This is done by defining a new _Relation Type_. + +### How is this different to pickers? + +With a Content, Member, or Media picker the relationship only works as a 1-way street. The content item knows it has 'picked' another content item but that other content item does not know where it has been picked. + +Umbraco Relations works as a 2-way street. When creating a relation between two different types of entities, it will be possible to find one entity from the other and vice versa. As an example this provides the option to list out all the pages that a content banner had been picked on. + +## Relation Types + +A Relation Type specifies how two types of entities are related. Two items might be related under multiple Relation Types, and you might only be interested in your 'Related Language Page' Relation Type. + +## Viewing Relations + +It is possible to view the existing Relation Types from the Umbraco backoffice: + +1. Access the Umbraco Backoffice. +2. Navigate to the **Settings** section. +3. Locate the **Advanced** group in the sidebar. +4. Select **Relations**. + +![View Relations](images/Relations-in-the-backoffice.png) + +On the dashboard all defined relations will be listed. Select a Relation to view a list of all the objects that have been related for that specific Relation Type. + +## Creating Relations + +You can create Relations using the RelationService API via code. + +[Some examples are provided here in the RelationService Documentation Page](../../reference/management/using-services/relationservice.md) + +## Use cases + +You might want to create a 'Relation' between two objects either as: + +* A response to a backoffice event. For example, a content item being published that has picked other content items. Storing a relationship between these items would make querying between them easier. Perhaps show all the pages on which a particular 'banner' has been picked. +* A logged-in member on the front end of an Umbraco website might have the facility to upload images. In response, the implementation could store the photos programmatically in the Media Section and at the same time, create a Relation to record the relationship between the member and their uploaded pictures. On an image gallery page, it would be possible to display all the gallery images for the current logged-in Member using the relations. + +## Community Packages + +Some of the community packages that use Relations are listed below: + +* ['Relations Picker'](https://our.umbraco.com/packages/backoffice-extensions/relations-picker/) - a content picker that automatically creates Relations. +* ['ContentRelations'](https://our.umbraco.com/packages/backoffice-extensions/contentrelations/) - allows you to relate two items via the Backoffice. +* ['LinkedPages'](https://our.umbraco.com/packages/backoffice-extensions/linked-pages/) - Provides a LinkedPages context item to show, edit, and add relations between content pages. diff --git a/16/umbraco-cms/fundamentals/data/scheduled-publishing.md b/16/umbraco-cms/fundamentals/data/scheduled-publishing.md new file mode 100644 index 00000000000..485aaf1e881 --- /dev/null +++ b/16/umbraco-cms/fundamentals/data/scheduled-publishing.md @@ -0,0 +1,62 @@ +--- +description: >- + Each document in Umbraco can be scheduled for publishing and unpublishing on a + pre-defined date and time. +--- + +# Scheduled Publishing + +Each document in Umbraco can be scheduled for publishing and unpublishing on a pre-defined date and time. + +You can find the options to do this click on the arrow next to the **Save and publish** button and pick **Schedule...** + + + +
Scheduled publishing

Scheduled publishing

+ +This will open a **Schedule Publishing** dialog where you can specify dates and time. + +![Scheduled publishing](images/scheduled-publishing.png) + +## Timezones + +Your server may be in a different timezone than where you are located. You are able to select a date and time in your timezone and Umbraco will make sure that the item gets published at that time. So, if you select 12 PM then the item will be published at 12PM in the timezone you are in. This may be 8 PM on the server, which is indicated when you select the date and time. + +
Scheduled publishing time

Scheduled publishing

+ +If you are in the same timezone as the server, this message will not appear under the date picker. + +{% hint style="info" %} +In Umbraco versions lower than 7.5, the time you select has to be the time on the server. These older versions of Umbraco do not detect your local time zone. +{% endhint %} + +## Permissions + +All users with access to the Content section in the Umbraco backoffice are able to schedule content for publishing/unpublish. + +## Configuration + +In some cases you will need to adjust your configuration to ensure that scheduled publishing/unpublishing works. The schedule works by the server sending an HTTP(S) request to itself. + +If you are in a load balanced environment special care must be given to ensure you've configured this correctly, [see the docs here](../setup/server-setup/load-balancing/file-system-replication.md) + +If you are not load balancing, the way that Umbraco determines the base URL to send the scheduled HTTP(S) request to is as follows: + +* umbracoSettings:settings/web.routing/@umbracoApplicationUrl if it exists _(see_ [_these docs_](../../reference/configuration/webroutingsettings.md) _for details)_ +* Else umbracoSettings:settings/scheduledTasks/@baseUrl if it exits _(deprecated)_ +* Else umbracoSettings:distributedCall/servers if we have the server in there _(deprecated, see load balance docs)_ +* Else it's based on the first request that the website receives and uses the base URL of this request _(default)_ + +If the `umbracoApplicationUrl` is used, the value also specifies the scheme (either HTTP or HTTPS). The request for scheduled publishing will always be sent over HTTPS if the appSettings `umbracoUseSSL` is set to `true`. + +## Troubleshooting + +If your scheduled publishing/unpublishing is not working as expected it is probably an issue that your server cannot communicate with the scheduled publishing endpoint. This can be caused by a number of reasons such as: + +* Url rewrites in place that prevent the endpoint from being reached +* DNS misconfiguration not allowing the server to communicate to the base URL used in the first request that the website receives - which could be directly affected by a firewall/Network Address Translation (NAT)/load balancer that your server sites behind +* Secure Sockets Layer (SSL) and/or umbracoUseSSL misconfiguration not allowing the server to communicate to the scheduled publishing endpoint on the correct http/https scheme + +To better diagnose the issue you can temporarily change your log4net config settings to be `DEBUG` instead of `INFO`. This will give you all sorts of information including being able to see whether or not the scheduled publishing endpoint is being reached or not. + +In some cases it might be easiest to specify the [umbracoSettings:settings/web.routing/@umbracoApplicationUrl](../../reference/configuration/webroutingsettings.md) setting. This is to ensure that your server is communicating to itself on the correct URL. diff --git a/16/umbraco-cms/fundamentals/data/users/README.md b/16/umbraco-cms/fundamentals/data/users/README.md new file mode 100644 index 00000000000..7e4f27b75ff --- /dev/null +++ b/16/umbraco-cms/fundamentals/data/users/README.md @@ -0,0 +1,84 @@ +--- +description: >- + This guide will explain how to define, create, and manage users in the + backoffice +--- + +# Users + +Users (not to be confused with [Members](../members.md)) are people who have access to the Umbraco backoffice. These could include Content Editors, Translators, Web Designers, and Developers. + +This guide will explain how to define, create, and manage users in the backoffice. + +## Creating a user + +Go to the **Users** section. Here, you will see an overview of all the current backoffice users. + +To create a new user select the **Invite user** button. You will be prompted to add a **Name** and an **Email** for the new user. You will also need to select which **User group** the new user should be added to and enter a **Message** for the invitation. + +Once you have created the user, an autogenerated password will be provided. This password needs to be used to access the account. + +### User profiles + +There are default properties on every user that can be defined: + +* Change/Remove photo. +* Change Password (provides an option to set a new password). +* Disable (allows one to disable service access). +* Update the email for the user. +* Language (sets the backoffice language of the user account). +* User Group (determines the scope of access in the backoffice) +* Start nodes for both Content and Media sections to limit access + +## Default User Groups + +By default, the User Groups available to new users are **Administrators**, **Writers**, **Editors**, **Translators,** and **Sensitive Data**. + +* **Administrator**: Can do anything when editing nodes in the content section (has all permissions). +* **Editor**: Allowed to create and publish content items or nodes on the website without approval from others or restrictions (has permissions to **Public Access**, **Rollback**, **Browse Node**, **Create Content Template**, **Delete**, **Create**, **Publish**, **Unpublish**, **Update**, **Copy**, **Move** and **Sort**). +* **Writer**: Allowed to browse nodes, create nodes, and request for publication of items. Not allowed to publish directly without someone else's approval like an Editor (has permissions to **Browse Node**, **Create**, **Send to Publish,** and **Update**). +* **Translator**: These are used for translating your website. Translators are allowed to browse and update nodes as well as grant dashboard access. Translations of site pages must be reviewed by others before publication (has permissions to **Browse Node** and **Update**). +* **Sensitive data**: Any users added to this User group will have access to view any data marked as sensitive. Learn more about this feature in the [Sensitive Data](../../../reference/security/sensitive-data-on-members.md) article. + +## Creating a User Group + +You can also create your own custom User Groups and add properties and tabs as you would with Document Types and Member Types. + +Go to the **Users** section and select the **Groups** tab in the top-right corner. + +![User Groups Tab](../../../../../14/umbraco-cms/fundamentals/data/images/user-groups.png) + +Select **Create Group** and you will be taken to the **User Group** editor. Here you can define and edit the User Group through custom settings and properties. + +### User Group Parameters + +![Create User Group](../../../../../14/umbraco-cms/fundamentals/data/images/create-user-group.png) + +Shows basic information about the User Group and settings for custom properties. + +* **Name**: The name of the User Group is shown in the User Group tab. +* **Alias**: Used to reference the User Group in code - the alias will be auto-generated based on the name. +* **Assign access**: Define which sections and languages the users will have access to, and if the users should have access to only some or all content and media. +* **Default Permissions**: Select the default permissions granted to users of the User Group. +* **Granular permissions**: Define a specific node the users in the group should have access to. +* **Users**: Add users to the new group. + +## User Permissions + +Depending on which User Group a user is added to, each user has a set of permissions associated with their accounts. These permissions either enable or disable a user's ability to perform its associated function. + +The available user permissions are defined under **Default Permissions** in the User group. + +![Default permissions](../../../../../14/umbraco-cms/fundamentals/data/images/default-permissions.png) + +### Setting User Permissions + +When a new user is created, you can set specific permissions for that user on different domains and subdomains. You can also set permissions on different User Groups, even for the default types. + +## Technical + +As a developer, you are only able to leverage your website from the backoffice when you build on the Users section of Umbraco. This is because the Users section is restricted to the Umbraco backoffice. + +## [Managing Forms Security](https://docs.umbraco.com/umbraco-forms/developer/security) + +Umbraco Forms has a backoffice security model integrated with Umbraco Users. You can manage the details in the **Users** section of the backoffice, within a tree named **Forms Security**. diff --git a/16/umbraco-cms/fundamentals/data/users/api-users.md b/16/umbraco-cms/fundamentals/data/users/api-users.md new file mode 100644 index 00000000000..4de28c8af13 --- /dev/null +++ b/16/umbraco-cms/fundamentals/data/users/api-users.md @@ -0,0 +1,19 @@ +--- +description: This guide will explain the API Users concept and how they differ from regular Users how to define +--- + +# API Users + +API Users allow for authorizing [external access](../../../reference/management-api/external-access.md) to the Management API. + +An API User is identical to a [regular User](README.md) except for one thing: It has no password. In fact, API Users are not allowed to log into the backoffice like regular Users. + +Instead, API Users hold the Client Credentials used to authorize against the Management API. When an external source authorizes using Client Credentials, it effectively assumes the identity of the API User. + +Since API Users are identical to regular Users their backoffice access can be controlled in the same way. This allows for imposing detailed access control on the external sources connected to the Management API. + +![An API User in the backoffice](../images/api-user.png) + +{% hint style="info" %} +Client IDs for API Users are explicitly prefixed with `umbraco-back-office-`. This guards against API Users accidentally taking over one of the Client IDs used by the Umbraco core. +{% endhint %} diff --git a/16/umbraco-cms/fundamentals/design/README.md b/16/umbraco-cms/fundamentals/design/README.md new file mode 100644 index 00000000000..9855ffb0c57 --- /dev/null +++ b/16/umbraco-cms/fundamentals/design/README.md @@ -0,0 +1,21 @@ +# Design + +## [Templates](templates/README.md) + +Creating, managing, and reusing templates in Umbraco. + +## [Rendering content](rendering-content.md) + +Querying and rendering published content. + +## [Rendering media](rendering-media.md) + +Querying and rendering media items. + +## [Partial Views](partial-views.md) + +Working with partial views in Umbraco's templates. + +## [Stylesheets and JavaScript](stylesheets-javascript.md) + +Working with CSS and JavaScript in Umbraco's templates. diff --git a/16/umbraco-cms/fundamentals/design/images/1-creating-stylesheet.png b/16/umbraco-cms/fundamentals/design/images/1-creating-stylesheet.png new file mode 100644 index 00000000000..46a9328fea0 Binary files /dev/null and b/16/umbraco-cms/fundamentals/design/images/1-creating-stylesheet.png differ diff --git a/16/umbraco-cms/fundamentals/design/images/10-reference-script-v9.png b/16/umbraco-cms/fundamentals/design/images/10-reference-script-v9.png new file mode 100644 index 00000000000..14202a63874 Binary files /dev/null and b/16/umbraco-cms/fundamentals/design/images/10-reference-script-v9.png differ diff --git a/16/umbraco-cms/fundamentals/design/images/10-reference-script.png b/16/umbraco-cms/fundamentals/design/images/10-reference-script.png new file mode 100644 index 00000000000..4aabb9cd603 Binary files /dev/null and b/16/umbraco-cms/fundamentals/design/images/10-reference-script.png differ diff --git a/16/umbraco-cms/fundamentals/design/images/2-rte-editor.png b/16/umbraco-cms/fundamentals/design/images/2-rte-editor.png new file mode 100644 index 00000000000..c09094a7d33 Binary files /dev/null and b/16/umbraco-cms/fundamentals/design/images/2-rte-editor.png differ diff --git a/16/umbraco-cms/fundamentals/design/images/3-rte-editor-p2.png b/16/umbraco-cms/fundamentals/design/images/3-rte-editor-p2.png new file mode 100644 index 00000000000..2e2c1713de4 Binary files /dev/null and b/16/umbraco-cms/fundamentals/design/images/3-rte-editor-p2.png differ diff --git a/16/umbraco-cms/fundamentals/design/images/3-rte-editor-p3.png b/16/umbraco-cms/fundamentals/design/images/3-rte-editor-p3.png new file mode 100644 index 00000000000..55f70239042 Binary files /dev/null and b/16/umbraco-cms/fundamentals/design/images/3-rte-editor-p3.png differ diff --git a/16/umbraco-cms/fundamentals/design/images/4-link-css-v9.png b/16/umbraco-cms/fundamentals/design/images/4-link-css-v9.png new file mode 100644 index 00000000000..9a9a2d7f9d2 Binary files /dev/null and b/16/umbraco-cms/fundamentals/design/images/4-link-css-v9.png differ diff --git a/16/umbraco-cms/fundamentals/design/images/4-link-css.png b/16/umbraco-cms/fundamentals/design/images/4-link-css.png new file mode 100644 index 00000000000..ffdf9a237fa Binary files /dev/null and b/16/umbraco-cms/fundamentals/design/images/4-link-css.png differ diff --git a/16/umbraco-cms/fundamentals/design/images/5-rtesheet.png b/16/umbraco-cms/fundamentals/design/images/5-rtesheet.png new file mode 100644 index 00000000000..8abfe1a78c1 Binary files /dev/null and b/16/umbraco-cms/fundamentals/design/images/5-rtesheet.png differ diff --git a/16/umbraco-cms/fundamentals/design/images/6-rte-connect-sheet.png b/16/umbraco-cms/fundamentals/design/images/6-rte-connect-sheet.png new file mode 100644 index 00000000000..ef4e6e1c09c Binary files /dev/null and b/16/umbraco-cms/fundamentals/design/images/6-rte-connect-sheet.png differ diff --git a/16/umbraco-cms/fundamentals/design/images/7-content-rte.png b/16/umbraco-cms/fundamentals/design/images/7-content-rte.png new file mode 100644 index 00000000000..db8e18048d0 Binary files /dev/null and b/16/umbraco-cms/fundamentals/design/images/7-content-rte.png differ diff --git a/16/umbraco-cms/fundamentals/design/images/8-create-js.png b/16/umbraco-cms/fundamentals/design/images/8-create-js.png new file mode 100644 index 00000000000..1290096e6e6 Binary files /dev/null and b/16/umbraco-cms/fundamentals/design/images/8-create-js.png differ diff --git a/16/umbraco-cms/fundamentals/design/images/9-myscript.png b/16/umbraco-cms/fundamentals/design/images/9-myscript.png new file mode 100644 index 00000000000..000ee142a1b Binary files /dev/null and b/16/umbraco-cms/fundamentals/design/images/9-myscript.png differ diff --git a/16/umbraco-cms/fundamentals/design/images/Partial-Views-folder.png b/16/umbraco-cms/fundamentals/design/images/Partial-Views-folder.png new file mode 100644 index 00000000000..fb64d9dc65f Binary files /dev/null and b/16/umbraco-cms/fundamentals/design/images/Partial-Views-folder.png differ diff --git a/16/umbraco-cms/fundamentals/design/images/button-v8.png b/16/umbraco-cms/fundamentals/design/images/button-v8.png new file mode 100644 index 00000000000..2d4e00f98b8 Binary files /dev/null and b/16/umbraco-cms/fundamentals/design/images/button-v8.png differ diff --git a/16/umbraco-cms/fundamentals/design/images/button.png b/16/umbraco-cms/fundamentals/design/images/button.png new file mode 100644 index 00000000000..bb6de535ee1 Binary files /dev/null and b/16/umbraco-cms/fundamentals/design/images/button.png differ diff --git a/16/umbraco-cms/fundamentals/design/images/created-partial-view-from-snippet.png b/16/umbraco-cms/fundamentals/design/images/created-partial-view-from-snippet.png new file mode 100644 index 00000000000..55f96243d65 Binary files /dev/null and b/16/umbraco-cms/fundamentals/design/images/created-partial-view-from-snippet.png differ diff --git a/16/umbraco-cms/fundamentals/design/images/created-partial-view-macro-file-from-snippet.png b/16/umbraco-cms/fundamentals/design/images/created-partial-view-macro-file-from-snippet.png new file mode 100644 index 00000000000..c8a3bb9a69a Binary files /dev/null and b/16/umbraco-cms/fundamentals/design/images/created-partial-view-macro-file-from-snippet.png differ diff --git a/16/umbraco-cms/fundamentals/design/images/created-partial-view-macro-file-without-macro.png b/16/umbraco-cms/fundamentals/design/images/created-partial-view-macro-file-without-macro.png new file mode 100644 index 00000000000..9c8a77f8ee4 Binary files /dev/null and b/16/umbraco-cms/fundamentals/design/images/created-partial-view-macro-file-without-macro.png differ diff --git a/16/umbraco-cms/fundamentals/design/images/created-partial-view-macro-file.png b/16/umbraco-cms/fundamentals/design/images/created-partial-view-macro-file.png new file mode 100644 index 00000000000..d7486429b06 Binary files /dev/null and b/16/umbraco-cms/fundamentals/design/images/created-partial-view-macro-file.png differ diff --git a/16/umbraco-cms/fundamentals/design/images/created-partial-view.png b/16/umbraco-cms/fundamentals/design/images/created-partial-view.png new file mode 100644 index 00000000000..9f38a626f45 Binary files /dev/null and b/16/umbraco-cms/fundamentals/design/images/created-partial-view.png differ diff --git a/16/umbraco-cms/fundamentals/design/images/creating-partial-view-macro-files.png b/16/umbraco-cms/fundamentals/design/images/creating-partial-view-macro-files.png new file mode 100644 index 00000000000..38d0eddacd0 Binary files /dev/null and b/16/umbraco-cms/fundamentals/design/images/creating-partial-view-macro-files.png differ diff --git a/16/umbraco-cms/fundamentals/design/images/creating-partial-view.png b/16/umbraco-cms/fundamentals/design/images/creating-partial-view.png new file mode 100644 index 00000000000..d81a6916a5d Binary files /dev/null and b/16/umbraco-cms/fundamentals/design/images/creating-partial-view.png differ diff --git a/16/umbraco-cms/fundamentals/design/images/dialog.png b/16/umbraco-cms/fundamentals/design/images/dialog.png new file mode 100644 index 00000000000..d79cecce0b7 Binary files /dev/null and b/16/umbraco-cms/fundamentals/design/images/dialog.png differ diff --git a/16/umbraco-cms/fundamentals/design/images/folder.png b/16/umbraco-cms/fundamentals/design/images/folder.png new file mode 100644 index 00000000000..6832f2efa93 Binary files /dev/null and b/16/umbraco-cms/fundamentals/design/images/folder.png differ diff --git a/16/umbraco-cms/fundamentals/design/images/language-fallback.png b/16/umbraco-cms/fundamentals/design/images/language-fallback.png new file mode 100644 index 00000000000..bb48e169036 Binary files /dev/null and b/16/umbraco-cms/fundamentals/design/images/language-fallback.png differ diff --git a/16/umbraco-cms/fundamentals/design/images/partial-views-in-directory.png b/16/umbraco-cms/fundamentals/design/images/partial-views-in-directory.png new file mode 100644 index 00000000000..df2e5682b03 Binary files /dev/null and b/16/umbraco-cms/fundamentals/design/images/partial-views-in-directory.png differ diff --git a/16/umbraco-cms/fundamentals/design/images/query-button.png b/16/umbraco-cms/fundamentals/design/images/query-button.png new file mode 100644 index 00000000000..373fbe4391b Binary files /dev/null and b/16/umbraco-cms/fundamentals/design/images/query-button.png differ diff --git a/16/umbraco-cms/fundamentals/design/images/query-v8.png b/16/umbraco-cms/fundamentals/design/images/query-v8.png new file mode 100644 index 00000000000..d3074f19d3a Binary files /dev/null and b/16/umbraco-cms/fundamentals/design/images/query-v8.png differ diff --git a/16/umbraco-cms/fundamentals/design/images/query-v9.png b/16/umbraco-cms/fundamentals/design/images/query-v9.png new file mode 100644 index 00000000000..53dd76262eb Binary files /dev/null and b/16/umbraco-cms/fundamentals/design/images/query-v9.png differ diff --git a/16/umbraco-cms/fundamentals/design/images/query.png b/16/umbraco-cms/fundamentals/design/images/query.png new file mode 100644 index 00000000000..cc517bf09bc Binary files /dev/null and b/16/umbraco-cms/fundamentals/design/images/query.png differ diff --git a/16/umbraco-cms/fundamentals/design/partial-views.md b/16/umbraco-cms/fundamentals/design/partial-views.md new file mode 100644 index 00000000000..c363e80d7cb --- /dev/null +++ b/16/umbraco-cms/fundamentals/design/partial-views.md @@ -0,0 +1,115 @@ +--- +description: Information on working with partial views in Umbraco +--- + +# Partial Views + +A Partial View (`.cshtml` file) is a regular view that can be used multiple times throughout your site. A Partial View is used to break up large markup files into smaller components such as header, footer, navigation menu, etc. It helps to reduce the duplication of code. A partial view renders a view within the parent view. + +## Partial Views in the Backoffice + +You can create and edit partial views in the **Partial Views** folder from the **Settings** section of the Backoffice. + +![Creating a new partial view](templates/images/create-partial.png) + +In the **Create** menu, there are three options available: + +* New empty partial view +* New partial view from snippet +* Folder (for keeping the partial views organized) + +## Creating a Partial View + +To create a partial view: + +1. Go to the **Settings** section in the Umbraco backoffice. +2. Click **...** next to the **Partial Views** folder. +3. Choose **Create**. +4. Select **New empty partial view**. +5. Enter a partial view name. +6. Click the **Save** button. You will now see the partial view markup in the backoffice editor. + +
Created partial view

Created partial view

+ +By default, the partial views are saved in the `Views/Partials` folder in the solution. + +![Partial View folder in the project directory](images/partial-views-in-directory.png) + +## Creating a Partial View from Snippet + +To create a partial view from the snippet: + +1. Go to the **Settings** section in the Umbraco backoffice. +2. Click **...** next to the **Partial Views** folder. +3. Choose **Create**. +4. Select **New empty partial view from snippet**. +5. Select the snippet you want to create a partial view for and enter a partial view name. The code snippet you selected is displayed in the backoffice editor. +6. Click the **Save** button. + +

Created partial view from snippet

+ +Umbraco provides the following partial view snippets: + +* Empty - Creates an empty partial view file. +* Breadcrumb - Creates a breadcrumb of parents using the `Ancestors()` method to generate links in an unordered HTML list. It displays the name of the current page without a link. +* Edit Profile - Creates a Member profile model that can be edited. +* List Ancestors From Current Page - Displays a list of links to the parents of the current page using the `Ancestors()` method to generate links in an unordered HTML list. It displays the name of the current page without a link. +* List Child Pages From Current Page - Displays a list of links to the children of the current page using the `Children()` method to generate links in an unordered HTML list. +* List Child Pages Ordered By Date - Displays a list of links to the children of the current page using the `Children()` method to generate links in an unordered HTML list. The pages are sorted by the creation date in a descending order using the `OrderByDescending()` method. +* List Child Pages Ordered By Name - Displays a list of links to the children of the current page using the `Children()` method to generate links in an unordered HTML list. The pages are sorted by the page name using the `OrderBy()` method. +* List Child Pages With DocType - Displays only the children of a certain Document Type. +* List Descendants From Current Page - Displays a list of links for every page below the current page in an unordered HTML list. +* Login - Displays a login form. +* Login Status - Displays the user name if the user is logged in. +* Multinode Tree-picker - Lists the items from a Multinode tree picker using the picker's default settings. +* Navigation - Displays a list of links of the pages under the top-most page in the Content tree. It also highlights the currently active page/section in the navigation menu. +* Register Member - Displays a Member registration form. It will only display the properties marked as **Member can edit** on the **Info** tab of the Member Type. +* Site Map - Displays a list of links of all the visible pages of the site using the `Traverse()` method to select and display the markup and links as nested unordered HTML lists. + +## Creating a Folder + +To create a folder: + +1. Go to the **Settings** section in the Umbraco backoffice. +2. Click **...** next to the **Partial Views** folder. +3. Choose **Create**. +4. Select **Folder**. +5. Enter a folder name. +6. Click the **Create** button. + +
+ +
Created folder

Created folder

+ +
+ +## Rendering a Partial View + +To render the created partial view into any template, use any of these helper methods: `@Html.PartialAsync`, `@Html.Partial()`, or `@Html.RenderPartial()` + +```csharp +@using Umbraco.Cms.Web.Common.PublishedModels; +@inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage +@using ContentModels = Umbraco.Cms.Web.Common.PublishedModels; +@{ + Layout = null; +} + +@await Html.PartialAsync("Login") + +@Html.Partial("Login") + +@{ + Html.RenderPartial("Login"); +} +``` + +### Related Articles + +* [Using MVC Partial Views in Umbraco](../../reference/templating/mvc/partial-views.md) + +### Video Materials + +{% embed url="https://www.youtube.com/watch?ab_channel=UmbracoLearningBase&v=RcYM_DJ-JnQ" %} +Getting started with Umbraco: Partial Views +{% endembed %} diff --git a/16/umbraco-cms/fundamentals/design/rendering-content.md b/16/umbraco-cms/fundamentals/design/rendering-content.md new file mode 100644 index 00000000000..c76382ca502 --- /dev/null +++ b/16/umbraco-cms/fundamentals/design/rendering-content.md @@ -0,0 +1,110 @@ +# Rendering Content + +_The primary task of any template is to render the values of the current page or the result of a query against the content cache._ + +## Display a value in your template view + +Each property in your [Document Type](../data/defining-content/#what-is-a-document-type) has an alias, this is used to specify where in the template view to display the value. + +```html +

@Model.Value("pageTitle")

+
@Model.Value("bodyContent")
+ +``` + +### Specifying types of data + +You can specify the type of data being returned to help you format the value for display. + +In our example, we have a string, a date and some rich text: + +```html +

@(Model.Value("pageTitle"))

+
@(Model.Value("bodyContent"))
+

Article date:

+``` + +{% hint style="info" %} +To use `IHtmlEncodedString` as the typed value, add the `@using Umbraco.Cms.Core.Strings;` directive. +{% endhint %} + +### Using ModelsBuilder + +```html +

@Model.PageTitle

+
@Model.BodyContent
+ +``` + +### Using fall-back methods + +The `.Value()` method has a number of optional parameters that support scenarios where we want to "fall-back" to some other content. + +To use the `fallback` type, add the `@using Umbraco.Cms.Core.Models.PublishedContent;` directive. + +* To display a static, default value when a property value is not populated on the current content item: + + ```csharp + @Model.Value("pageTitle", fallback: Fallback.ToDefaultValue, defaultValue: new HtmlString("Default page title")) + ``` +* A second supported method is to traverse up the tree ancestors to try to find a value. If the current content item isn't populated for a property, we can retrieve the value from the parent, grand-parent, or a higher ancestor in the tree. The first ancestor encountered that has a value will be the one returned. + + ```csharp + @Model.Value("pageTitle", fallback: Fallback.ToAncestors) + ``` +* If developing a multi-lingual site and fall-back languages\* have been configured, the third method available is to retrieve a value for a different language, if the language we are requesting does not have content populated. In this way, we could render a field containing French content for a property if it's populated in that language, and if not, default to English. + + ```csharp + @Model.Value("pageTitle", "fr", fallback: Fallback.ToLanguage) + ``` +* We can also combine these options to create some more sophisticated scenarios. For example, we might want to fall-back via language first, and if that doesn't find any populated content, then try to find a value by traversing through the ancestors of the tree. We can do that using the following syntax, with the order of the fall-back options provided determining the order that content will be attempted to be retrieved: + + ```csharp + @Model.Value("pageTitle", "fr", fallback: Fallback.To(Fallback.Language, Fallback.Ancestors)) + ``` +* In this example, we are looking for content firstly on the current node for the default language, and if not found we'll search through the ancestors. If failing to find any populated value from them, we'll use the provided default: + + ```csharp + @Model.Value("pageTitle", fallback: Fallback.To(Fallback.Ancestors, Fallback.DefaultValue), defaultValue: new HtmlString("Default page title")) + ``` +* We can use similar overloads when working with ModelsBuilder, for example: + + ```csharp + // For projects created before January 2020 + @Model.Value(x => x.PageTitle, "fr", fallback: Fallback.ToLanguage) + @Model.Value(x => x.PageTitle, fallback: Fallback.To(Fallback.Ancestors, Fallback.DefaultValue), defaultValue: new HtmlString("Default page title")) + + // For projects created after January 2020 + @Model.ValueFor(x => x.PageTitle, "fr", fallback: Fallback.ToLanguage) + @Model.ValueFor(x => x.PageTitle, fallback: Fallback.To(Fallback.Ancestors, Fallback.DefaultValue), defaultValue: new HtmlString("Default page title")) + ``` + + * Fall-back languages can be configured via the **Languages** tree within the **Settings** section. + * Each language can optionally be provided with a fall-back language, that will be used when content is not populated for the language requested and the appropriate overload parameters are provided. + * It is possible to chain these language fall-backs, so requesting content for Portuguese, could fall-back to Spanish and then on to English. + + ![Configuring fall-back languages](images/language-fallback.png) + +## Query content + +In many cases, you want to do more than display values from the current page, like creating a list of pages in the navigation. You can access content relative to the current page using methods such as `Children()`, `Descendants()` & `Ancestors()`. Explore the [full list of methods](../../reference/templating/mvc/querying.md#traversing). + +You can do this by querying content relative to your current page in template views: + +```csharp +
    + @foreach (var child in Model.Children()) + { +
  • @child.Name()
  • + } +
+``` + +You can use the Query Builder in the template editor to build more advanced queries. ![Query button](../../../../10/umbraco-cms/fundamentals/design/images/button-v8.png) + +![Query helper](../../../../10/umbraco-cms/fundamentals/design/images/query-v9.png) + +### More information + +* [Razor examples](../../reference/templating/mvc/examples.md) +* [Querying](../../reference/templating/mvc/querying.md) diff --git a/16/umbraco-cms/fundamentals/design/rendering-media.md b/16/umbraco-cms/fundamentals/design/rendering-media.md new file mode 100644 index 00000000000..e5adb5bcc58 --- /dev/null +++ b/16/umbraco-cms/fundamentals/design/rendering-media.md @@ -0,0 +1,113 @@ +--- +description: "Info on rendering media items and imaging cropping" +--- + +# Rendering media + +_Templates (Views) can access items in the_ [_Media library_](../data/creating-media/) _to assist in displaying rich content like galleries_. + +In the following examples, we will be looking at rendering an `Image`. + +Image is only one of the 'types' of Media in Umbraco. The same principles apply to all Media Types. The properties available to render will be different from Media Type to Media Type. For example, a `File` will not have a Width property. + +## Rendering a media item + +A media item is not only a reference to a static file. Like content, it is a collection of fields, such as width, height, and file path. This means that accessing and rendering media in a Template is similar to rendering content. + +### Example 1: Accessing a Media Image IPublishedContent item based on the ID + +An uploaded image in the Media library is based on the Media Type `Image` which has a number of standard properties: + +* Name +* Width & Height +* Size +* Type (based on file extension) +* UmbracoFile (the path to the file or JSON data containing crop information) + +These standard properties are pre-populated and set during the upload process. For example, this means that the width and height are calculated for you. + +If you want to add further properties to use with your Media Item, edit the Image Media Type under **Settings**. In this example, we are going to retrieve an image from the Media section. Then we will render out an `img` tag using the URL of the media item and use the Name as the value for the `alt` attribute. + +{% hint style="info" %} +The Media item in the following sample will use a sample Guid (`55240594-b265-4fc2-b1c1-feffc5cf9571`). This example is **not using Models Builder**. +{% endhint %} + +```csharp +@{ + // The Umbraco Helper has a Media method that will retrieve a Media Item by Guid in the form of IPublishedContent. In this example, the Media Item has a Guid of 55240594-b265-4fc2-b1c1-feffc5cf9571 + + var mediaItem = Umbraco.Media(Guid.Parse("55240594-b265-4fc2-b1c1-feffc5cf9571")); + + if (mediaItem != null) + { + // To get the URL for your media item, you use the Url method: + var url = mediaItem.Url(); + // to read a property by alias + var imageHeight = mediaItem.Value("umbracoHeight"); + var imageWidth = mediaItem.Value("umbracoWidth"); + var orientationCssClass = imageWidth > imageHeight ? "img-landscape" : "img-portrait"; + + @mediaItem.Name + } +} +``` + +But wait a second, Umbraco comes with [Models Builder](../../reference/templating/modelsbuilder/). This means that you can use strongly typed models for your media items if Models Builder is enabled (which it is by default). + +### Example 2: Accessing a Media Image ModelsBuilder item based on the ID + +As with example one, we are accessing a MediaType `image` using the same Guid assumption. + +```csharp +@{ + // Since the Image Model generated by Modelsbuilder is a compatible type to IPublishedContent we can use the 'as' operator to convert it into the ModelsBuilder Umbraco.Cms.Web.Common.PublishedModels.Image class + var mediaItemAsImage = Umbraco.Media(Guid.Parse("55240594-b265-4fc2-b1c1-feffc5cf9571")) as Image; + if (mediaItemAsImage != null) + { + // you could add this as an extension method to the Umbraco.Cms.Web.Common.PublishedModels.Image class + var orientationCssClass = mediaItemAsImage.UmbracoWidth > mediaItemAsImage.UmbracoHeight ? "img-landscape" : "img-portrait"; + + @mediaItemAsImage.Name + } +} +``` + +{% hint style="info" %} +It is always worth having null-checks around your code when retrieving media in case the conversion fails or Media() returns null. This makes your code more robust. +{% endhint %} + +### Other Media Items such as `File` + +Accessing other media items can be performed in the same way. The techniques are not limited to the `Image` type, but it is one of the most common use cases. + +## Image Cropper + +The Image Cropper can be used with `Image` Media Types and is the default option for the `umbracoFile` property on an `Image` Media Type. + +When working with the Image Cropper for an image the `GetCropUrl` extension method is used to retrieve cropped versions of the image. Details of the Image Cropper property editor and other examples of using it can be found in the [Image Cropper article](../backoffice/property-editors/built-in-umbraco-property-editors/image-cropper.md). The following is an example to help you get started with the Image Cropper. + +### Example of using Image Cropper + +```csharp +@{ + var mediaItemToCrop = Umbraco.Media(Guid.Parse("55240594-b265-4fc2-b1c1-feffc5cf9571")); + if (mediaItemToCrop != null) + { + @mediaItemToCrop.Name + } +} +``` + +This example assumes that you have set up a crop called **square** on your Image Cropper Data Type. + +If you want the original, uncropped image, you can ignore the GetCropUrl extension method and use one of the previously discussed approaches as shown below. + +```csharp + +``` + +### More information + +* [Media Picker](../backoffice/property-editors/built-in-umbraco-property-editors/media-picker-3.md) +* [Image Cropper](../backoffice/property-editors/built-in-umbraco-property-editors/image-cropper.md) +* [Creating a Media Type](../data/creating-media/#creating-a-media-type) diff --git a/16/umbraco-cms/fundamentals/design/stylesheets-javascript.md b/16/umbraco-cms/fundamentals/design/stylesheets-javascript.md new file mode 100644 index 00000000000..5d58387f0af --- /dev/null +++ b/16/umbraco-cms/fundamentals/design/stylesheets-javascript.md @@ -0,0 +1,81 @@ +--- +description: >- + Information on working with stylesheets and JavaScript in Umbraco, including + bundling & minification. +--- + +# Stylesheets And JavaScript + +## Stylesheets in the Backoffice + +You can create and edit stylesheets in the Stylesheets folder in the Settings section of the Backoffice. + +
Creating a new stylesheet

Creating a new stylesheet

+ +In the Create menu, these options are available: + +* Stylesheet file (for use in templates/views) +* Rich Text Editor stylesheet file (for use in [Rich Text Editor](../backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor/)) +* Folder (for keeping stylesheets organized) + +{% hint style="info" %} +It is currently not possible to use any CSS preprocessor (such as Syntactically Awesome Style Sheets (SASS)) in the backoffice. +{% endhint %} + +After creating a new stylesheet, you would work with it as you would with templates or JavaScript files - using the built-in backoffice text editor. When you're working with stylesheets, you also have access to the Rich Text Editor, which allows you to create CSS styles and get a real-time preview. + +![Stylesheet Rich Text Editor (RTE)](../../../../10/umbraco-cms/fundamentals/design/images/2-rte-editor.png) + +The rules you create in the Rich Text Editor section will carry over to the Code tab. + +![Stylesheet RTE tab](../../../../10/umbraco-cms/fundamentals/design/images/3-rte-editor-p2.png) ![Stylesheet Code tab](../../../../10/umbraco-cms/fundamentals/design/images/3-rte-editor-p3.png) + +To reference your newly included stylesheet in a template file, navigate to Templates, pick the template you like (css files are usually referenced in the layout or home templates) and link to it with the `link` tag. + +![Linking CSS in template](../../../../10/umbraco-cms/fundamentals/design/images/4-link-css-v9.png) + +By default, the stylesheets will be saved in the `wwwroot/css` folder in the solution. To reference them you can use either of the methods used in the above screenshot. + +```html + +``` + +or + +```html + +``` + +With the stylesheet referenced, you will be able to style the template file with the rules and classes defined in the stylesheet. + +Your stylesheets can be used in Rich Text Editors (datatype) as well - please see the [Rich Text Editor](../backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor/#rte-styles) documentation for more information. + +{% hint style="info" %} +If your RTE is styled differently on the frontend of the site, the backoffice styling might be getting overwritten by other stylesheets you have included. +{% endhint %} + +## JavaScript files in the Backoffice + +To create and edit JavaScript files in the Backoffice, head on over to the Scripts folder in the Settings section of the Backoffice. + +
Creating a new JavaScript

Creating a new JavaScript

+ +From here you can add a new JavaScript file, or a new folder. + +Add a new JavaScript file and write your code: + +![Sample JS script](../../../../10/umbraco-cms/fundamentals/design/images/9-myscript.png) + +Then, navigate to the template where you would like to include your JS file. + +```html + +``` + +![Reference the script in template](../../../../10/umbraco-cms/fundamentals/design/images/10-reference-script-v9.png) + +By default all JavaScript files will be stored in the `wwwroot/scripts` folder in the solution. + +{% hint style="info" %} +If you are working locally, you can create CSS and JS files outside of the Backoffice - as long as they are placed in appropriate folders (`css` and `scripts`), they will show up in the Backoffice when you click **...** next to the **Stylesheets** folder and then select **Reload**. +{% endhint %} diff --git a/16/umbraco-cms/fundamentals/design/templates/README.md b/16/umbraco-cms/fundamentals/design/templates/README.md new file mode 100644 index 00000000000..01d90a986a0 --- /dev/null +++ b/16/umbraco-cms/fundamentals/design/templates/README.md @@ -0,0 +1,193 @@ +--- +description: Templating in Umbraco builds on the concept of Razor Views from ASP.NET MVC. +--- + +# Templates + +Templates are the files that control the look and feel of the frontend of your Umbraco websites. Building on the concept of MVC Razor Views, template files enable you to structure your websites using HTML, CSS, and JavaScript. When tied to a Document Type, templates are used to render your Umbraco content on the frontend. + +You can manage and work with your templates directly from the Settings section in the Umbraco backoffice. Each Template can also be found as a `cshtml` file in the `Views` folder in your project directory. + +## Creating Templates + +When building an Umbraco website you can automatically generate Templates when you create a new Document Type. This will ensure the connection between the two and you can jump straight from defining the content to structuring it. + +Choose the option called **[Document Type with Template](../../data/defining-content/README.md)** when you create a new Document Type to automatically create a Template as well. + +In some cases, you might want to create independent Templates that don't have a direct connection to a Document Type. You can follow the steps below to create a new blank Template: + +1. Go to the **Settings** section inside the Umbraco backoffice. +2. Click **...** next to the **Templates** folder. +3. Choose **Create**. +4. Enter a template name. +5. Click the **Save** button. + +You will now see the default template markup in the backoffice template editor. + +![Created template](images/create-template.png) + +## Allowing a Template on a Document Type + +To use a Template on your content, you must first allow it on the content Document Type. + +1. Open the Document Type you want to use the template. +2. Open the **Templates** Workspace View. +3. Select your Template under the **Allowed Templates** section. + +![Allowing template](images/allow-template.png) + +## Inheriting a Template + +A Template can inherit content from a "Master Template". This is done by using the ASP.NET views Layout feature. + +Let's say you have a Template called **MainView**, containing the following HTML: + +```csharp +@inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage +@{ + Layout = null; +} + + + +

Hello world

+ @RenderBody() + + +``` + +This file contains the structural HTML tags for your website. + +By using the Template as the "Master Template" on your other Templates, you can ensure that they inherit the same structural HTML. + +Follow these steps to use a Template file as a Master Template: + +1. Open one of your Template files. +2. Select the **Master template: No master** button above the editor. +3. Select the Template that should be defined as the Master Template. +4. Click **Choose**. + +![Inherit template](images/inherit-template.png) + +Alternatively, you can manually change the value of the `Layout` variable in the Template using the name of the Template file. + +The updated markup will look something like the snippet below and the Template is now referred to as a *Child Template*: + +```csharp +@inherits Umbraco.Web.Mvc.UmbracoViewPage +@{ + Layout = "MainView.cshtml"; +} +

My content

+``` + +When a page that uses a Template with a Master Template defined is rendered, the HTML of the two templates is merged. + +The code from the Template replaces the `@RenderBody()` tag in the Master Template. Following the examples above, the final HTML will look like the code in the snippet below: + +```csharp +@inherits Umbraco.Web.Mvc.UmbracoViewPage +@{ + Layout = null; +} + + + +

Hello world

+

My content

+ + +``` + +## Named Sections + +Template Sections give you added flexibility when building your templates. Use the Template Section together with a Master Template setup, to decide where sections of content are placed. + +If a Child Template needs to add code to the `` tag a Section must be defined and then used in the Master Template. This is made possible by [Named Sections](https://www.youtube.com/watch?v=lrnJwglbGUA). + +The following steps will guide you through defining and using a Named Section: + +1. Open your Template. +2. Select the **Sections** option. +3. Choose **Define a named section**. +4. Give the section a name and click **Submit**. + +![Define a named section by giving it a name](images/defined-named-section.png) + +The following code will be added to your Template: + +```csharp +@section SectionName { + +} +``` + +5. Add your code between the curly brackets. +6. Save the changes. +7. Open the Master Template. +8. Choose a spot for the section and set the cursor there. +9. Select the **Sections** option. +10. Choose **Render a named section**. +11. Enter the name of the section you want to add. +12. Click **Submit**. + +For instance, if you want to be able to add HTML to your `` tags, you would add the tag there: + +```csharp +@inherits Umbraco.Web.Mvc.UmbracoViewPage +@{ + Layout = null; +} + + + + Title + @RenderSection("SectionName", false) + + + + + +``` + +You can decide whether a section should be mandatory or not. Making a section mandatory means that any template using the Master Template is required to have the section defined. + +{% hint style="info" %} +Keep in mind that whenever a mandatory named section is missing, it will result in errors on your website. +{% endhint %} + +To make the section mandatory, you have two options: + +* Add `true` to the code tag: `@RenderSection("SectionName", true)`. +* Check the **Section is mandatory** field when using the **Sections** dialog in the backoffice. + +![Create partial](images/render-named-section-mandatory.png) + +## Injecting Partial Views + +Another way to reuse HTML is to use partial views - which are small reusable views that can be injected into another view. + +Like templates, you can create a partial view, by clicking **...** next to the **Partial Views** folder and selecting **Create**. You can then either create an empty partial view or a partial view from a snippet. + +![Create partial](images/create-partial.png) + +The created partial view can now be injected into any template by using the `@Html.Partial()` method like so: + +```csharp +@inherits Umbraco.Web.Mvc.UmbracoViewPage +@{ + Layout = "MasterView.cshtml"; +} + +

My new page

+@Html.Partial("a-new-view") +``` + +### Related Articles + +* [Basic Razor syntax](basic-razor-syntax.md) +* [Rendering content](../rendering-content.md) + +### Tutorials + +* [Creating a basic website with Umbraco](../../../tutorials/creating-a-basic-website/) diff --git a/16/umbraco-cms/fundamentals/design/templates/basic-razor-syntax.md b/16/umbraco-cms/fundamentals/design/templates/basic-razor-syntax.md new file mode 100644 index 00000000000..0d886804f75 --- /dev/null +++ b/16/umbraco-cms/fundamentals/design/templates/basic-razor-syntax.md @@ -0,0 +1,107 @@ +--- +description: "How to perform common logical tasks in Razor like if/else, foreach loops, switch statements and using the @ character to separate code and markup" +--- + +# Basic Razor Syntax + +_Shows how to perform common logical tasks in Razor like if/else, foreach loops, switch statements and using the @ character to separate code and markup._ + +## The @ symbol + +The @ symbol is used in Razor to initiate code, and tell the compiler where to start interpreting code, instead of returning the content of the file as text. Using a single character for this separation, results in cleaner, compact code which is easier to read. + +```csharp +@* Writing a value inside a html element *@ + +

@Model.Name

+ +@* Inside an attribute *@ +@Model.Name + +@* Using it to start logical structures *@ +@if (selection?.Length > 0) +{ +
    + @foreach (var item in selection) + { +
  • + @item.Name +
  • + } +
+} +``` + +## Embedding comments in razor + +Commenting your code is important, use comments to explain what the code does. `@* *@` indicates a comment, which will not be visible in the rendered output. + +```csharp +@* Here we check if the name is equal to foobar *@ +@if (Model.Name == "foobar") +{ + @foreach (var child in Model.Children()) + { + @* here we write stuff for each child page *@ +

write stuff

+ } +} +``` + +## If/else + +If/else statements perform one task if a condition is true, and another if the condition is not true + +```csharp +@if (Model.Name == "home") +{ +

This is the homepage!

+} + +@if (Model.NodeTypeAlias == "TextPage") +{ +

This is a textpage

+} +else +{ +

This is NOT a textpage

+} +``` + +## Foreach loops + +A foreach loop goes through a collection of items, typically a collection of pages and performs an action for each item + +```csharp +@foreach (var item in Model.Children()) +{ +

The item name is: @Item.Name

+} +``` + +## Switch block + +A Switch block is used when testing a large number of conditions + +```csharp +@switch (Model.WeekDay) +{ + case "Monday": + "

It is Monday

"; + break; + case "Tuesday": + "

It is Tuesday

"; + break; + case "Wednesday": + "

It is Wednesday

"; + break; + default: + "

It's some day of the week

"; + break; +} +``` + +### More information + +- [More examples](../../../reference/templating/mvc/examples.md) +- [Querying](../../../reference/querying/README.md) diff --git a/16/umbraco-cms/fundamentals/design/templates/images/Sections-option.png b/16/umbraco-cms/fundamentals/design/templates/images/Sections-option.png new file mode 100644 index 00000000000..cbff077027e Binary files /dev/null and b/16/umbraco-cms/fundamentals/design/templates/images/Sections-option.png differ diff --git a/16/umbraco-cms/fundamentals/design/templates/images/allow-template-v8.png b/16/umbraco-cms/fundamentals/design/templates/images/allow-template-v8.png new file mode 100644 index 00000000000..a18945a38da Binary files /dev/null and b/16/umbraco-cms/fundamentals/design/templates/images/allow-template-v8.png differ diff --git a/16/umbraco-cms/fundamentals/design/templates/images/allow-template.png b/16/umbraco-cms/fundamentals/design/templates/images/allow-template.png new file mode 100644 index 00000000000..d35ea803f7b Binary files /dev/null and b/16/umbraco-cms/fundamentals/design/templates/images/allow-template.png differ diff --git a/16/umbraco-cms/fundamentals/design/templates/images/create-partial-v8.png b/16/umbraco-cms/fundamentals/design/templates/images/create-partial-v8.png new file mode 100644 index 00000000000..3f51d809f52 Binary files /dev/null and b/16/umbraco-cms/fundamentals/design/templates/images/create-partial-v8.png differ diff --git a/16/umbraco-cms/fundamentals/design/templates/images/create-partial.png b/16/umbraco-cms/fundamentals/design/templates/images/create-partial.png new file mode 100644 index 00000000000..f2db936d658 Binary files /dev/null and b/16/umbraco-cms/fundamentals/design/templates/images/create-partial.png differ diff --git a/16/umbraco-cms/fundamentals/design/templates/images/create-template-v8.png b/16/umbraco-cms/fundamentals/design/templates/images/create-template-v8.png new file mode 100644 index 00000000000..47fa57416a0 Binary files /dev/null and b/16/umbraco-cms/fundamentals/design/templates/images/create-template-v8.png differ diff --git a/16/umbraco-cms/fundamentals/design/templates/images/create-template.png b/16/umbraco-cms/fundamentals/design/templates/images/create-template.png new file mode 100644 index 00000000000..7a4805ff22a Binary files /dev/null and b/16/umbraco-cms/fundamentals/design/templates/images/create-template.png differ diff --git a/16/umbraco-cms/fundamentals/design/templates/images/defined-named-section.png b/16/umbraco-cms/fundamentals/design/templates/images/defined-named-section.png new file mode 100644 index 00000000000..082b4b1ce31 Binary files /dev/null and b/16/umbraco-cms/fundamentals/design/templates/images/defined-named-section.png differ diff --git a/16/umbraco-cms/fundamentals/design/templates/images/inherit-template-v8.png b/16/umbraco-cms/fundamentals/design/templates/images/inherit-template-v8.png new file mode 100644 index 00000000000..cdefa5b56bf Binary files /dev/null and b/16/umbraco-cms/fundamentals/design/templates/images/inherit-template-v8.png differ diff --git a/16/umbraco-cms/fundamentals/design/templates/images/inherit-template.png b/16/umbraco-cms/fundamentals/design/templates/images/inherit-template.png new file mode 100644 index 00000000000..1ebcecc4986 Binary files /dev/null and b/16/umbraco-cms/fundamentals/design/templates/images/inherit-template.png differ diff --git a/16/umbraco-cms/fundamentals/design/templates/images/render-named-section-mandatory.png b/16/umbraco-cms/fundamentals/design/templates/images/render-named-section-mandatory.png new file mode 100644 index 00000000000..2c7edf033ea Binary files /dev/null and b/16/umbraco-cms/fundamentals/design/templates/images/render-named-section-mandatory.png differ diff --git a/16/umbraco-cms/fundamentals/design/templates/images/template-sections-v8.png b/16/umbraco-cms/fundamentals/design/templates/images/template-sections-v8.png new file mode 100644 index 00000000000..d8ce4c1268e Binary files /dev/null and b/16/umbraco-cms/fundamentals/design/templates/images/template-sections-v8.png differ diff --git a/16/umbraco-cms/fundamentals/design/templates/images/template-sections.png b/16/umbraco-cms/fundamentals/design/templates/images/template-sections.png new file mode 100644 index 00000000000..d8ce4c1268e Binary files /dev/null and b/16/umbraco-cms/fundamentals/design/templates/images/template-sections.png differ diff --git a/16/umbraco-cms/fundamentals/design/templates/razor-cheatsheet.md b/16/umbraco-cms/fundamentals/design/templates/razor-cheatsheet.md new file mode 100644 index 00000000000..991507fe1eb --- /dev/null +++ b/16/umbraco-cms/fundamentals/design/templates/razor-cheatsheet.md @@ -0,0 +1,20 @@ +--- +description: "All the code snippets you need to get a jump start on building templates with Razor in Umbraco CMS." +--- + +# Razor Cheatsheet + +The Razor Cheatsheet is a collection of common methods used for building templates and views in Umbraco CMS. + +Get the Umbraco 11 Cheatsheet: [https://umbra.co/razorCheatsheet](https://umbra.co/razorCheatsheet) + +You can also find the [cheatsheet on GitHub](https://github.com/umbraco/UmbracoDocs/tree/RazorCheatSheet) where you can give feedback, contribute, or download the template used to _generate_ the sheet. + +Use the cheatsheet if you: + +* Use Models Builder on your project and +* Use Umbraco 11. + +{% hint style="info" %} +Most of the methods in the Umbraco 11 version of the cheatsheet will also work in Umbraco 10 and 9. +{% endhint %} diff --git a/16/umbraco-cms/fundamentals/get-to-know-umbraco.md b/16/umbraco-cms/fundamentals/get-to-know-umbraco.md new file mode 100644 index 00000000000..f24a8db91ab --- /dev/null +++ b/16/umbraco-cms/fundamentals/get-to-know-umbraco.md @@ -0,0 +1,11 @@ +--- +description: >- + All the fundamentals of using Umbraco - from making a local installation to + extending the backend. +--- + +# Get to know Umbraco + +In this part of the Umbraco CMS documentation, you can get to know the product and the default functionality. It is here you start your Umbraco journey with the installation, setup, and basics of working with the CMS. + +
SetupFind requirements for both local development and hosting as well as guides for installing and upgrading Umbraco CMS.setupsetup-image.png
BackofficeLearn about the different sections found in the Umbraco CMS backoffice and get an overview of the available features.backofficesetup.png
DataDive a little deeper and learn how to create different types of data directly from the Umbraco CMS backoffice.datadata.png
DesignGet to know the different options the Umbraco CMS backoffice provides for designing your website.designdesign.png
CodeA high-level overview of working with some of the more advanced elements like services and notifications.codecode-image.png
diff --git a/16/umbraco-cms/fundamentals/setup/README.md b/16/umbraco-cms/fundamentals/setup/README.md new file mode 100644 index 00000000000..c04f0fc4329 --- /dev/null +++ b/16/umbraco-cms/fundamentals/setup/README.md @@ -0,0 +1,31 @@ +--- +description: "Information on the requirements to setup, install & upgrade Umbraco" +--- + +# Setup + +How to install and configure your Umbraco installation. + +## [Requirements](requirements.md) + +Defines the system requirements to run Umbraco. + +## [Installation](install/README.md) + +Umbraco installation steps and guidelines. + +## [Upgrade your project](upgrading/README.md) + +Covers the steps to upgrade your copy of Umbraco to a newer version. + +## [Server setup](server-setup/README.md) + +Information about server setup for Umbraco including information about permissions and load balancing. + +## [Configuration](../../reference/configuration/README.md) + +How to configure your Umbraco installation. Includes information about all of Umbraco's configuration files and options. + +## [Installing Nightly Builds](install/installing-nightly-builds.md) + +How to install the latest nightly builds. diff --git a/16/umbraco-cms/fundamentals/setup/install/README.md b/16/umbraco-cms/fundamentals/setup/install/README.md new file mode 100644 index 00000000000..a184166a46b --- /dev/null +++ b/16/umbraco-cms/fundamentals/setup/install/README.md @@ -0,0 +1,83 @@ +--- +description: Instructions on installing Umbraco on various platforms using various tools. +--- + +# Installation + +## Install Umbraco using CLI + +The fastest way to get the latest version of Umbraco up and running is using the command line (CLI). + +1. Open your command line. +2. Install the Umbraco templates: + +```bash +dotnet new install Umbraco.Templates +``` + +3. Create a new project: + +```bash +dotnet new umbraco --name MyProject +``` + +4. Navigate to the newly created project folder. It will be the folder containing the `.csproj` file: + +```bash +cd MyProject +``` + +5. Build and run the newly created Umbraco site: + +```bash +dotnet run +``` + +6. The console will output a message similar to: `[10:57:39 INF] Now listening on: https://localhost:44388` + +{% hint style="info" %} +We recommend setting up a developer certificate and running the website under HTTPS. If that has not yet been configured, run the following command: + +```console +dotnet dev-certs https --trust +``` +{% endhint %} + +7. Open your browser and navigate to that URL. +8. Follow the instructions to finish up the installation of Umbraco. + +{% hint style="info" %} +Members of the Umbraco Community have created a website that makes the installation of Umbraco a lot easier for you. You can find the website at [https://psw.codeshare.co.uk](https://psw.codeshare.co.uk). On the website, you can configure your options to generate the required script to run. Click on the Install Script tab to get the commands you need to paste into the terminal. This tab also includes the commands for adding a starter kit or unattended install which creates the database for you. +{% endhint %} + +## Alternative Methods for Installing Umbraco + +There are numerous ways to install Umbraco. Below, you can find links to different installation methods that will help you easily install and set up Umbraco projects. + +### [.NET CLI installation](install-umbraco-with-templates.md) + +.NET CLI, included with the .NET Software Development Kit (SDK), can be used to install or uninstall .NET templates from NuGet. This can be done by using the `dotnet new` command on any OS. The underlying Template Engine enables the creation of custom templates which make new project bootstrapping much faster. With a few steps you can have an Umbraco project running without the need for a code editor. + +### [Visual Studio installation](visual-studio.md) + +Visual Studio is used to write native code and managed code supported by .NET and many others. Its built-in tools provide the ability to develop and execute applications for any platform. Developers will be able to install Umbraco without ever having to leave Visual Studio. + +### [Run Umbraco on IIS](iis.md) + +Learn how to run an already installed local installation of Umbraco. + +### [VS Code installation](install-umbraco-with-vs-code.md) + +Visual Studio Code is an editor with an embedded webserver (through the IIS Express extension). A fast way to get you up and running with Umbraco. + +### [Installing Nightly Builds](installing-nightly-builds.md) + +From Umbraco v9 and above you can use the Nightly Builds to get the latest version to use and test before it is released. Learn how to install the Nightly builds to get started. + +### [Running Umbraco on Linux/macOS](running-umbraco-on-linux-macos.md) + +Since Umbraco 9 it has been possible to run Umbraco CMS natively on Linux or macOS High Sierra. To get Umbraco running you will need to follow some steps. + +### [Install Umbraco unattended](unattended-install.md) + +Use the Unattended installs when spinning up Umbraco instances on something like Azure Web Apps to avoid having to run through the installation wizard. diff --git a/16/umbraco-cms/fundamentals/setup/install/iis.md b/16/umbraco-cms/fundamentals/setup/install/iis.md new file mode 100644 index 00000000000..c6ce71b85c1 --- /dev/null +++ b/16/umbraco-cms/fundamentals/setup/install/iis.md @@ -0,0 +1,113 @@ +--- +description: This article describes how to run an Umbraco 9 site on a local IIS server. +--- + +# Local IIS With Umbraco + +This is a quick guide on getting your Umbraco website running locally on IIS. + +The guide will assume you already have IIS configured and know your way around it, as well as having a local website you wish to host. + +## Setting up prerequisites + +First, you need to ensure you have "Development time IIS support installed". To check this, go to the Visual Studio installer, click modify and check on the right side under "ASP.NET and web development": + +![Checking the IIS module exists](../../../../../10/umbraco-cms/fundamentals/setup/install/images/iis-module.png) + +Once that is installed you should set up a new IIS site - and make sure to add the hostname to your hosts file as well. Here is my setup for an example: + +![IIS site example](../../../../../10/umbraco-cms/fundamentals/setup/install/images/iis-site.png) + +{% hint style="info" %} +For the path you want to point it at the root of your site - where the `.csproj` file is. +{% endhint %} + +## Add permissions to NuGet cache folder + +You might need to change permissions for the NuGet cache folder - `C:\users\\.nuget\packages`. The user or group (IIS\_IUSRS) that the IIS site is running on requires Read permissions on this folder because this is where some of the files for Umbraco and Umbraco packages are being served from during development. If the IIS user or group does not have permission to read from the NuGet cache folder, you could run into a `DirectoryNotFoundException` while running the site. + +When the site is published these files are copied from the NuGet cache folder to `wwwroot/umbraco` and `wwwroot/App_Plugins` and these folders will typically have the correct permissions. For more information on setting permissions, see the [File and folder permissions](../server-setup/permissions.md) article. + +## Add new launch profile + +At this point you can go to your Visual Studio solution of the site and in the `Properties` folder there is a `launchSettings.json` file, that looks like this: + +```json +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:40264", + "sslPort": 44360 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "Umbraco.Web.UI.NetCore": { + "commandName": "Project", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "applicationUrl": "https://localhost:44360;http://localhost:40264" + } + } +} +``` + +You can add a new profile called IIS, and point it at your local domain. Here it is with my example domain: + +```json +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iis": { + "applicationUrl": "https://testsite.local", + "sslPort": 0 + }, + "iisExpress": { + "applicationUrl": "http://localhost:40264", + "sslPort": 44360 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IIS": { + "commandName": "IIS", + "launchBrowser": true, + "launchUrl": "https://testsite.local", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "Umbraco.Web.UI.NetCore": { + "commandName": "Project", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "applicationUrl": "https://localhost:44360;http://localhost:40264" + } + } +} +``` + +At this point IIS will be added to the launch profiles, and you can run the site from Visual Studio by choosing IIS in the dropdown: + +![Launch profiles](../../../../../10/umbraco-cms/fundamentals/setup/install/images/launchprofiles.png) + +And finally the site is running from your local IIS: + +![Local IIS site](../../../../../10/umbraco-cms/fundamentals/setup/install/images/voila.png) diff --git a/16/umbraco-cms/fundamentals/setup/install/images/Manual/2012-03-12_204006.png b/16/umbraco-cms/fundamentals/setup/install/images/Manual/2012-03-12_204006.png new file mode 100644 index 00000000000..1d3c945c2a9 Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/install/images/Manual/2012-03-12_204006.png differ diff --git a/16/umbraco-cms/fundamentals/setup/install/images/Manual/2012-03-12_204144.png b/16/umbraco-cms/fundamentals/setup/install/images/Manual/2012-03-12_204144.png new file mode 100644 index 00000000000..c7c600948b3 Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/install/images/Manual/2012-03-12_204144.png differ diff --git a/16/umbraco-cms/fundamentals/setup/install/images/Manual/2012-03-12_204429.png b/16/umbraco-cms/fundamentals/setup/install/images/Manual/2012-03-12_204429.png new file mode 100644 index 00000000000..52dbb612873 Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/install/images/Manual/2012-03-12_204429.png differ diff --git a/16/umbraco-cms/fundamentals/setup/install/images/Manual/2012-03-12_204433.png b/16/umbraco-cms/fundamentals/setup/install/images/Manual/2012-03-12_204433.png new file mode 100644 index 00000000000..efe194d3e53 Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/install/images/Manual/2012-03-12_204433.png differ diff --git a/16/umbraco-cms/fundamentals/setup/install/images/Manual/2012-03-12_223022.png b/16/umbraco-cms/fundamentals/setup/install/images/Manual/2012-03-12_223022.png new file mode 100644 index 00000000000..b7dceace3c4 Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/install/images/Manual/2012-03-12_223022.png differ diff --git a/16/umbraco-cms/fundamentals/setup/install/images/Manual/2012-03-17_164508.png b/16/umbraco-cms/fundamentals/setup/install/images/Manual/2012-03-17_164508.png new file mode 100644 index 00000000000..a1e8f9856b9 Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/install/images/Manual/2012-03-17_164508.png differ diff --git a/16/umbraco-cms/fundamentals/setup/install/images/Manual/2012-03-17_173822.png b/16/umbraco-cms/fundamentals/setup/install/images/Manual/2012-03-17_173822.png new file mode 100644 index 00000000000..78b3a4db373 Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/install/images/Manual/2012-03-17_173822.png differ diff --git a/16/umbraco-cms/fundamentals/setup/install/images/Nightly/Latest_nightly_build_version.jpg b/16/umbraco-cms/fundamentals/setup/install/images/Nightly/Latest_nightly_build_version.jpg new file mode 100644 index 00000000000..341da76534c Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/install/images/Nightly/Latest_nightly_build_version.jpg differ diff --git a/16/umbraco-cms/fundamentals/setup/install/images/Nightly/Manage_NuGet_Pkgs.jpg b/16/umbraco-cms/fundamentals/setup/install/images/Nightly/Manage_NuGet_Pkgs.jpg new file mode 100644 index 00000000000..68138f7e4b6 Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/install/images/Nightly/Manage_NuGet_Pkgs.jpg differ diff --git a/16/umbraco-cms/fundamentals/setup/install/images/Nightly/Manage_Packages.jpg b/16/umbraco-cms/fundamentals/setup/install/images/Nightly/Manage_Packages.jpg new file mode 100644 index 00000000000..9ff2c8538dc Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/install/images/Nightly/Manage_Packages.jpg differ diff --git a/16/umbraco-cms/fundamentals/setup/install/images/Nightly/NewFeed_Details.jpg b/16/umbraco-cms/fundamentals/setup/install/images/Nightly/NewFeed_Details.jpg new file mode 100644 index 00000000000..5ce96af75da Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/install/images/Nightly/NewFeed_Details.jpg differ diff --git a/16/umbraco-cms/fundamentals/setup/install/images/Nightly/NuGet_NewFeed.jpg b/16/umbraco-cms/fundamentals/setup/install/images/Nightly/NuGet_NewFeed.jpg new file mode 100644 index 00000000000..e17dee522b9 Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/install/images/Nightly/NuGet_NewFeed.jpg differ diff --git a/16/umbraco-cms/fundamentals/setup/install/images/Nightly/Register_Nightly_Feed.jpg b/16/umbraco-cms/fundamentals/setup/install/images/Nightly/Register_Nightly_Feed.jpg new file mode 100644 index 00000000000..bd549030ecc Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/install/images/Nightly/Register_Nightly_Feed.jpg differ diff --git a/16/umbraco-cms/fundamentals/setup/install/images/Nightly/Rider_Nightly_Feed.jpg b/16/umbraco-cms/fundamentals/setup/install/images/Nightly/Rider_Nightly_Feed.jpg new file mode 100644 index 00000000000..68701876301 Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/install/images/Nightly/Rider_Nightly_Feed.jpg differ diff --git a/16/umbraco-cms/fundamentals/setup/install/images/Nightly/Rider_Nightly_Feed_version.jpg b/16/umbraco-cms/fundamentals/setup/install/images/Nightly/Rider_Nightly_Feed_version.jpg new file mode 100644 index 00000000000..9b60bb59f21 Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/install/images/Nightly/Rider_Nightly_Feed_version.jpg differ diff --git a/16/umbraco-cms/fundamentals/setup/install/images/NuGet/enable-package-manager-console-v8.png b/16/umbraco-cms/fundamentals/setup/install/images/NuGet/enable-package-manager-console-v8.png new file mode 100644 index 00000000000..9b0a1ade255 Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/install/images/NuGet/enable-package-manager-console-v8.png differ diff --git a/16/umbraco-cms/fundamentals/setup/install/images/NuGet/enable-package-manager-console.png b/16/umbraco-cms/fundamentals/setup/install/images/NuGet/enable-package-manager-console.png new file mode 100644 index 00000000000..b04913257fe Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/install/images/NuGet/enable-package-manager-console.png differ diff --git a/16/umbraco-cms/fundamentals/setup/install/images/NuGet/manage-nuget-packages-v8.png b/16/umbraco-cms/fundamentals/setup/install/images/NuGet/manage-nuget-packages-v8.png new file mode 100644 index 00000000000..7cb2bc648ed Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/install/images/NuGet/manage-nuget-packages-v8.png differ diff --git a/16/umbraco-cms/fundamentals/setup/install/images/NuGet/manage-nuget-packages.png b/16/umbraco-cms/fundamentals/setup/install/images/NuGet/manage-nuget-packages.png new file mode 100644 index 00000000000..758cdb3f246 Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/install/images/NuGet/manage-nuget-packages.png differ diff --git a/16/umbraco-cms/fundamentals/setup/install/images/NuGet/new-project-dotnet4.png b/16/umbraco-cms/fundamentals/setup/install/images/NuGet/new-project-dotnet4.png new file mode 100644 index 00000000000..18cc5e95e3c Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/install/images/NuGet/new-project-dotnet4.png differ diff --git a/16/umbraco-cms/fundamentals/setup/install/images/NuGet/new-project-vs2012.png b/16/umbraco-cms/fundamentals/setup/install/images/NuGet/new-project-vs2012.png new file mode 100644 index 00000000000..8278146c95b Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/install/images/NuGet/new-project-vs2012.png differ diff --git a/16/umbraco-cms/fundamentals/setup/install/images/NuGet/new-project-vs2013-1.png b/16/umbraco-cms/fundamentals/setup/install/images/NuGet/new-project-vs2013-1.png new file mode 100644 index 00000000000..e336c5e672c Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/install/images/NuGet/new-project-vs2013-1.png differ diff --git a/16/umbraco-cms/fundamentals/setup/install/images/NuGet/new-project-vs2013-2.png b/16/umbraco-cms/fundamentals/setup/install/images/NuGet/new-project-vs2013-2.png new file mode 100644 index 00000000000..166878fd19c Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/install/images/NuGet/new-project-vs2013-2.png differ diff --git a/16/umbraco-cms/fundamentals/setup/install/images/NuGet/new-project-vs2013-3.png b/16/umbraco-cms/fundamentals/setup/install/images/NuGet/new-project-vs2013-3.png new file mode 100644 index 00000000000..0a9ccb0e8f4 Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/install/images/NuGet/new-project-vs2013-3.png differ diff --git a/16/umbraco-cms/fundamentals/setup/install/images/NuGet/new-project-vs2017-1-v8.png b/16/umbraco-cms/fundamentals/setup/install/images/NuGet/new-project-vs2017-1-v8.png new file mode 100644 index 00000000000..e745a23ac68 Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/install/images/NuGet/new-project-vs2017-1-v8.png differ diff --git a/16/umbraco-cms/fundamentals/setup/install/images/NuGet/new-project-vs2017-2-v8.png b/16/umbraco-cms/fundamentals/setup/install/images/NuGet/new-project-vs2017-2-v8.png new file mode 100644 index 00000000000..0129e11fa81 Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/install/images/NuGet/new-project-vs2017-2-v8.png differ diff --git a/16/umbraco-cms/fundamentals/setup/install/images/NuGet/nuget-overwrite-dialog.png b/16/umbraco-cms/fundamentals/setup/install/images/NuGet/nuget-overwrite-dialog.png new file mode 100644 index 00000000000..9b8dd02ba54 Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/install/images/NuGet/nuget-overwrite-dialog.png differ diff --git a/16/umbraco-cms/fundamentals/setup/install/images/NuGet/nuget-search-v8.png b/16/umbraco-cms/fundamentals/setup/install/images/NuGet/nuget-search-v8.png new file mode 100644 index 00000000000..e0edef36004 Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/install/images/NuGet/nuget-search-v8.png differ diff --git a/16/umbraco-cms/fundamentals/setup/install/images/NuGet/nuget-search.png b/16/umbraco-cms/fundamentals/setup/install/images/NuGet/nuget-search.png new file mode 100644 index 00000000000..71dd8c30c5d Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/install/images/NuGet/nuget-search.png differ diff --git a/16/umbraco-cms/fundamentals/setup/install/images/NuGet/nuget-update.png b/16/umbraco-cms/fundamentals/setup/install/images/NuGet/nuget-update.png new file mode 100644 index 00000000000..1a50a023a0c Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/install/images/NuGet/nuget-update.png differ diff --git a/16/umbraco-cms/fundamentals/setup/install/images/NuGet/package-manager-console-overwrite.png b/16/umbraco-cms/fundamentals/setup/install/images/NuGet/package-manager-console-overwrite.png new file mode 100644 index 00000000000..267d5c0e5d1 Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/install/images/NuGet/package-manager-console-overwrite.png differ diff --git a/16/umbraco-cms/fundamentals/setup/install/images/NuGet/package-manager-console.png b/16/umbraco-cms/fundamentals/setup/install/images/NuGet/package-manager-console.png new file mode 100644 index 00000000000..2a2b89de938 Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/install/images/NuGet/package-manager-console.png differ diff --git a/16/umbraco-cms/fundamentals/setup/install/images/NuGet/visual-studio-version-v8.png b/16/umbraco-cms/fundamentals/setup/install/images/NuGet/visual-studio-version-v8.png new file mode 100644 index 00000000000..efba7ce0289 Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/install/images/NuGet/visual-studio-version-v8.png differ diff --git a/16/umbraco-cms/fundamentals/setup/install/images/Rider/add-the-feed.jpg b/16/umbraco-cms/fundamentals/setup/install/images/Rider/add-the-feed.jpg new file mode 100644 index 00000000000..818aea631f8 Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/install/images/Rider/add-the-feed.jpg differ diff --git a/16/umbraco-cms/fundamentals/setup/install/images/Rider/choose-the-feed.jpg b/16/umbraco-cms/fundamentals/setup/install/images/Rider/choose-the-feed.jpg new file mode 100644 index 00000000000..03ad0197afe Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/install/images/Rider/choose-the-feed.jpg differ diff --git a/16/umbraco-cms/fundamentals/setup/install/images/Rider/find-the-version.jpg b/16/umbraco-cms/fundamentals/setup/install/images/Rider/find-the-version.jpg new file mode 100644 index 00000000000..a9a7e2aba07 Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/install/images/Rider/find-the-version.jpg differ diff --git a/16/umbraco-cms/fundamentals/setup/install/images/Rider/open-add-feed.jpg b/16/umbraco-cms/fundamentals/setup/install/images/Rider/open-add-feed.jpg new file mode 100644 index 00000000000..9212394b306 Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/install/images/Rider/open-add-feed.jpg differ diff --git a/16/umbraco-cms/fundamentals/setup/install/images/VS/Umbraco10_install.png b/16/umbraco-cms/fundamentals/setup/install/images/VS/Umbraco10_install.png new file mode 100644 index 00000000000..b1005b76548 Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/install/images/VS/Umbraco10_install.png differ diff --git a/16/umbraco-cms/fundamentals/setup/install/images/VS/additional-info.png b/16/umbraco-cms/fundamentals/setup/install/images/VS/additional-info.png new file mode 100644 index 00000000000..9429fc52c81 Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/install/images/VS/additional-info.png differ diff --git a/16/umbraco-cms/fundamentals/setup/install/images/VS/configure-project.png b/16/umbraco-cms/fundamentals/setup/install/images/VS/configure-project.png new file mode 100644 index 00000000000..b5882efaab7 Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/install/images/VS/configure-project.png differ diff --git a/16/umbraco-cms/fundamentals/setup/install/images/VS/create-project.png b/16/umbraco-cms/fundamentals/setup/install/images/VS/create-project.png new file mode 100644 index 00000000000..a75a86267b1 Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/install/images/VS/create-project.png differ diff --git a/16/umbraco-cms/fundamentals/setup/install/images/VS/find-the-version.jpg b/16/umbraco-cms/fundamentals/setup/install/images/VS/find-the-version.jpg new file mode 100644 index 00000000000..7e82fe6ee2b Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/install/images/VS/find-the-version.jpg differ diff --git a/16/umbraco-cms/fundamentals/setup/install/images/VS/open-nuget-package-manager.jpg b/16/umbraco-cms/fundamentals/setup/install/images/VS/open-nuget-package-manager.jpg new file mode 100644 index 00000000000..b671b595c7a Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/install/images/VS/open-nuget-package-manager.jpg differ diff --git a/16/umbraco-cms/fundamentals/setup/install/images/VS/open-nuget-setttings.jpg b/16/umbraco-cms/fundamentals/setup/install/images/VS/open-nuget-setttings.jpg new file mode 100644 index 00000000000..bf51a558012 Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/install/images/VS/open-nuget-setttings.jpg differ diff --git a/16/umbraco-cms/fundamentals/setup/install/images/VS/ready-solution.png b/16/umbraco-cms/fundamentals/setup/install/images/VS/ready-solution.png new file mode 100644 index 00000000000..9059c79ec2f Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/install/images/VS/ready-solution.png differ diff --git a/16/umbraco-cms/fundamentals/setup/install/images/VS/registering-nightly-feed.jpg b/16/umbraco-cms/fundamentals/setup/install/images/VS/registering-nightly-feed.jpg new file mode 100644 index 00000000000..468f63a7e1e Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/install/images/VS/registering-nightly-feed.jpg differ diff --git a/16/umbraco-cms/fundamentals/setup/install/images/VS/select-nuget-feed.jpg b/16/umbraco-cms/fundamentals/setup/install/images/VS/select-nuget-feed.jpg new file mode 100644 index 00000000000..2479463ebbd Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/install/images/VS/select-nuget-feed.jpg differ diff --git a/16/umbraco-cms/fundamentals/setup/install/images/VS/visual-studio-version-v10.png b/16/umbraco-cms/fundamentals/setup/install/images/VS/visual-studio-version-v10.png new file mode 100644 index 00000000000..16638b8c2a9 Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/install/images/VS/visual-studio-version-v10.png differ diff --git a/16/umbraco-cms/fundamentals/setup/install/images/VS/visual-studio-version-v9.png b/16/umbraco-cms/fundamentals/setup/install/images/VS/visual-studio-version-v9.png new file mode 100644 index 00000000000..e7aea38b466 Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/install/images/VS/visual-studio-version-v9.png differ diff --git a/16/umbraco-cms/fundamentals/setup/install/images/VsCode/1.PNG b/16/umbraco-cms/fundamentals/setup/install/images/VsCode/1.PNG new file mode 100644 index 00000000000..f279c232dde Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/install/images/VsCode/1.PNG differ diff --git a/16/umbraco-cms/fundamentals/setup/install/images/VsCode/2.PNG b/16/umbraco-cms/fundamentals/setup/install/images/VsCode/2.PNG new file mode 100644 index 00000000000..cb5d150d76a Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/install/images/VsCode/2.PNG differ diff --git a/16/umbraco-cms/fundamentals/setup/install/images/VsCode/3.PNG b/16/umbraco-cms/fundamentals/setup/install/images/VsCode/3.PNG new file mode 100644 index 00000000000..e5759f35f58 Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/install/images/VsCode/3.PNG differ diff --git a/16/umbraco-cms/fundamentals/setup/install/images/VsCode/4.PNG b/16/umbraco-cms/fundamentals/setup/install/images/VsCode/4.PNG new file mode 100644 index 00000000000..364fe1c4a3f Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/install/images/VsCode/4.PNG differ diff --git a/16/umbraco-cms/fundamentals/setup/install/images/VsCode/ConfigureTask.png b/16/umbraco-cms/fundamentals/setup/install/images/VsCode/ConfigureTask.png new file mode 100644 index 00000000000..880a0e3e594 Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/install/images/VsCode/ConfigureTask.png differ diff --git a/16/umbraco-cms/fundamentals/setup/install/images/VsCode/Installer-v8.PNG b/16/umbraco-cms/fundamentals/setup/install/images/VsCode/Installer-v8.PNG new file mode 100644 index 00000000000..3b9d2a263a6 Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/install/images/VsCode/Installer-v8.PNG differ diff --git a/16/umbraco-cms/fundamentals/setup/install/images/VsCode/Installer-v9.png b/16/umbraco-cms/fundamentals/setup/install/images/VsCode/Installer-v9.png new file mode 100644 index 00000000000..30036fadebb Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/install/images/VsCode/Installer-v9.png differ diff --git a/16/umbraco-cms/fundamentals/setup/install/images/VsCode/NetcoreTask.png b/16/umbraco-cms/fundamentals/setup/install/images/VsCode/NetcoreTask.png new file mode 100644 index 00000000000..41d2f75c4b4 Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/install/images/VsCode/NetcoreTask.png differ diff --git a/16/umbraco-cms/fundamentals/setup/install/images/VsCode/NetcoreTemplate.png b/16/umbraco-cms/fundamentals/setup/install/images/VsCode/NetcoreTemplate.png new file mode 100644 index 00000000000..f8f28bc4d02 Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/install/images/VsCode/NetcoreTemplate.png differ diff --git a/16/umbraco-cms/fundamentals/setup/install/images/VsCode/TaskJsonFromTemplate.png b/16/umbraco-cms/fundamentals/setup/install/images/VsCode/TaskJsonFromTemplate.png new file mode 100644 index 00000000000..8c11cb849b1 Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/install/images/VsCode/TaskJsonFromTemplate.png differ diff --git a/16/umbraco-cms/fundamentals/setup/install/images/VsCode/VsCodeExtension.png b/16/umbraco-cms/fundamentals/setup/install/images/VsCode/VsCodeExtension.png new file mode 100644 index 00000000000..8cdb2030c56 Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/install/images/VsCode/VsCodeExtension.png differ diff --git a/16/umbraco-cms/fundamentals/setup/install/images/VsCode/creatingLaunchFile.png b/16/umbraco-cms/fundamentals/setup/install/images/VsCode/creatingLaunchFile.png new file mode 100644 index 00000000000..3f4d7881f43 Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/install/images/VsCode/creatingLaunchFile.png differ diff --git a/16/umbraco-cms/fundamentals/setup/install/images/VsCode/dashboard-v8.PNG b/16/umbraco-cms/fundamentals/setup/install/images/VsCode/dashboard-v8.PNG new file mode 100644 index 00000000000..1855ef73dac Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/install/images/VsCode/dashboard-v8.PNG differ diff --git a/16/umbraco-cms/fundamentals/setup/install/images/VsCode/netcoreStructure.png b/16/umbraco-cms/fundamentals/setup/install/images/VsCode/netcoreStructure.png new file mode 100644 index 00000000000..2080f1f70a9 Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/install/images/VsCode/netcoreStructure.png differ diff --git a/16/umbraco-cms/fundamentals/setup/install/images/WebMatrix/web-db-CE.png b/16/umbraco-cms/fundamentals/setup/install/images/WebMatrix/web-db-CE.png new file mode 100644 index 00000000000..b0554e526b1 Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/install/images/WebMatrix/web-db-CE.png differ diff --git a/16/umbraco-cms/fundamentals/setup/install/images/WebMatrix/web-db-install.png b/16/umbraco-cms/fundamentals/setup/install/images/WebMatrix/web-db-install.png new file mode 100644 index 00000000000..f19b1d36b03 Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/install/images/WebMatrix/web-db-install.png differ diff --git a/16/umbraco-cms/fundamentals/setup/install/images/WebMatrix/web-finish.png b/16/umbraco-cms/fundamentals/setup/install/images/WebMatrix/web-finish.png new file mode 100644 index 00000000000..e2d51d1801b Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/install/images/WebMatrix/web-finish.png differ diff --git a/16/umbraco-cms/fundamentals/setup/install/images/WebMatrix/web-start.png b/16/umbraco-cms/fundamentals/setup/install/images/WebMatrix/web-start.png new file mode 100644 index 00000000000..e16b4d6e776 Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/install/images/WebMatrix/web-start.png differ diff --git a/16/umbraco-cms/fundamentals/setup/install/images/WebMatrix/web-starter-kit.png b/16/umbraco-cms/fundamentals/setup/install/images/WebMatrix/web-starter-kit.png new file mode 100644 index 00000000000..a3cea2b6991 Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/install/images/WebMatrix/web-starter-kit.png differ diff --git a/16/umbraco-cms/fundamentals/setup/install/images/WebMatrix/web-user.png b/16/umbraco-cms/fundamentals/setup/install/images/WebMatrix/web-user.png new file mode 100644 index 00000000000..a92f5250986 Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/install/images/WebMatrix/web-user.png differ diff --git a/16/umbraco-cms/fundamentals/setup/install/images/WebMatrix/webmatrix-license.png b/16/umbraco-cms/fundamentals/setup/install/images/WebMatrix/webmatrix-license.png new file mode 100644 index 00000000000..49dffdfbe4e Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/install/images/WebMatrix/webmatrix-license.png differ diff --git a/16/umbraco-cms/fundamentals/setup/install/images/WebMatrix/webmatrix-search.png b/16/umbraco-cms/fundamentals/setup/install/images/WebMatrix/webmatrix-search.png new file mode 100644 index 00000000000..97d50abf896 Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/install/images/WebMatrix/webmatrix-search.png differ diff --git a/16/umbraco-cms/fundamentals/setup/install/images/WebMatrix/webmatrix-start.png b/16/umbraco-cms/fundamentals/setup/install/images/WebMatrix/webmatrix-start.png new file mode 100644 index 00000000000..5e2c73592d5 Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/install/images/WebMatrix/webmatrix-start.png differ diff --git a/16/umbraco-cms/fundamentals/setup/install/images/WebMatrix/webmatrix3-database.png b/16/umbraco-cms/fundamentals/setup/install/images/WebMatrix/webmatrix3-database.png new file mode 100644 index 00000000000..62b51b346e9 Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/install/images/WebMatrix/webmatrix3-database.png differ diff --git a/16/umbraco-cms/fundamentals/setup/install/images/WebMatrix/webmatrix3-install-complete.png b/16/umbraco-cms/fundamentals/setup/install/images/WebMatrix/webmatrix3-install-complete.png new file mode 100644 index 00000000000..6d11719b8be Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/install/images/WebMatrix/webmatrix3-install-complete.png differ diff --git a/16/umbraco-cms/fundamentals/setup/install/images/WebMatrix/webmatrix3-localhost.png b/16/umbraco-cms/fundamentals/setup/install/images/WebMatrix/webmatrix3-localhost.png new file mode 100644 index 00000000000..0f913f58c8a Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/install/images/WebMatrix/webmatrix3-localhost.png differ diff --git a/16/umbraco-cms/fundamentals/setup/install/images/WebMatrix/webmatrix3-start.PNG b/16/umbraco-cms/fundamentals/setup/install/images/WebMatrix/webmatrix3-start.PNG new file mode 100644 index 00000000000..9c11306027f Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/install/images/WebMatrix/webmatrix3-start.PNG differ diff --git a/16/umbraco-cms/fundamentals/setup/install/images/WebPl/add-from-gallery.png b/16/umbraco-cms/fundamentals/setup/install/images/WebPl/add-from-gallery.png new file mode 100644 index 00000000000..eeadc0a3d48 Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/install/images/WebPl/add-from-gallery.png differ diff --git a/16/umbraco-cms/fundamentals/setup/install/images/WebPl/complete.png b/16/umbraco-cms/fundamentals/setup/install/images/WebPl/complete.png new file mode 100644 index 00000000000..71b4bb4bbbd Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/install/images/WebPl/complete.png differ diff --git a/16/umbraco-cms/fundamentals/setup/install/images/WebPl/db-step1.png b/16/umbraco-cms/fundamentals/setup/install/images/WebPl/db-step1.png new file mode 100644 index 00000000000..1c01b42223b Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/install/images/WebPl/db-step1.png differ diff --git a/16/umbraco-cms/fundamentals/setup/install/images/WebPl/db-step2.png b/16/umbraco-cms/fundamentals/setup/install/images/WebPl/db-step2.png new file mode 100644 index 00000000000..19fe74b991d Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/install/images/WebPl/db-step2.png differ diff --git a/16/umbraco-cms/fundamentals/setup/install/images/WebPl/db-type.png b/16/umbraco-cms/fundamentals/setup/install/images/WebPl/db-type.png new file mode 100644 index 00000000000..44f2d5eabf8 Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/install/images/WebPl/db-type.png differ diff --git a/16/umbraco-cms/fundamentals/setup/install/images/WebPl/download-files.png b/16/umbraco-cms/fundamentals/setup/install/images/WebPl/download-files.png new file mode 100644 index 00000000000..680395ed6f7 Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/install/images/WebPl/download-files.png differ diff --git a/16/umbraco-cms/fundamentals/setup/install/images/WebPl/search-umbraco.png b/16/umbraco-cms/fundamentals/setup/install/images/WebPl/search-umbraco.png new file mode 100644 index 00000000000..e24d4ab579e Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/install/images/WebPl/search-umbraco.png differ diff --git a/16/umbraco-cms/fundamentals/setup/install/images/WebPl/warning.png b/16/umbraco-cms/fundamentals/setup/install/images/WebPl/warning.png new file mode 100644 index 00000000000..72f22d11b5d Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/install/images/WebPl/warning.png differ diff --git a/16/umbraco-cms/fundamentals/setup/install/images/WebPl/web-db.png b/16/umbraco-cms/fundamentals/setup/install/images/WebPl/web-db.png new file mode 100644 index 00000000000..be216c66262 Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/install/images/WebPl/web-db.png differ diff --git a/16/umbraco-cms/fundamentals/setup/install/images/WebPl/web-done.png b/16/umbraco-cms/fundamentals/setup/install/images/WebPl/web-done.png new file mode 100644 index 00000000000..5df0672bfca Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/install/images/WebPl/web-done.png differ diff --git a/16/umbraco-cms/fundamentals/setup/install/images/WebPl/web-install.png b/16/umbraco-cms/fundamentals/setup/install/images/WebPl/web-install.png new file mode 100644 index 00000000000..170c569fb26 Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/install/images/WebPl/web-install.png differ diff --git a/16/umbraco-cms/fundamentals/setup/install/images/WebPl/web-start.png b/16/umbraco-cms/fundamentals/setup/install/images/WebPl/web-start.png new file mode 100644 index 00000000000..3326f300736 Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/install/images/WebPl/web-start.png differ diff --git a/16/umbraco-cms/fundamentals/setup/install/images/WebPl/web-starter.png b/16/umbraco-cms/fundamentals/setup/install/images/WebPl/web-starter.png new file mode 100644 index 00000000000..a32e55d9d4d Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/install/images/WebPl/web-starter.png differ diff --git a/16/umbraco-cms/fundamentals/setup/install/images/WebPl/web-user.png b/16/umbraco-cms/fundamentals/setup/install/images/WebPl/web-user.png new file mode 100644 index 00000000000..16e19cf1d93 Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/install/images/WebPl/web-user.png differ diff --git a/16/umbraco-cms/fundamentals/setup/install/images/WebPl/website.png b/16/umbraco-cms/fundamentals/setup/install/images/WebPl/website.png new file mode 100644 index 00000000000..b578ff608be Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/install/images/WebPl/website.png differ diff --git a/16/umbraco-cms/fundamentals/setup/install/images/iis-module.png b/16/umbraco-cms/fundamentals/setup/install/images/iis-module.png new file mode 100644 index 00000000000..b139dcb4804 Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/install/images/iis-module.png differ diff --git a/16/umbraco-cms/fundamentals/setup/install/images/iis-site.png b/16/umbraco-cms/fundamentals/setup/install/images/iis-site.png new file mode 100644 index 00000000000..dd07e16fb94 Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/install/images/iis-site.png differ diff --git a/16/umbraco-cms/fundamentals/setup/install/images/installer.png b/16/umbraco-cms/fundamentals/setup/install/images/installer.png new file mode 100644 index 00000000000..2c109064317 Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/install/images/installer.png differ diff --git a/16/umbraco-cms/fundamentals/setup/install/images/launchprofiles.png b/16/umbraco-cms/fundamentals/setup/install/images/launchprofiles.png new file mode 100644 index 00000000000..7cc99dd19c5 Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/install/images/launchprofiles.png differ diff --git a/16/umbraco-cms/fundamentals/setup/install/images/unattended/VS-unattended-install.png b/16/umbraco-cms/fundamentals/setup/install/images/unattended/VS-unattended-install.png new file mode 100644 index 00000000000..058af6bad78 Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/install/images/unattended/VS-unattended-install.png differ diff --git a/16/umbraco-cms/fundamentals/setup/install/images/unattended/final-screen.png b/16/umbraco-cms/fundamentals/setup/install/images/unattended/final-screen.png new file mode 100644 index 00000000000..f20cd2a606e Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/install/images/unattended/final-screen.png differ diff --git a/16/umbraco-cms/fundamentals/setup/install/images/voila.png b/16/umbraco-cms/fundamentals/setup/install/images/voila.png new file mode 100644 index 00000000000..1b094bdb556 Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/install/images/voila.png differ diff --git a/16/umbraco-cms/fundamentals/setup/install/install-umbraco-with-templates.md b/16/umbraco-cms/fundamentals/setup/install/install-umbraco-with-templates.md new file mode 100644 index 00000000000..c17d00ba4eb --- /dev/null +++ b/16/umbraco-cms/fundamentals/setup/install/install-umbraco-with-templates.md @@ -0,0 +1,156 @@ +# Install using .NET CLI + +We have made custom Umbraco templates that are available for use with `dotnet new`. The steps below will demonstrate the minimum amount of actions required to get you going and set up an Umbraco project from the command line using .NET templates. + +## Video Tutorial + +{% embed url="https://www.youtube-nocookie.com/embed/ZByL3qILNnI" %} +Video Tutorial +{% endembed %} + +## Install the template + +1. Install the latest [.NET SDK](https://dotnet.microsoft.com/download). +2. Run `dotnet new install Umbraco.Templates` to install the project templates. + _The solution is packaged up into the NuGet package [Umbraco.Templates](https://www.nuget.org/packages/Umbraco.Templates) and can be installed into the dotnet CLI_. + +```cli +Templates Short Name Language Tags +------------------------------------------------------------------------------------------------------ +Umbraco Project umbraco [C#] Web/CMS/Umbraco +Umbraco Extension umbraco-extension [C#] Web/CMS/Umbraco/Extension/Plugin/Razor Class Library +Umbraco Docker Compose umbraco-compose Web/CMS/Umbraco +``` + +{% hint style="info" %} +In some cases the templates may silently fail to install (usually this is an issue with NuGet sources). If this occurs you can try specifying the NuGet source in the command by running `dotnet new install Umbraco.Templates --nuget-source "https://api.nuget.org/v3/index.json"`. +{% endhint %} + +To get **help** on a project template with `dotnet new` run the following command: + +`dotnet new umbraco -h` + +From that command's output, you will get a better understanding of what are the default template options, as well as those command-line flags specific to Umbraco that you can use (as seen below): + +``` +Umbraco Project (C#) +Author: Umbraco HQ +Description: An empty Umbraco project ready to get started. + +Usage: + dotnet new umbraco [options] [template options] + +Options: + -n, --name The name for the output being created. If no name is specified, the name of the output directory is used. + -o, --output Location to place the generated output. + --dry-run Displays a summary of what would happen if the given command line were run if it would result in a template + creation. + --force Forces content to be generated even if it would change existing files. + --no-update-check Disables checking for the template package updates when instantiating a template. + --project The project that should be used for context evaluation. + -lang, --language Specifies the template language to instantiate. + --type Specifies the template type to instantiate. + +Template options: + -r, --release The Umbraco release to use, either latest or latest long term supported + Type: choice + Latest The latest umbraco release + LTS The most recent long term supported version + Default: Latest + --use-https-redirect Adds code to Startup.cs to redirect HTTP to HTTPS and enables the UseHttps setting. + Type: bool + Default: false + -da, --use-delivery-api Enables the Delivery API + Type: bool + Default: false + --add-docker Adds a docker file to the project. + Type: bool + Default: false + --no-restore If specified, skips the automatic restore of the project on create. + Type: bool + Default: false + --exclude-gitignore Whether to exclude .gitignore from the generated template. + Type: bool + Default: false + --minimal-gitignore Whether to only include minimal (Umbraco specific) rules in the .gitignore. + Type: bool + Default: false + --connection-string Database connection string used by Umbraco. + Type: string + --connection-string-provider-name Database connection string provider name used by Umbraco. + Type: string + Default: Microsoft.Data.SqlClient + --development-database-type Database type used by Umbraco for development. + Type: choice + None Do not configure a database for development. + SQLite Use embedded SQLite database. + LocalDB Use embedded LocalDB database (requires SQL Server Express with Advanced + Services). + Default: None + --friendly-name Used to specify the name of the default admin user when using unattended install on + development (stored as plain text). + Type: string + --email Used to specify the email of the default admin user when using unattended install on + development (stored as plain text). + Type: string + --password Used to specify the password of the default admin user when using unattended install on + development (stored as plain text). + Type: string + --no-nodes-view-path Path to a custom view presented with the Umbraco installation contains no published + content. + Type: string + -dm, --development-mode Choose the development mode to use for the project. + Type: choice + BackofficeDevelopment Enables backoffice development, allowing you to develop from + within the backoffice, this is the default behaviour. + IDEDevelopment Configures appsettings.Development.json to Development runtime + mode and SourceCodeAuto models builder mode, and configures appsettings.json to + Production runtime mode, Nothing models builder mode, and enables UseHttps + Default: BackofficeDevelopment + -mm, --models-mode Choose the models builder mode to use for the project. When development mode is set to + IDEDevelopment this only changes the models builder mode appsetttings.development.json + Type: choice + Default Let DevelopmentMode determine the models builder mode. + InMemoryAuto Generate models in memory, automatically updating when a content + type change, this means no need for app rebuild, however models are only available in + views. + SourceCodeManual Generate models as source code, only updating when requested + manually, this means a interaction and rebuild is required when content type(s) change, + however models are available in code. + SourceCodeAuto Generate models as source code, automatically updating when a + content type change, this means a rebuild is required when content type(s) change, + however models are available in code. + Nothing No models are generated, this is recommended for production assuming + generated models are used for development. + Default: Default + -sk, --starter-kit Choose a starter kit to install. + Type: choice + None No starter kit. + Umbraco.TheStarterKit The Umbraco starter kit. + Default: None +``` + +## Create an Umbraco project + +1. Create a new empty Umbraco solution:\ + `dotnet new umbraco -n MyCustomUmbracoProject` + +You will now have a new project with the name _MyCustomUmbracoProject_, or the name you chose to use. The new project can be opened and run using your favorite IDE or you can continue using the CLI commands. + +{% hint style="info" %} +If you want to create a solution file as well you can run the commands below.\ +`dotnet new sln`\ +`dotnet sln add MyCustomUmbracoProject` +{% endhint %} + +## Run Umbraco + +1. Navigate to the newly created project folder:\ + `cd MyCustomUmbracoProject` +2. Build and run the new Umbraco .Net Core project:\ + `dotnet build`\ + `dotnet run` + +The project is now running on the [Kestrel server](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/servers/?view=aspnetcore-5.0\&tabs=windows#kestrel) and has assigned a free available port to run it on. Look in the terminal window after the `dotnet run` command to see the URLs. + +The next step is to run through the Umbraco CMS installation. If you chose to use MS SQL Server/Azure you will need to add your connection string during this setup process to get access to the Umbraco backoffice. diff --git a/16/umbraco-cms/fundamentals/setup/install/install-umbraco-with-vs-code.md b/16/umbraco-cms/fundamentals/setup/install/install-umbraco-with-vs-code.md new file mode 100644 index 00000000000..aed7b20a512 --- /dev/null +++ b/16/umbraco-cms/fundamentals/setup/install/install-umbraco-with-vs-code.md @@ -0,0 +1,81 @@ +# Install using Visual Studio Code + +Follow these steps to set up an Umbraco project with VS Code. The benefit of using VS Code is that it is super quick to get up and running. + +## Installing and setting up VS Code + +1. Go to [https://code.visualstudio.com/](https://code.visualstudio.com/) and download VS Code for free. +2. Once installed, launch VS Code. +3. Click the extensions menu at the bottom on the left side. Then search for **C#** and install it. + + ![VS Code install extension](../../../.gitbook/assets/Marketplace.jpg) + +## Creating your Umbraco project + +Follow the [Templates Guide](install-umbraco-with-templates.md) to create your project folder. + +## Configure VS Code to run the Umbraco project + +Open your project folder in VS Code, your project will look something like this: + +![Fresh Umbraco installation](../../../.gitbook/assets/VS_Code_Explorer.png) + +Now we need to tell VS Code how to run your project. + +Open the command palette, you can use the shortcut `Ctrl+Shift+P`, and type in `Tasks: Configure` and select the `Tasks: Configure Task` option: + +![Configure task option](../../../../../10/umbraco-cms/fundamentals/setup/install/images/VsCode/ConfigureTask.png) + +Select "Create task.json from template" + +![Create task from template](../../../../../10/umbraco-cms/fundamentals/setup/install/images/VsCode/TaskJsonFromTemplate.png) + +Now select ".NET Core" as your template. + +![Create .NET Core Template](../../../../../10/umbraco-cms/fundamentals/setup/install/images/VsCode/NetcoreTemplate.png) + +After this VS Code will have created a folder called `.vscode` that contains a file called `tasks.json`, it's this file that tells VS Code how to build your project. + +Now that we've told VS Code how to build your project, we need to tell it how to launch it. VS Code can do this for you. First, select the little play button in the left side menu, and then select the "create a launch.json file" link. + +![Creating launch.json file](../../../.gitbook/assets/Create_LaunchJson_file.jpg) + +This will prompt a menu to appear, select **.NET 5+ and .NET Core**: + +![Prompt Menu](../../../../../10/umbraco-cms/fundamentals/setup/install/images/VsCode/Prompt_Menu.jpg) + +{% hint style="info" %} +If **.NET 5+ and .NET Core** is missing in the drop-down menu: + +1. Press **Ctrl + Shift + P** (on Windows/Linux) or **Cmd + Shift + P** (on macOS) to open the Command Palette. +2. Search for the command `.NET: Generate Assets for Build and Debug`. +This command will generate the necessary assets for building and debugging your .NET application. +{% endhint %} + +Now you'll see a green play button appear with a dropdown where ".NET Core Launch (web)" is selected. + +![Green play button options](../../../../../10/umbraco-cms/fundamentals/setup/install/images/VsCode/Dropdown_option.jpg) + +If you navigate to the Files section, a new `launch.json` file is created in the `.vscode` folder. When you press F5, the `launch.json` file tells VS Code to build your project, run it, and then open a browser . + +![launch.json file](../../../../../10/umbraco-cms/fundamentals/setup/install/images/VsCode/launchJson.jpg) + +With that, you're ready to run the project! Press F5, or click the little green play button in the **Run and Debug** section to run your brand new Umbraco site locally. + +## Umbraco Web Installer + +This section continues from where we left off but covers the installation and configuration of Umbraco inside your web browser when you run Umbraco for the first time. + +You will see the install screen where you will need to fill in some data before Umbraco can be installed. + +![Web Installer - Lets Get Started](../../../.gitbook/assets/Install_Umbraco.jpg) + +When the installer is done, you will be logged into the backoffice. + +
+ +Congratulations, you have installed an Umbraco site! + +{% hint style="info" %} +You can log into your Umbraco site by entering the following into your browser: http://yoursite.com/umbraco/. +{% endhint %} diff --git a/16/umbraco-cms/fundamentals/setup/install/installing-nightly-builds.md b/16/umbraco-cms/fundamentals/setup/install/installing-nightly-builds.md new file mode 100644 index 00000000000..bb9b2dfd6af --- /dev/null +++ b/16/umbraco-cms/fundamentals/setup/install/installing-nightly-builds.md @@ -0,0 +1,154 @@ +--- +description: Instructions on installing nightly builds of Umbraco. +--- + +# Installing Nightly Builds + +In this article, we'll explain how you can get the latest builds of Umbraco. You can do this in three steps: + +1. [Adding the nightly feed as a NuGet source](installing-nightly-builds.md#adding-the-nightly-feed-as-a-nuget-source) +2. [Finding the latest nightly version](installing-nightly-builds.md#finding-the-latest-nightly-version) +3. [Installing the latest nightly version template](installing-nightly-builds.md#installing-the-latest-nightly-version-template) + +## Adding the nightly feed as a NuGet source + +The NuGet feed containing the nightly builds is `https://www.myget.org/F/umbraconightly/api/v3/index.json`. + +You can either add this feed through the command line or use an IDE of your choice. In this article, we'll provide steps for: + +* [Using the command line](installing-nightly-builds.md#option-1-using-the-command-line) +* [Using Visual Studio](installing-nightly-builds.md#option-2-using-visual-studio) +* [Using Rider](installing-nightly-builds.md#option-3-using-rider) + +### Option 1: Using the command line + +To add the nightly feed using the command line: + +1. Open a command prompt of your choice. +2. Run the following command: + +``` +dotnet nuget add source "https://www.myget.org/F/umbraconightly/api/v3/index.json" -n "Umbraco Nightly" +``` + +Now the feed is added as a source named `Umbraco Nightly`. + +### Option 2: Using Visual Studio + +To add the nightly feed using Visual Studio: + +1. Open Visual Studio. +2. Go to **Tools** > **NuGet Package Manager** > **Package Manager Settings**. + +![Package Manager Settings](<../../../.gitbook/assets/Package-Manager-Settings (1) (2).jpg>) + +3. The **Options** window open. +4. Select the **Package Sources** option in the **NuGet Package Manager** section. +5. Click the `+` icon. +6. A new Package source will be added automatically and highlighted. +7. Enter the desired name for the feed in the **Name** field. +8. Enter the link `https://www.myget.org/F/umbraconightly/api/v3/index.json` into the **Source** field. +9. Click **OK**. + +![Register the nightly feed](../../../.gitbook/assets/Register\_Nightly\_Feed.jpg) + +Now the feed is added as a source named `Umbraco Nightly`. + +### Option 3: Using Rider + +To add the nightly feed using Rider: + +1. Open Rider. +2. Go to **View** > **Tool Windows** > **NuGet**. +3. Go to **Sources** tab. +4. Choose the `C:\Users\Úmbraco\AppData\Roaming\NuGet\NuGet.Config` to add the feed globally. +5. Cick the green `+` button in the **New Feed** field. + +![Open the new feed menu](../../../.gitbook/assets/NuGet\_NewFeed.jpg) + +6. The New feed dialog opens. +7. Enter the desired name in the **Name** field. +8. Enter `https://www.myget.org/F/umbraconightly/api/v3/index.json` in the URL field. + +{% hint style="info" %} +Leave the **User, Password** fields empty, and the **Enabled** checkbox ticked. +{% endhint %} + +![Adding the feed](../../../.gitbook/assets/NewFeed\_Details.jpg) + +9. Click **OK**. + +Now the feed is added as a source named `Umbraco Nightly`. + +## Finding the latest nightly version + +In the previous steps, we've added the feed and are now ready to install the nightly build. + +However, which version should we install? This is, in particular, an issue if we want to create a new site using the dotnet template. The dotnet command does not allow us to use wildcard characters to install the newest version. + +Using IDE, we can see a list of available versions in both Visual Studio and Rider. We can then use that version to install the template. + +Here we're going to assume that you want to create a brand new site with the dotnet template. The approach is the same if you're updating an existing site. You'll click the **Update** button for the `Umbraco.Cms` package instead of installing the template through the terminal. + +Let's look at how we can find the latest version of the nightly build: + +* [Using Visual Studio](installing-nightly-builds.md#option-1-using-visual-studio) +* [Using Rider](installing-nightly-builds.md#option-2-using-rider) + +### Option 1: Using Visual Studio + +You can use the package manager in Visual Studio to browse the available template versions. + +1. Open Visual Studio. +2. Go to **Tools** > **NuGet Package Manager** > **Manage NuGet Packages For Solution...** + +![Opening the Nuget Package Manager](<../../../.gitbook/assets/Manage\_NuGet\_Pkgs (1).jpg>) + +3. Select **Umbraco Nightly** from the **Package source** dropdown in the **NuGet - Solution** window. + +![Select the nightly NuGet feed](../../../.gitbook/assets/Manage\_Packages.jpg) + +4. Check the **Include prerelease** checkbox. +5. Search for **Umbraco.Templates** in the **Browse** field. +6. Choose that package. +7. Click on the **Version** dropdown and see the available nightly builds. +8. Choose the applicable version and note down the version number. + +![Find the version](../../../.gitbook/assets/Latest\_nightly\_build\_version.jpg) + +### Option 2: Using Rider + +You can use the NuGet window in Rider to browse the available template versions. + +1. Open Rider. +2. Go to the **Packages** tab in the **NuGet** window.. +3. Select **Umbraco Nightly** from the **All Feeds** dropdown. + +![Choose the feed](../../../.gitbook/assets/Rider\_Nightly\_Feed.jpg) + +4. Check the **Prerelase** checkbox. +5. Search for **Umbraco.Templates** in the **Search** field. +6. Choose that package. +7. Click on the **Version** drop down and see the available nightly builds. +8. Choose the applicable version and note down the version number + +![Find the version](../../../.gitbook/assets/Rider\_Nightly\_Feed\_version.jpg) + +## Installing the latest nightly version template + +Now that our feed is added and we know the exact version we're ready to install our template. + +To install the latest nightly version template: + +1. Open your command prompt. +2. Run the following command using the latest version: + +```bash +dotnet new install Umbraco.Templates +``` + +With that, we've successfully installed the latest nightly build of Umbraco. + +All we have to do now is to create a site using the `dotnet new umbraco -n MyAwesomeNightlySite` command. + +For more information about installing Umbraco, see the [Installation](./) article. diff --git a/16/umbraco-cms/fundamentals/setup/install/running-umbraco-on-docker-locally.md b/16/umbraco-cms/fundamentals/setup/install/running-umbraco-on-docker-locally.md new file mode 100644 index 00000000000..7df00c825dd --- /dev/null +++ b/16/umbraco-cms/fundamentals/setup/install/running-umbraco-on-docker-locally.md @@ -0,0 +1,100 @@ +--- +description: "Running Umbraco on docker locally using docker compose" +--- + +# Running Umbraco in Docker using Docker Compose + +To aid in developing Umbraco with additional services, the templates can provide the requisite files to run Umbraco with an SQL Server database in Docker. This setup is designed to be used for local development, and not for production. + +## Prerequisites + +Before you can run Umbraco in Docker, you need to have the following installed: +* Version 14.3.0 or higher of the Umbraco Templates +* Docker Desktop + +## Installing + +Installing Umbraco with the Docker file and Docker Compose file is a two-step process. + +1. Create a folder to hold your project and enter that folder. + +```bash +mkdir MyDockerProject +cd MyDockerProject +``` +2. Create your Umbraco project using the Umbraco Templates, and remember to use the `--add-docker` flag to include the Docker files. + + +Conventionally this is named the same as the folder, but it is not a requirement. + +```bash +dotnet new umbraco -n MyDockerProject --add-docker +``` + +Now we need to add some additional files to make docker compose work. We can do this using the umbraco-compose template, passing the project name we specified earlier to the -P parameter: + +```bash +dotnet new umbraco-compose -P "MyDockerProject" +``` + +The `-P` flag is required to specify the correct paths in the docker-compose file. The project is now ready to run with docker compose. + +The final folder structure looks like this: + +* MyDockerProject + * MyDockerProject + * Typical project files + * DockerFile + * `.dockerignore` + * `.env` + * Database + * DockerFile + * `healthcheck.sh` + * `setup.sql` + * `startup.sh` + * `docker-compose.yml` + +The project now includes docker files for both Umbraco and the SQL server database. + +It also includes additional scripts to launch and configure the database and a `.env` file with the database password. + +## Running + +To run the project use the `docker compose up` command in the root of the project files where the `docker-compose.yml` is. + +This command will build both the Umbraco and SQL Server images and launch them in the correct order. When the site is booted, access it in your browser on `http://localhost:44372/`. + +### Useful commands + +There are some useful commands you can use to manage the docker containers: + +* `docker compose down --volumes`: Delete your containers and the volumes they use. This is useful if you want to start from scratch. + +{% hint style="warning" %} +Be careful with this command, as it deletes your database and all data in it. +{% endhint %} + +* `docker compose up --build`: Rebuild the images and start the containers. This is useful if you have made changes to the project and want to see them reflected on the running site. +* `docker compose watch`: Start the containers and watch the default models folder. This means that if the project uses a source-code models builder the images are automatically rebuilt and restarts when you change the models. + +## Details + +The docker compose file uses bind mounts for the following folders: + +* `/wwwroot/media` +* `/wwwroot/scripts` +* `/wwwroot/css` +* `/Views` +* `/models` + +This is not meant to be used in production. + +For local development, however, this means that the files necessary for development are available from outside the container in your IDE. This allows development even though the project is running in docker. + +## Template options + +The `umbraco-compose` template has a few options that can be used to customize the setup: + +* `-P` or `--project-name`: The name of the project. This is required and used to set the correct paths in the docker-compose file. +* `-dbpw` or `--DatabasePassword`: Used to specify the database password. This is stored in the `.env` file and defaults to: `Password1234`. +* `-p` or `--Port`: Used to specify the port the site will run on. Defaults to `44372`. diff --git a/16/umbraco-cms/fundamentals/setup/install/running-umbraco-on-linux-macos.md b/16/umbraco-cms/fundamentals/setup/install/running-umbraco-on-linux-macos.md new file mode 100644 index 00000000000..787a0ffb0f0 --- /dev/null +++ b/16/umbraco-cms/fundamentals/setup/install/running-umbraco-on-linux-macos.md @@ -0,0 +1,30 @@ +--- +description: "Since Umbraco 9 it has been possible to run Umbraco CMS natively on Linux or macOS High Sierra 10.13 and newer." +--- + +# Running Umbraco on Linux or macOS + +With Umbraco CMS on .NET Core, Linux and macOS is natively supported with SQLite as the database. + +In the below section, we describe how to get started with running Umbraco CMS on Linux or macOS. + +## How to get started running Umbraco CMS on Linux or macOS + +To get started with Umbraco CMS first have a look at the [requirements for running Umbraco CMS](../requirements.md#local-development). + +Once you've made sure you meet the requirements it is time to install the Umbraco Templates on your system. + +To do this follow the [Install using .NET CLI](install-umbraco-with-templates.md#install-the-template) guide. + +With the templates installed on your system, it is now possible to create Umbraco projects. + +To create a project, there are two options: + +- Continue creating projects using the .NET CLI. +- Create new projects using Visual Studio (only macOS). + +To create new projects using Visual Studio, you can use the [Install using Visual Studio](visual-studio.md) guide. + +Once you create a new project it will use SQLite by default on Linux/macOS. + +If you want to use an SQL server database, you will need to [set up Docker](https://skrift.io/issues/umbraco-and-docker-part-1-getting-familiar-with-containers/). diff --git a/16/umbraco-cms/fundamentals/setup/install/unattended-install.md b/16/umbraco-cms/fundamentals/setup/install/unattended-install.md new file mode 100644 index 00000000000..0be7a2e91bc --- /dev/null +++ b/16/umbraco-cms/fundamentals/setup/install/unattended-install.md @@ -0,0 +1,144 @@ +# Unattended Installs + +In some cases, you might need to install Umbraco instances automatically without having to run through the installation wizard to configure the instance. + +You can use the **Unattended installs** feature to allow for quick installation and set up of Umbraco instances on something like Azure Web Apps. + +This article will give you the details you need to install Umbraco unattended. + +## Get clean install of Umbraco + +In order to get a clean instance of Umbraco, follow our installation guide for how to [Install an Umbraco project template](install-umbraco-with-templates.md#install-umbraco-with-net-cli). + +## Configure your database + +As you will not be running through the installation wizard when using this feature, you need to manually tell Umbraco which database to use. + +* Set up and configure a new database - see [Requirements](../requirements.md#hosting) for details. +* Add the connection string using configuration. + +{% hint style="info" %} +Umbraco can create an SQL Server database for you during the unattended install process. The user specified by the credentials in your connection string needs to have the `CREATE DATABASE` permission granted and the global setting [InstallMissingDatabase](../../../reference/configuration/globalsettings.md#install-missing-database) is set to `true`. + +If your connection string is for SQLite or SQL Server Express LocalDB it is assumed that a database should be created when missing. This is regardless of the value of the `InstallMissingDatabase` setting. +{% endhint %} + +### SQL Server Example in appsettings.json + +```json +{ + "ConnectionStrings": { + "umbracoDbDSN": "server=localhost;database=UmbracoUnicore;user id=sa;password='P@ssw0rd'", + "umbracoDbDSN_ProviderName": "System.Data.SqlClient" + } +} +``` + +{% hint style="info" %} +The 'umbracoDbDSN_ProviderName' attribute sets the .NET Framework data provider name for the DataSource control's connection. For more information on the data providers included in the .Net Framework, see the [Microsoft Documentation](https://learn.microsoft.com/en-us/dotnet/api/system.web.ui.webcontrols.sqldatasource.providername?#remarks). +{% endhint %} + +### SQLite Example in appsettings.json + +A value is configured for the key`umbracoDbDSN_ProviderName` to ensure usage of the `Microsoft.Data.SQLite` ADO.NET provider. + +It is recommended that you make use of the values shown below for the `Cache`, `Foreign Keys` and `Pooling` keywords on your connection string. + +```json +{ + "ConnectionStrings": { + "umbracoDbDSN": "Data Source=|DataDirectory|/Umbraco.sqlite.db;Cache=Private;Foreign Keys=True;Pooling=True", + "umbracoDbDSN_ProviderName": "Microsoft.Data.Sqlite" + } +} +``` + +## Enable the unattended installs feature + +The unattended installs feature is disabled by default. In order to enable it, you need to add the following JSON object to a JSON configuration source. + +```json +{ + "Umbraco": { + "CMS": { + "Unattended": { + "InstallUnattended": true, + "UnattendedUserName": "FRIENDLY_NAME", + "UnattendedUserEmail": "EMAIL", + "UnattendedUserPassword": "PASSWORD" + } + } + } +} +``` + +Remember to set the value of `InstallUnattended` to `true`. + +Alternatively you may set your configuration with Environment Variables or other means. Learn more about this in the [Microsoft .Net Core config documentation](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/?view=aspnetcore-5.0#environment-variables). + +The keys for this would then be as follows: + +``` +Umbraco__CMS__Unattended__InstallUnattended +Umbraco__CMS__Unattended__UnattendedUserName +Umbraco__CMS__Unattended__UnattendedUserEmail +Umbraco__CMS__Unattended__UnattendedUserPassword +``` + +## Initialize the unattended install + +After completing the steps above you can now initialize the installation by booting up the Umbraco instance. + +Once it has completed, you should see the following when visiting the frontend of the site. + +
+ +## Configuration options + +Depending on your preferences, you can use any type of configuration to specify the connection string and login information, as well as enable unattended install. With the extending configuration functionality, it is possible to read from all kinds of sources. One example can be using a JSON file or environment variables. + +**Program.cs** has a condition, which if met, an _appsettings.Local.json_ file will be added and configured as a configuration source. + +``` +#if DEBUG + .ConfigureAppConfiguration(config + => config.AddJsonFile( + "appsettings.Local.json", + optional: true, + reloadOnChange: true)) +#endif +``` + +Having intellisense will help you to add your connection string and information needed for the unattended install. + +```json +{ + "ConnectionStrings": { + "umbracoDbDSN": "server=localhost;database=UmbracoUnicore;user id=sa;password='P@ssw0rd'" + }, + "Umbraco": { + "CMS": { + "Unattended": { + "InstallUnattended": true, + "UnattendedUserName": "FRIENDLY_NAME", + "UnattendedUserEmail": "EMAIL", + "UnattendedUserPassword": "PASSWORD" + } + } + } +} +``` + +## More support + +We have added support for unattended installs with Name, Email and Password, and Connection String as CLI params, which are also available in Visual Studio. There you can fill in your information as follows: + +### CLI + +```powershell +dotnet new umbraco -n MyNewProject --friendly-name "Friendly User" --email user@email.com --password password1234 --connection-string "Server=(localdb)\Umbraco;Database=MyDatabase;Integrated Security=true" --version 10.0.0 +``` + +### Visual Studio + +
diff --git a/16/umbraco-cms/fundamentals/setup/install/visual-studio.md b/16/umbraco-cms/fundamentals/setup/install/visual-studio.md new file mode 100644 index 00000000000..fee530a8797 --- /dev/null +++ b/16/umbraco-cms/fundamentals/setup/install/visual-studio.md @@ -0,0 +1,77 @@ +--- +description: A guide to install Umbraco CMS using Visual Studio. +--- + +# Install using Visual Studio + +## Prerequisites + +* Install the newest [Umbraco dotnet templates](install-umbraco-with-templates.md). + * In Visual Studio 2022, the .NET CLI templates are enabled to appear, by default. For information on how to enable .NET CLI templates in Visual Studio 2019, see the [.NET CLI Templates in Visual Studio](https://devblogs.microsoft.com/dotnet/net-cli-templates-in-visual-studio/) article. +* Check the [Requirements](../requirements.md) to ensure you have everything you need to start your Umbraco project. + +## Quick Start + +This is an abbreviated version of the installation steps. Jump to the [Create a new project](visual-studio.md#new-project) section for a more thorough guide. + +* Open Visual Studio. +* Go to `File` > `New` > `Project`, search for **Umbraco**. +* Choose **Umbraco Project (Umbraco HQ)** then click **Next**. +* Choose or specify the parameters, leave the default or leave them all empty. +* Click **Create**. +* Use **CTRL+F5** to run the project and start the Umbraco installer. + +## Video Tutorial + +{% embed url="https://www.youtube.com/watch?ab_channel=UmbracoLearningBase&v=CDeAYGu_-cI" %} +How to install Umbraco using NuGet and Visual Studio +{% endembed %} + +## Create a new Umbraco project + +To install Umbraco we first need to install Umbraco's dotnet new templates. + +For more information check the first 2 steps of [Install Umbraco with .NET CLI](install-umbraco-with-templates.md#install-the-template). + +### Create the Visual Studio project + +Go to **File > New > Project** and search for `Umbraco` in the _Search for templates_ field. + +![The Create a new project dialog in Visual Studio.](../../../../../10/umbraco-cms/fundamentals/setup/install/images/VS/create-project.png) + +Once you select **Umbraco Project (Umbraco HQ)** navigate to the next step by clicking _Next_. + +### Configure project + +In this step, you will be able to give your project a name specific to the project you are creating. + +![The Configure your new project dialog in Visual Studio.](../../../.gitbook/assets/New\_Project.jpg) + +{% hint style="info" %} +Refrain from changing the Solution name, as this will cause a namespace conflict with the CMS itself. +{% endhint %} + +### Additional information + +In the next step, you are able to specify some additional parameters like the _Target framework_. The rest are optional. + +![The Additional information dialog in Visual Studio.](../../../.gitbook/assets/Additional\_Info.jpg) + +You can then click the _Create_ button and your Umbraco Project will be ready for you. + +![Overview of files in the project solution](../../../.gitbook/assets/Solution\_Explorer.png) + +### Running the site + +You can now run the site through Visual Studio using **F5** or the **Debug** button. + +Follow the installation wizard and after a few steps, you will get a message saying the installation was a success. + +## Next steps + +You are now ready to start building your Umbraco project. Have a look below for different resources on the next steps. + +* [Getting Started with Umbraco](../../../tutorials/creating-a-basic-website/getting-started.md) +* [Tutorial: Create a website from scratch](../../../tutorials/creating-a-basic-website/) +* [Find different options for hosting your Umbraco website](../server-setup/) +* [Learn about configuration in Umbraco CMS](../../../reference/configuration/) diff --git a/16/umbraco-cms/fundamentals/setup/requirements.md b/16/umbraco-cms/fundamentals/setup/requirements.md new file mode 100644 index 00000000000..48343ad4b7e --- /dev/null +++ b/16/umbraco-cms/fundamentals/setup/requirements.md @@ -0,0 +1,72 @@ +# Requirements + +## Browsers + +The Umbraco UI works in all modern browsers: + +* Chrome (Latest) +* Edge (Chromium) +* Firefox (Latest) +* Safari (Latest) + +## Local Development + +Below you can find the minimum requirements to run Umbraco 15 on your machine: + +* [.NET 9.0 and higher](https://dotnet.microsoft.com/en-us/download/dotnet/9.0) +* One of the [.NET 9 - Supported OS versions](https://github.com/dotnet/core/blob/main/release-notes/9.0/supported-os.md#net-9---supported-os-versions) +* One of the following .NET Tools or Editors: + * [Visual Studio Code](https://code.visualstudio.com/) with the [IISExpress extension](https://marketplace.visualstudio.com/items?itemName=warren-buckley.iis-express) + * [Microsoft Visual Studio](https://www.visualstudio.com/) 2022 version 17.12 or higher. + * Optional: [JetBrains Rider](https://www.jetbrains.com/rider) version 2022.3 and higher + * [.NET Core CLI](install/install-umbraco-with-templates.md) +* [SQL connection string (SQL Server)](../../reference/configuration/connectionstringssettings.md) +* [Node.js version 20.11.0](https://nodejs.org/en/download/prebuilt-installer) and higher + +{% hint style="info" %} +When using Visual Studio as your primary Integrated Development Environment (IDE) we recommend [finding and downloading the Software Development Kits (SDKs) for Visual Studio](https://dotnet.microsoft.com/en-us/download/visual-studio-sdks). +{% endhint %} + +{% hint style="info" %} +Are you using Microsoft SQL as your data? +The Umbraco Data Access Layer (DAL) does not support case-sensitive naming. +When you use Microsoft SQL as your database, ensure that the database is created using a case-insensitive (CI) collation variant. For example, `SQL_Latin1_General_CP1_CI_AS`. +Learn more about [collation modes](https://learn.microsoft.com/en-us/sql/relational-databases/collations/collation-and-unicode-support?view=sql-server-ver16) in the official Microsoft documentation. +{% endhint %} + +## Hosting + +### Recommendation requirements to run Umbraco + +As Umbraco releases are aligned to the .NET release cadence, it's also aligned with Microsoft's Long-term support policy for the underlying framework. For the best experience, we would recommend that you ensure to be on the latest and supported Microsoft versions to run and host Umbraco CMS: + +* [Windows Supported releases](https://learn.microsoft.com/en-us/dotnet/core/install/windows?tabs=net70#supported-releases) +* [MacOs Supported releases](https://learn.microsoft.com/en-us/dotnet/core/install/macos#supported-releases) +* [Ubuntu Supported distributions](https://learn.microsoft.com/en-us/dotnet/core/install/linux-ubuntu#supported-distributions) and other [Linux Packages](https://learn.microsoft.com/en-us/dotnet/core/install/linux#packages) +* [.NET Supported releases](https://dotnet.microsoft.com/en-us/platform/support/policy) +* [IIS Supported releases](https://learn.microsoft.com/en-us/lifecycle/products/internet-information-services-iis) +* [SQL Server Supported releases](https://learn.microsoft.com/en-us/sql/sql-server/end-of-support/sql-server-end-of-support-overview?view=sql-server-ver16#lifecycle-dates) +* [SQLite](https://www.sqlite.org/index.html) + +_For more information, see the_ [_Host and deploy ASP.NET Core applications_](https://learn.microsoft.com/en-us/aspnet/core/host-and-deploy/?view=aspnetcore-7.0) _article in the Microsoft documentation._ + +{% hint style="success" %} +You can use [Umbraco Cloud](https://umbraco.com/products/umbraco-cloud/) to manage the hosting infrastructure. All Umbraco Cloud plans are hosted on Microsoft Azure, which gives your site a proven and solid foundation. +{% endhint %} + +### Other recommendation + +* Ability to set file permissions to include create/read/write (or better) for the user that "owns" the Application Pool for your site. This would typically be **NETWORK SERVICE**. + +## Database Account Roles + +The database account used in the connection string will need permission to read and write from tables. It will also require permission to create schema during installs and upgrades: + +* The `db_owner` role has full permissions on the database. +* To use an account with more restricted permissions, the `db_datareader` and `db_datawriter` roles will be needed for normal use to read from and write to the database. The `db_ddladmin` role, which can modify the database schema, is required for installs and upgrades of the CMS and/or any packages that create database tables. + +For more information on the Database-level roles, see the [Microsoft documentation](https://docs.microsoft.com/en-us/sql/relational-databases/security/authentication-access/database-level-roles?view=sql-server-ver16#fixed-database-roles). + +{% hint style="info" %} +For more information on how to create a database user via SQL, you can check the [Microsoft documentation](https://learn.microsoft.com/en-us/sql/relational-databases/security/authentication-access/database-level-roles?view=sql-server-ver16#a--adding-a-user-to-a-database-level-role). +{% endhint %} diff --git a/16/umbraco-cms/fundamentals/setup/server-setup/README.md b/16/umbraco-cms/fundamentals/setup/server-setup/README.md new file mode 100644 index 00000000000..3a12b2f5893 --- /dev/null +++ b/16/umbraco-cms/fundamentals/setup/server-setup/README.md @@ -0,0 +1,31 @@ +--- +description: >- + This section describes different ways of setting up servers for use with + Umbraco +--- + +# Server setup + +## Secure Sockets Layer (SSL) and HTTPS + +We strongly encourage the use of HTTPS with Umbraco installations especially in production environments. Using HTTPS will greatly enhance the security of your website, see the [Security reference](../../../reference/security/) for more information. + +## [File & folder permissions](permissions.md) + +To ensure a stable and smoothly running Umbraco installation, these permissions need to be set correctly. + +## [Hosting v9+ in IIS](iis.md) + +Information about hosting a v9 application using IIS. + +## [Load Balanced setup](load-balancing/) + +Information on how to deploy Umbraco in a Load Balanced scenario and other details to consider when setting up Umbraco for load balancing. + +## [Running Umbraco on Azure Web Apps](azure-web-apps.md) + +Best practices for running Umbraco on Azure Web Apps. + +## [Runtime modes](runtime-modes.md) + +The runtime mode setting optimizes Umbraco for the best development experience or optimal production environment. diff --git a/16/umbraco-cms/fundamentals/setup/server-setup/azure-web-apps.md b/16/umbraco-cms/fundamentals/setup/server-setup/azure-web-apps.md new file mode 100644 index 00000000000..6bc861c805c --- /dev/null +++ b/16/umbraco-cms/fundamentals/setup/server-setup/azure-web-apps.md @@ -0,0 +1,97 @@ +# Running Umbraco On Azure Web Apps + +This section describes best practices with running Umbraco on Azure Web Apps + +## What are Azure Web Apps + +They have been called a few names in the past, many people still know Azure Web Apps as Azure Web Sites. + +> App Service is a fully Managed Platform for professional developers that brings a rich set of capabilities to web, mobile and integration scenarios. Quickly create and deploy mission critical web Apps that scale with your business by using Azure App Service. + +[You can read more about them here](https://azure.microsoft.com/en-us/products/app-service/web) + +Umbraco will run on Azure Web Apps but there are some configuration options and specific Azure Web Apps environment limitations to be aware of. + +## Recommended configuration + +You need to add these configuration values. E.g in a json configuration source like `appSettings.json`: + +```json +{ + "Umbraco": { + "CMS": { + "Global": { + "MainDomLock" : "FileSystemMainDomLock" + }, + "Hosting": { + "LocalTempStorageLocation": "EnvironmentTemp" + }, + "Examine": { + "LuceneDirectoryFactory": "SyncedTempFileSystemDirectoryFactory" + } + } + } +} +``` + +You can also copy the following JSON directly into your Azure Web App configuration via the Advanced Edit feature. + +![image](https://github.com/umbraco/UmbracoDocs/assets/11179749/ae53a26b-c45a-4b71-932a-0682f3d264a8) + +```json +{ + "name": "UMBRACO__CMS__Global__MainDomLock", + "value": "FileSystemMainDomLock", + "slotSetting": false +}, +{ + "name": "UMBRACO__CMS__Hosting__LocalTempStorageLocation", + "value": "EnvironmentTemp", + "slotSetting": false +}, +{ + "name": "UMBRACO__CMS__Examine__LuceneDirectoryFactory", + "value": "SyncedTempFileSystemDirectoryFactory", + "slotSetting": false +} +``` + +Remember to add an `ASPNETCORE_ENVIRONMENT` variable with values `Development`, `Staging`, or `Production`. + +The minimum recommended Azure SQL Tier is "S2", however noticeable performance improvements are seen in higher Tiers + +If you are load balancing or require the scaling ("scale out") ability of Azure Web Apps then you need to consult the [Load Balancing documentation](load-balancing/). This is due to the fact that a lot more needs to be configured to support scaling/auto-scaling. + +## Storage + +It is important to know that Azure Web Apps uses a remote file share to host the files to run your website. This is due to the files running your website do not exist on the machine running your website. In many cases this isn't an issue. It can become one if you have a large amount of IO operations running over remote file-share. + +## Issues with read-only filesystems + +Although Umbraco can be configured to use environmental storage it still requires its working-directory to be writable. If Umbraco is deployed to a read-only file system it will [fail to boot](https://github.com/umbraco/Umbraco-CMS/issues/12043). + +For example, Azure's [Run from Package feature](https://docs.microsoft.com/en-us/azure/app-service/deploy-run-package) is not supported by Umbraco. To check if your web app is using this feature you can check the `WEBSITE_RUN_FROM_PACKAGE` environment variable. + +## Scaling + +If you require the scaling ("scale out") ability of Azure Web Apps you need to consult the [Load Balancing documentation](load-balancing/). This is due to the fact that a lot more needs to be configured to support scaling/auto-scaling. + +## Web worker migrations + +It's important to know that Azure Web Apps may move your website between their 'workers' at any given time. This is normally a transparent operation. In some cases you may be affected by it if any of your code or libraries use the following variables: + +* `Environment.MachineName` (or equivalent) + +When your site is migrated to another worker, these variables will change. You cannot rely on these variables remaining static for the lifetime of your website. + +### How to find the Linux App Service Logs + +The quickest way to get to your logs is using the following URL template and replacing `{app}` with your Web App name: + +`https://{app}.scm.azurewebsites.net/api/logstream` + +You can also find this in the KUDU console by clicking **Advanced Tools** > **Log Stream** on the Web App in the Azure Portal. + +## Web App secret management + +Consult the [Azure Key Vault documentation](../../../extending/key-vault.md) if you would like to directly reference Azure Key Vault Secrets to your Azure Web App. diff --git a/16/umbraco-cms/fundamentals/setup/server-setup/iis.md b/16/umbraco-cms/fundamentals/setup/server-setup/iis.md new file mode 100644 index 00000000000..650140ddbbe --- /dev/null +++ b/16/umbraco-cms/fundamentals/setup/server-setup/iis.md @@ -0,0 +1,167 @@ +--- +description: Information on hosting Umbraco on IIS +--- + +# Hosting Umbraco in IIS + +## Install .NET Core Runtime + +* Download the .NET Core Runtime from the [Download .NET page](https://dotnet.microsoft.com/en-us/download/dotnet/). +* Ensure you download the version that matches your project’s requirements, which can be found in the [requirements](../requirements.md) article. + +## Install the Hosting Bundle + +Download the [ASP.NET Core Runtime Hosting Bundle](https://dotnet.microsoft.com/en-us/download/dotnet/) to enable IIS to run ASP.NET Core applications. + +## Restart IIS + +After installing .NET Core Runtime, restart IIS to ensure the changes are applied. + +1. Open Command Prompt as Administrator. +2. Run the following commands: + +```bash +net stop was /y +net start w3svc +``` + +## Create the Site in IIS + +Next, create a new site in IIS for hosting Umbraco. + +### Set Application Pool + +1. Open IIS Manager. +2. Right-click on **Application Pools** in the **Connections** panel. +3. Select **Add Application Pool**. +4. Enter the Name. +5. Ensure the .NET Common Language Runtime (CLR) version is set to **No Managed Code**. +6. Click **Ok**. + +![IIS Application Pool](images/iis-app-pool-core-v15.png) + +### Create a Site + +1. Open IIS Manager. +2. Right-click on **Sites** in the **Connections** panel. +3. Select **Add Website**. +4. Fill in the **Site Name**, **Physical Path** (to your Umbraco installation), and the **Port**. +5. Click **Ok**. + +![IIS Creating Site](images/create-site-in-iis.png) + +### Publish the Website + +You can either use the dotnet CLI or Visual Studio to publish your Umbraco website. + +#### Option 1: Use dotnet CLI for Manual Deployment + +1. Open the Command Prompt in the directory of your Umbraco project. +2. Run the following command: + +```bash +dotnet publish -o ../deployment-artefacts -f net9.0 +``` + +{% hint style="info" %} +Make sure to replace net9.0 with the version of .NET you are using. +{% endhint %} + +![Using dotnet CLI for Manual Deployment](images/dotnet-cli-command.png) + +#### Option 2: Use Visual Studio to Deploy + +1. Open your project in Visual Studio. +2. Right-click on the Umbraco project in Solution Explorer. +3. Select **Publish...**. + +![Using Visual Studio to Deploy](images/visual-studio-deploy.png) + +{% hint style="info" %} +To deploy Umbraco to IIS via Azure DevOps, you can use the [IIS Release task in Azure DevOps](https://learn.microsoft.com/en-us/azure/devops/pipelines/release/deploy-webdeploy-iis-deploygroups). This task is a wrapper for `MSDeploy.exe` and can be configured as preferred. +{% endhint %} + +### Configure Environment Variables + +In IIS, you can configure environment variables for Umbraco to store sensitive data such as your connection string. + +#### Configure Environment Variables in IIS + +1. Open IIS Manager and select your Umbraco site. +2. Double-click **Configuration Editor** in the **Management** section. + +![IIS Website Configuration](images/iis-core-website-config-v15.png) + +3. Select `system.webServer/aspNetCore` in the **Section** dropdown. +4. Select `ApplicationHost.config ` in the **From** dropdown. + +This ensures your settings will be stored in a machine specific file. The configuration files might end in a public repository and should not contain sensitive data like Connection Strings or Simple Mail Transfer Protocol (SMTP) credentials. Additionally, by default the configuration file will be overwritten during each publish processes. + +5. Locate the **environmentVariables** line and open the dialog to add environment variables. +6. Enter the values for `ASPNETCORE_ENVIRONMENT` and `ConnectionStrings__umbracoDbDSN`. + +{% hint style="info" %} +* Variable names need to change the object structure form JSON by combining the segments with double underscore `__` For example: `ConnectionStrings__umbracoDbDSN` +* Escaped backslashes `\\` For example: `serverName\\databaseInstanceName` are replaced by single backslash `\` (`DATABASESERVER123\SQL2017`) +{% endhint %} + +7. Click **Add**. +8. Click **Apply**. + +![IIS Configuration Editor](images/iis-environment-variables-v15.png) + +9. Restart IIS to apply the changes after updating the environment variables. + +### IIS Hosting Models + +IIS can host .NET applications using two different hosting models: In-process (default) and Out-of-process. + +#### In-process Hosting (Default) + +* [In-process hosting](https://docs.microsoft.com/en-us/aspnet/core/host-and-deploy/iis/in-process-hosting) runs the .NET application within the same process as the IIS worker process. +* This is the default hosting model and is more efficient, as there is no inter-process communication between IIS and the application. + +#### Default Configuration + +No changes are required to your application configuration for in-process hosting. It works automatically with the default IIS settings. + +#### Out-of-process Hosting + +* [Out-of-process hosting](https://docs.microsoft.com/en-us/aspnet/core/host-and-deploy/iis/out-of-process-hosting) allows the .NET application to run separately from the IIS worker process. IIS acts as a reverse proxy to the Kestrel server, which hosts the .NET application. +* This model is useful when you need to run a .NET application in a separate process, for example, when the application requires more isolated resources or needs to scale independently from IIS. + +#### Configuration + +To enable out-of-process hosting: + +1. Open your Umbraco project. +2. Locate the `.csproj` file. +3. Add the following line: + +```xml + + OutOfProcess + +``` + +4. Save the `.csproj` file. +5. Rebuild your application. + +### Troubleshooting: Handling WebDAV Module Interference + +**Issue:** A 405 error may occur when attempting to save content items in Umbraco, due to the WebDAV module blocking PUT requests. + +**Solution:** If you encounter this issue, it may be because the WebDAV module in IIS is blocking the necessary PUT requests. This issue is not present on all IIS setups, but if you do have WebDAV installed, try disabling the WebDAV module. + +**Workaround:** + +1. Open the `web.config` file. +2. Add the following code to remove the WebDAV module: + +```xml + + + +``` + +3. Restart IIS. diff --git a/16/umbraco-cms/fundamentals/setup/server-setup/images/contextmenu-publish-command.jpg b/16/umbraco-cms/fundamentals/setup/server-setup/images/contextmenu-publish-command.jpg new file mode 100644 index 00000000000..5e4c10665cf Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/server-setup/images/contextmenu-publish-command.jpg differ diff --git a/16/umbraco-cms/fundamentals/setup/server-setup/images/create-site-in-iis.png b/16/umbraco-cms/fundamentals/setup/server-setup/images/create-site-in-iis.png new file mode 100644 index 00000000000..1a2d0659547 Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/server-setup/images/create-site-in-iis.png differ diff --git a/16/umbraco-cms/fundamentals/setup/server-setup/images/dotnet-cli-command.png b/16/umbraco-cms/fundamentals/setup/server-setup/images/dotnet-cli-command.png new file mode 100644 index 00000000000..45619a693f8 Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/server-setup/images/dotnet-cli-command.png differ diff --git a/16/umbraco-cms/fundamentals/setup/server-setup/images/iis-app-pool-core-v15.png b/16/umbraco-cms/fundamentals/setup/server-setup/images/iis-app-pool-core-v15.png new file mode 100644 index 00000000000..3be0fd98be8 Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/server-setup/images/iis-app-pool-core-v15.png differ diff --git a/16/umbraco-cms/fundamentals/setup/server-setup/images/iis-app-pool-core.png b/16/umbraco-cms/fundamentals/setup/server-setup/images/iis-app-pool-core.png new file mode 100644 index 00000000000..09e0a3b14c7 Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/server-setup/images/iis-app-pool-core.png differ diff --git a/16/umbraco-cms/fundamentals/setup/server-setup/images/iis-core-website-config-v15.png b/16/umbraco-cms/fundamentals/setup/server-setup/images/iis-core-website-config-v15.png new file mode 100644 index 00000000000..9af7c7413ae Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/server-setup/images/iis-core-website-config-v15.png differ diff --git a/16/umbraco-cms/fundamentals/setup/server-setup/images/iis-core-website-config.png b/16/umbraco-cms/fundamentals/setup/server-setup/images/iis-core-website-config.png new file mode 100644 index 00000000000..906af9f7875 Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/server-setup/images/iis-core-website-config.png differ diff --git a/16/umbraco-cms/fundamentals/setup/server-setup/images/iis-environment-variables-v15.png b/16/umbraco-cms/fundamentals/setup/server-setup/images/iis-environment-variables-v15.png new file mode 100644 index 00000000000..d53383954f8 Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/server-setup/images/iis-environment-variables-v15.png differ diff --git a/16/umbraco-cms/fundamentals/setup/server-setup/images/iis-environment-variables.png b/16/umbraco-cms/fundamentals/setup/server-setup/images/iis-environment-variables.png new file mode 100644 index 00000000000..eca4129d499 Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/server-setup/images/iis-environment-variables.png differ diff --git a/16/umbraco-cms/fundamentals/setup/server-setup/images/visual-studio-deploy.png b/16/umbraco-cms/fundamentals/setup/server-setup/images/visual-studio-deploy.png new file mode 100644 index 00000000000..e920000213d Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/server-setup/images/visual-studio-deploy.png differ diff --git a/16/umbraco-cms/fundamentals/setup/server-setup/load-balancing/README.md b/16/umbraco-cms/fundamentals/setup/server-setup/load-balancing/README.md new file mode 100644 index 00000000000..59ebf29ef23 --- /dev/null +++ b/16/umbraco-cms/fundamentals/setup/server-setup/load-balancing/README.md @@ -0,0 +1,160 @@ +--- +description: >- + Information on how to deploy Umbraco in a Load Balanced scenario and other + details to consider when setting up Umbraco for load balancing +--- + +# Umbraco in Load Balanced Environments + +## Overview + +Configuring and setting up a load balanced server environment requires planning, design and testing. This document should assist you in setting up your servers, load balanced environment and Umbraco configuration. + +This document assumes that you have a fair amount of knowledge about: + +* Umbraco +* IIS 10+ +* Networking & DNS +* Windows Server +* .NET5+ + +{% hint style="info" %} +It is highly recommended that you setup your staging environment to also be load balanced so that you can run all of your testing on a similar environment to your live environment. +{% endhint %} + +## Design + +These instructions make the following assumptions: + +* All web servers can communicate with the database where Umbraco data is stored +* You are running Umbraco 9.0.0 or above +* _**You will designate a single server to be the backoffice server for which your editors will log into for editing content.**_ Umbraco will not work correctly if the backoffice is behind the load balancer. + +There are three design alternatives you can use to effectively load balance servers: + +1. You use cloud based auto-scaling appliances like [Microsoft's Azure Web Apps](https://azure.microsoft.com/en-us/services/app-service/web/) +2. Each server hosts copies of the load balanced website files and a file replication service is running to ensure that all files on all servers are up to date +3. The load balanced website files are located on a centralized file share (SAN/NAS/Clustered File Server/Network Share) + +You will need a load balancer to do your load balancing. + +## How Umbraco load balancing works + +In order to understand how to host your site it is best to understand how Umbraco's flexible load balancing works. + +The following diagram shows the data flow/communication between each item in the environment: + +![Umbraco flexible load balancing diagram](../../../../../../10/umbraco-cms/fundamentals/setup/server-setup/load-balancing/images/flexible-load-balancing-v9.png) + +The process is as follows: + +* Administrators and editors create, update, delete data/content on the backoffice server +* These events are converted into data structures called "instructions" and are stored in the database in a queue +* Each front-end server checks to see if there are any outstanding instructions it hasn't processed yet +* When a front-end server detects that there are pending instructions, it downloads them and processes them and in turn updates it's cache, cache files and indexes on its own file system +* There can be a delay between content updates and a front-end server's refreshing, this is expected and normal behaviour. + +## Scheduling and server role election + +Although there is a backoffice server designated for administration, by default this is not explicitly set as the "Scheduling server". In Umbraco there can only be a single scheduling server which performs the following tasks: + +* Scheduled tasks - to initiate any configured scheduled tasks +* Scheduled publishing - to initiate any scheduled publishing for documents + +### Automatic Server Role Election + +Umbraco will automatically elect a "Scheduling server" to perform the above services. This means that all of the servers will need to be able to resolve the URL of either: itself, the Backoffice server, the internal load balancer, or the public address. + +There are two server roles: + +* `SchedulingPublisher` - Usually this is the backoffice instance. +* `Subscriber` - These are the scalable front-end instances - not recommended to be used for backoffice access. + +{% hint style="info" %} +These new terms replace 'Master and Replica', in Umbraco versions 7 and 8. +{% endhint %} + +Each instance will be allocated a role by the automatic server role election process, but they can also be set explicitly (recommended) + +For example, In the following diagram the node **f02.mysite.local** is the elected "Scheduling server". In order for scheduling to work it needs to be able to send requests to itself, the Backoffice server, the internal load balancer or the public address. The address used by the "Scheduling server" is called the "umbracoApplicationUrl". + +![Umbraco flexible load balancing diagram](../../../../../../10/umbraco-cms/fundamentals/setup/server-setup/load-balancing/images/flexible-load-balancing-scheduler-v9.png) + +By default, Umbraco will set the "umbracoApplicationUrl" to the address made by the first accepted request when the AppDomain starts. It is assumed that this address will be a DNS address that the server can resolve. + +For example, if a public request reached the load balancer on `www.mysite.com`, the load balancer may send the request on to the servers with the original address: `www.mysite.com`. By default the "umbracoApplicationUrl" will be `www.mysite.com`. However, load balancers may route the request internally under a different DNS name such as "f02.mysite.local" which by default would mean the "umbracoApplicationUrl" is "f02.mysite.local". In any case the elected "Scheduling server" must be able to resolve this address. + +In many scenarios this is fine, but in case this is not adequate there's a few of options you can use: + +* **Recommended**: [set your front-end(s) (non-admin server) to be explicit subscriber servers](flexible-advanced.md#explicit-schedulingpublisher-server) by creating a custom `IServerRegistrar`, this means the front-end servers will never be used as the SchedulingPublisher server role. +* Set the `UmbracoApplicationUrl` property in the [WebRouting section of the CMS config](../../../../reference/configuration/webroutingsettings.md) + +## Common load balancing setup information + +_The below section applies to all ASP.NET load balancing configurations._ + +## Server Configuration + +This section describes the configuration options depending on your hosting setup: + +1. [Azure Web Apps](azure-web-apps.md) - _You use cloud based auto-scaling appliances like_ [_Microsoft's Azure Web Apps_](https://azure.microsoft.com/en-us/services/app-service/web/) +2. [File Replication](file-system-replication.md#synchronised-file-system) - _Each server hosts copies of the load balanced website files and a file replication service is running to ensure that all files on all servers are up to date_ +3. [Centralized file share](file-system-replication.md#synchronised-file-system) - _The load balanced website files are located on a centralized file share (SAN/NAS/Clustered File Server/Network Share)_ + +[Full documentation is available here](file-system-replication.md) + +### Data Protection + +The replacement for Machine Keys in ASP.NET Core are called Data Protection. You will need to setup data protection to the same keys on all servers, without this you will end up with view state errors, validation errors and encryption/decryption errors since each server will have its own generated key. + +ASP.NET Core supports multiple ways to share keys. Use the [official docs](https://docs.microsoft.com/en-us/aspnet/core/security/data-protection/configuration/overview) to find a description that fits your setup the best. + +### Session State and Distributed Cache + +It is required to setup a distributed cache, like `DistributedSqlServerCache` or an alternative provider (see [https://docs.microsoft.com/en-us/aspnet/core/performance/caching/distributed](https://docs.microsoft.com/en-us/aspnet/core/performance/caching/distributed) for more details). The distributed cache is used by the session in your application, which is used by the default TempDataProvider in MVC. + +Because Umbraco in some cases uses TempData, your setup needs to be configured with a distributed cache. + +### Logging + +There are some logging configurations to take into account no matter what type of load balancing environment you are using. + +[Full documentation is available here](logging.md) + +### Testing + +Your staging environment should also be load balanced so that you can see any issues relating to load balancing in that environment before going to production. + +You'll need to test this solution **a lot** before going to production. You need to ensure there are no windows security issues, etc... The best way to determine issues is have a lot of people testing this setup and ensuring all errors and warnings in your application/system logs in Windows are fixed. + +Ensure to analyze logs from all servers and check for any warnings and errors. + +## Unattended upgrades + +When upgrading it is possible to run the upgrades unattended. + +Find steps on how to enable the feature for a load balanced setup in the [General Upgrades](../../upgrading/#unattended-upgrades-in-a-load-balanced-setup) article. + +## FAQs + +_Here's some common questions that are asked regarding Load Balancing with Umbraco:_ + +**Question>** _Why do I need to have a single web instance for Umbraco admin?_ + +_TL:DR_ You must not load balance the Umbraco backoffice, you will end up with data integrity or corruption issues. + +The reason you need a single server is because there is no way to guarantee transactional safety between servers. This is because we don't currently use database level locking, we only use application (c#) level locks to guarantee transactional data integrity which is only possible to work on one server. If you have multiple admins saving and publishing at once between servers then the order in which this data is read and written to the database absolutely must be consistent otherwise you will end up with data corruption. + +Additionally, the order in which cache instructions are written to the cache instructions table is important for LB, this order is guaranteed by having a single admin server. + +**Question>** _Can my SchedulingPublisher backoffice admin server also serve front-end requests?_ + +Yes. There are no problems with having your SchedulingPublisher backoffice admin server also serve front-end request. + +However, if you wish to have different security policies for your front-end servers and your back office servers, you may choose to not do this. + +*** + +## Umbraco Training + +{% include "../../../../.gitbook/includes/umbraco-load-balancing-training-course.md" %} diff --git a/16/umbraco-cms/fundamentals/setup/server-setup/load-balancing/azure-web-apps.md b/16/umbraco-cms/fundamentals/setup/server-setup/load-balancing/azure-web-apps.md new file mode 100644 index 00000000000..861b73339b3 --- /dev/null +++ b/16/umbraco-cms/fundamentals/setup/server-setup/load-balancing/azure-web-apps.md @@ -0,0 +1,90 @@ +# Load Balancing Azure Web Apps + +Ensure you read the [Load Balancing overview](./) and general [Azure Web Apps](../azure-web-apps.md) documentation before you begin - you will need to ensure that your ASP.NET Core & logging configurations are correct. + +## Azure Requirements + +* 2 x App service plans with 1 x web app in each: + * One for the backoffice (Administrative) environment + * One for your scalable public-facing environment (Public) +* 1 x SQL server that is shared with these 2 web apps + +The setup above will allow for the proper scaling of the Administrative and Public web apps. + +The App Service plan with the Administrative web app should only be scaled up. The reason for this is that the web app needs to stay as a single instance. + +The App Service plan with the Public web app can be scaled both out and up. + +![Loadbalancing infrastructure on Azure web apps](images/loadbalanced-infrastructure.png) + +## Lucene/Examine configuration + +The single instance Backoffice Administrative Web App should be set to use [SyncedTempFileSystemDirectoryFactory](file-system-replication.md#examine-directory-factory-options). + +The multi-instance Scalable Public Web App should be set to use [TempFileSystemDirectoryFactory](file-system-replication.md#examine-directory-factory-options). + +## Umbraco TEMP files + +When an instance of Umbraco starts up it generates some 'temporary' files on disk. In a normal IIS environment, these would be created within the folders of the Web Application. In an Azure Web App, we want these to be created in the local storage of the actual server that Azure happens to be used for the Web App. So we set this configuration setting to 'true' and the temporary files will be located in the environment temporary folder. This is required for both the performance of the website as well as to prevent file locks from occurring due to the nature of Azure Web Apps shared files system. + +```json +{ + "Umbraco": { + "CMS": { + "Hosting": { + "LocalTempStorageLocation" : "EnvironmentTemp" + } + } + } +} +``` + +#### Host synchronization + +Umbraco runs within a [.NET Host](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/host/generic-host?view=aspnetcore-6.0). + +When a host restarts, the current host 'winds down' while another host is started. This means there can be more than one live host during a restart. Restarts can occur in many scenarios including when an Azure Web App auto-transitions between hosts, you scale the instances or you utilize slot swapping. + +Some file system based services in Umbraco such as the Published Cache and Lucene files can only be accessed by a single host at once. Umbraco manages this synchronization by an object called `IMainDom`. + +By default **Umbraco v9.4 & 9.5** uses a system-wide semaphore locking mechanism. This mechanism only works on Windows systems and doesn't work with multi-instance Azure Web Apps. We need to swap it out for an alternative file system based locking mechanism by using the following appSetting. With **Umbraco v10+** `FileSystemMainDomLock` is the default setting. + +```json +{ + "Umbraco": { + "CMS": { + "Global": { + "MainDomLock" : "FileSystemMainDomLock" + } + } + } +} +``` + +Apply this setting to both the **SCHEDULINGPUBLISHER** Administrative server and the **SUBSCRIBER** scalable public-facing servers. + +## Steps to set-up an environment + +1. Create an Azure SQL database +2. Install Umbraco on your backoffice administrative environment and ensure to use your Azure SQL Database +3. Install Umbraco on your scalable public-facing environment and ensure to use your Azure SQL Database +4. Test: Perform some content updates on the administrative environment, ensure they work successfully in that environment, then verify that those changes appear on the scalable public-facing environment +5. Fix the backoffice environment to be the SCHEDULINGPUBLISHER scheduling server and the scalable public-facing environment to be SUBSCRIBERs - see [Setting Explicit Server Roles](flexible-advanced.md#explicit-schedulingpublisher-server) + +{% hint style="info" %} +Ensure all Azure resources are in the same region to avoid connection lag. +{% endhint %} + +## Scaling + +**Do not scale your backoffice administrative environment** this is not supported and can cause issues. + +The public-facing subscriber Azure Web Apps can be manually or automatically scaled up or down and is supported by Umbraco's load balancing. + +## Deployment considerations + +Since you have 2 x web apps, when you deploy you will need to deploy to both places - There are various automation techniques you can use to simplify the process. That is outside the scope of this article. + +{% hint style="info" %} +This also means that you should not be editing templates or views on a live server as SchedulingPublisher and Subscriber environments do not share the same file system. Changes should be made in a development environment and then pushed to each live environment. +{% endhint %} diff --git a/16/umbraco-cms/fundamentals/setup/server-setup/load-balancing/file-system-replication.md b/16/umbraco-cms/fundamentals/setup/server-setup/load-balancing/file-system-replication.md new file mode 100644 index 00000000000..aaa82324f4e --- /dev/null +++ b/16/umbraco-cms/fundamentals/setup/server-setup/load-balancing/file-system-replication.md @@ -0,0 +1,115 @@ +# Standalone File System + +{% hint style="info" %} +No file replication is configured, deployment handles updating files on the different servers. +{% endhint %} + +If the file system on your servers isn't performing any file replication then no _Umbraco_ configuration file changes are necessary. However Media will need to be configured to use a shared location such as Blob storage or S3. + +Depending on the configuration and performance of the environment's local storage you might need to consider [Examine Directory Factory Options](file-system-replication.md#examine-directory-factory-options) and the [Umbraco temporary storage location](https://our.umbraco.com/documentation/Reference/Configuration-for-Umbraco-7-and-8/webconfig/#umbracocorelocaltempstorage). + +## Synchronised File System + +{% hint style="info" %} +The servers are performing file replication, updates to a file on one server, updates the corresponding file on any other servers. +{% endhint %} + +If the file system on your servers is performing file replication then the Umbraco temporary folder (`~/umbraco/Data/TEMP`) must be excluded from replication. + +If the file system on your servers is located on shared storage you will need to configure Umbraco to locate the Umbraco temporary folder outside of the shared storage. + +### Replication techniques + +A common way to replicate files on Windows Server is to use \[DFS]\(https://msdn.microsoft.com/en-us/library/windows/desktop/bb540031(v=vs.85), which is included with Windows Server. + +Additional DFS resources: + +* [Overview of DFS Replication in Windows Server 2008 R2](https://technet.microsoft.com/en-us/library/cc771058.aspx) +* [Watch an intro to installing and working with DFS](https://www.youtube.com/watch?v=DYfBoUt2RVE) + +There are other alternatives for file replication out there, some free and some licensed. You'll need to decide which solution is best for your environment. + +### Non-replicated files + +When deploying Umbraco in a load balanced scenario using file replication, it is important to ensure that not all files are replicated - otherwise you will experience file locking issues. Here are the folders and files that should not be replicated: + +* `~/umbraco/Data/TEMP/*` + +{% hint style="info" %} +Alternatively store the Umbraco temporary files in the local server's 'temp' folder and set Examine to use a [Directory Factory](file-system-replication.md#examine-directory-factory-options). + +Achieve this by changing the value of the `LuceneDirectoryFactory` setting to 'TempFileSystemDirectoryFactory' in the `appsettings.json`. The downside is that if you need to view temporary files you'll have to find it in the temp files. Locating the file this way isn't always clear. + +Below is shown how to do this in a Json configuration source. + +```json +{ + "Umbraco": { + "CMS": { + "Examine": { + "LuceneDirectoryFactory" : "TempFileSystemDirectoryFactory" + } + } + } +} +``` +{% endhint %} + +* `~/umbraco/Logs/*` + * This is **optional** and depends on how you want your logs configured (see below) + +If for some reason your file replication solution doesn't allow you to not replicate specific files folders (which it should!!) then you can use an alternative approach by using virtual directories. + +The following is not the recommended setup but it is a viable alternative: + +* Copy the `~/umbraco/Data/TEMP` directory to each server, outside of any replication areas or to a unique folder for each server. +* Create a virtual directory (not a virtual application) in the `~/umbraco/Data/` folder, and name it `TEMP`. Point the virtual directory to the folder you created in step 2. +* You may delete the `~/umbraco/Data/TEMP` folder from the file system - not IIS as this may delete the virtual directory - if you wish. + +### IIS Setup + +IIS configuration is pretty straightforward with file replication. IIS is only reading files from its own file system like a normal IIS website. + +## Mixture of standalone & synchronised + +In some scenarios you have a mixture of standalone and synchronised file systems. An example of this is Azure Web Apps where the file system isn't replicated between backoffice and front end servers but is replicated between all front end servers, in this configuration you should follow the steps for synchronised file systems. + +There is a specific documentation for load balancing with [Azure Web Apps](azure-web-apps.md) + +## Examine Directory Factory Options + +* The `TempFileSystemDirectoryFactory` allows Examine to store indexes directly in the environment temporary storage directory, and should be used instead of `SyncTempEnvDirectoryFactory` mentioned above. + +```json +{ + "Umbraco": { + "CMS": { + "Examine": { + "LuceneDirectoryFactory" : "TempFileSystemDirectoryFactory" + } + } + } +} +``` + +* The `SyncedTempFileSystemDirectoryFactory` enables Examine to sync indexes between the remote file system and the local environment temporary storage directory, the indexes will be accessed from the temporary storage directory. This setting is needed because Lucene has issues when working from a remote file share so the files need to be read/accessed locally. Any time the index is updated, this setting will ensure that both the locally created indexes and the normal indexes are written to. This will ensure that when the app is restarted or the local environment temp files are cleared out that the index files can be restored from the centrally stored index files. + +```json +{ + "Umbraco": { + "CMS": { + "Examine": { + "LuceneDirectoryFactory" : "SyncedTempFileSystemDirectoryFactory" + } + } + } +} +``` + +{% hint style="info" %} +If you are load balancing with [Azure Web Apps](azure-web-apps.md) make sure to check out the article we have for that specific set-up. +{% endhint %} + +## Advanced techniques + +Once you are familiar with how flexible load balancing works, you might be interested in some [advanced techniques](flexible-advanced.md). diff --git a/16/umbraco-cms/fundamentals/setup/server-setup/load-balancing/flexible-advanced.md b/16/umbraco-cms/fundamentals/setup/server-setup/load-balancing/flexible-advanced.md new file mode 100644 index 00000000000..e671265d563 --- /dev/null +++ b/16/umbraco-cms/fundamentals/setup/server-setup/load-balancing/flexible-advanced.md @@ -0,0 +1,104 @@ +# Advanced Techniques With Flexible Load Balancing + +_This describes some more advanced techniques that you could achieve with flexible load balancing_ + +The election process that runs during the startup of an Umbraco instance determines the server role that instance will undertake. + +There are two server roles to be aware of for flexible load balancing: + +* `SchedulingPublisher` - The Umbraco instance usually used for backoffice access, responsible for running scheduled tasks. +* `Subscriber` - A scalable instance that subscribes to content updates from the SchedulingPublisher server, not recommended to be used for backoffice access. + +{% hint style="info" %} +These new terms replace 'Master and Replica', in Umbraco versions 7 and 8. +{% endhint %} + +## Explicit SchedulingPublisher server + +It is recommended to configure an explicit SchedulingPublisher server since this reduces the amount of complexity that the election process performs. + +The first thing to do is create a couple of small classes that implement `IServerRoleAccessor` one for each of the different server roles: + +```csharp +public class SchedulingPublisherServerRoleAccessor : IServerRoleAccessor +{ + public ServerRole CurrentServerRole => ServerRole.SchedulingPublisher; +} + +public class SubscriberServerRoleAccessor : IServerRoleAccessor +{ + public ServerRole CurrentServerRole => ServerRole.Subscriber; +} +``` + +then you'll need to replace the default `IServerRoleAccessor` for the your custom registrars. You'll can do this by using the `SetServerRegistrar()` extension method on `IUmbracoBuilder` from a [Composer](../../../../implementation/composing.md). + +```csharp +// This should be executed on your single `SchedulingPublisher` server +builder.SetServerRegistrar(); + +// This should be executed on your `Subscriber` servers +builder.SetServerRegistrar(); +``` + +Now that your subscriber servers are using your custom `SubscriberServerRoleAccessor` class, they will always be deemed 'Subscriber' servers and will not attempt to run the automatic server role election process or task scheduling. + +By setting your SchedulingPublisher server to use your custom `SchedulingPublisherServerRoleAccessor` class, it will always be deemed the 'SchedulingPublisher' and will always be the one that executes all task scheduling. + +## Subscriber servers - Read-only database access + +{% hint style="info" %} +This description pertains only to Umbraco database tables +{% endhint %} + +In some cases infrastructure admins will not want their front-end servers to have write access to the database. By default front-end servers will require write full access to the following tables: + +* `umbracoServer` +* `umbracoNode` + +This is because by default each server will inform the database that they are active and more importantly it is used for task scheduling. Only a single server can execute task scheduling and these tables are used for servers to use a server role election process without the need for any configuration. So in the case that a subscriber server becomes the SchedulingPublisher task scheduler, **it will require write access to all of the Umbraco tables**. + +In order to have read-only database access configured for your front-end servers, you need to implement the [Explicit SchedulingPublisher server](flexible-advanced.md#explicit-schedulingpublisher-server) configuration mentioned above. + +Now that your subscriber servers are using your custom `SubscriberServerRoleAccessor` class, they will always be deemed 'Subscriber' servers and will not attempt to run the automatic server role election process or task scheduling. Because you are no longer using the default `ElectedServerRoleAccessor` they will not try to ping the umbracoServer table. + +{% hint style="info" %} +If using [SqlMainDomLock](azure-web-apps.md#appdomain-synchronization) on Azure WebApps then write-permissions are required for the following tables for all server roles including 'Subscriber'. + +* `umbracoLock` +* `umbracoKeyValue` + +SQL Server Replica databases cannot be used as they are read-only without replacing the default MainDomLock with a custom provider. +{% endhint %} + +## Controlling how often the load balancing instructions from the database are processed and pruned + +The configurations can be adjusted to control how often the load balancing instructions from the database are processed and pruned. + +Below is shown how to do this from a JSON configuration source. + +```json +{ + "Umbraco": { + "CMS": { + "Global": { + "DatabaseServerMessenger": { + "MaxProcessingInstructionCount": 1000, + "TimeBetweenPruneOperations": "00:01:00", + "TimeBetweenSyncOperations": "00:00:05", + "TimeToRetainInstructions": "2.00:00:00" + } + } + } + } +} +``` + +Options: + +* `TimeToRetainInstructions` - The timespan to keep instructions in the database; records older than this number will be pruned. +* `MaxProcessingInstructionCount` - The maximum number of instructions that can be processed at startup; otherwise the server cold-boots (rebuilds its caches) +* `TimeBetweenSyncOperations` - The timespan to wait between each sync operations +* `TimeBetweenPruneOperations` - The timespan to wait between each prune operation + +These setting would normally be applied to all environments as they are added to the global app settings. If you need these settings to be environment specific, we recommend using [environment specific `appSetting` files](../../../../reference/configuration/). diff --git a/16/umbraco-cms/fundamentals/setup/server-setup/load-balancing/images/flexible-load-balancing-scheduler-v9.png b/16/umbraco-cms/fundamentals/setup/server-setup/load-balancing/images/flexible-load-balancing-scheduler-v9.png new file mode 100644 index 00000000000..1b36ee80b84 Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/server-setup/load-balancing/images/flexible-load-balancing-scheduler-v9.png differ diff --git a/16/umbraco-cms/fundamentals/setup/server-setup/load-balancing/images/flexible-load-balancing-scheduler.png b/16/umbraco-cms/fundamentals/setup/server-setup/load-balancing/images/flexible-load-balancing-scheduler.png new file mode 100644 index 00000000000..010ecfef78e Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/server-setup/load-balancing/images/flexible-load-balancing-scheduler.png differ diff --git a/16/umbraco-cms/fundamentals/setup/server-setup/load-balancing/images/flexible-load-balancing-v9.png b/16/umbraco-cms/fundamentals/setup/server-setup/load-balancing/images/flexible-load-balancing-v9.png new file mode 100644 index 00000000000..e6b6d662975 Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/server-setup/load-balancing/images/flexible-load-balancing-v9.png differ diff --git a/16/umbraco-cms/fundamentals/setup/server-setup/load-balancing/images/flexible-load-balancing.png b/16/umbraco-cms/fundamentals/setup/server-setup/load-balancing/images/flexible-load-balancing.png new file mode 100644 index 00000000000..f056aabb13a Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/server-setup/load-balancing/images/flexible-load-balancing.png differ diff --git a/16/umbraco-cms/fundamentals/setup/server-setup/load-balancing/images/loadbalanced-infrastructure.png b/16/umbraco-cms/fundamentals/setup/server-setup/load-balancing/images/loadbalanced-infrastructure.png new file mode 100644 index 00000000000..89f00adff7b Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/server-setup/load-balancing/images/loadbalanced-infrastructure.png differ diff --git a/16/umbraco-cms/fundamentals/setup/server-setup/load-balancing/logging.md b/16/umbraco-cms/fundamentals/setup/server-setup/load-balancing/logging.md new file mode 100644 index 00000000000..0ffff474790 --- /dev/null +++ b/16/umbraco-cms/fundamentals/setup/server-setup/load-balancing/logging.md @@ -0,0 +1,7 @@ +# Logging with load balancing + +Umbraco v8+ uses Serilog for logging. When load balancing Umbraco consideration should be given as to how the log files from each server will be accessed. + +There are many Serilog Sinks available and one of these may be appropriate to store logs for all servers in a central repository such as Azure Application Insights or Elmah.io. + +For more information, see [SeriLog Provided Sinks](https://github.com/serilog/serilog/wiki/Provided-Sinks). diff --git a/16/umbraco-cms/fundamentals/setup/server-setup/permissions.md b/16/umbraco-cms/fundamentals/setup/server-setup/permissions.md new file mode 100644 index 00000000000..4872ff3dbfd --- /dev/null +++ b/16/umbraco-cms/fundamentals/setup/server-setup/permissions.md @@ -0,0 +1,30 @@ +--- +description: "Information on file and folder permissions required for Umbraco sites" +--- + +# File and folder permissions + +{% hint style="info" %} +To ensure a stable and smoothly running Umbraco installation, these permissions need to be set correctly. These permissions should be set up before or during the installation of Umbraco. + +The main account that requires 'modify' file permissions to be set on the folders below, is the account used start Umbraco. If Umbraco is hosted in IIS this will be the Application Pool Identity for the IIS website. Usually IIS APPPOOL\appPoolName or a specific local account or in some circumstances Network Service. If in doubt, ask your server admin / hosting company. Additionally, the Internet User (IUSR) account and IIS_IUSRS account only require 'read only' access to the site's folders. + +Generally, when developing locally with Visual Studio or Rider, permissions do not need to be strictly applied. +{% endhint %} + +{% hint style="info" %} +If you have any specific static files/media items/etc, you should add the appropriate permissions accordingly. + +The permissions documentation should allow you to run a plain Umbraco install successfully. +{% endhint %} + +|File / folder |Permission |Comment | +|--------------------------|-----------------------|-----------------------------------------------------| +|`/appSettings*.json` |Modify / Full control |Only needed for setting database and a global identifier during installation. So can be set to read-only afterwards for enhanced security.| +|`/App_Plugins` |Modify / Full control |Should always have modify rights as the folder and its files are used by packages. Not part of your project by default.| +|`/umbraco` |Modify / Full control |Should always have modify rights as the folder and its files are used for cache and storage.| +|`/Views` |Modify / Full control |Should always have modify rights as the folder and its files are used for Templates and Partial views| +|`/wwwroot/css` |Modify / Full control |Should always have modify rights as the folder and its files are used for css files.| +|`/wwwroot/media` |Modify / Full control |Should always have modify rights as the folder and its files are used for Media files uploaded via the Umbraco CMS backoffice.| +|`/wwwroot/umbraco` |Modify / Full control |Should always have modify rights as the folder and its files are used by packages. Not part of your project by default.| +|`/wwwroot/scripts` |Modify / Full control |Should always have modify rights as the folder and its files are used for script files.| diff --git a/16/umbraco-cms/fundamentals/setup/server-setup/runtime-modes.md b/16/umbraco-cms/fundamentals/setup/server-setup/runtime-modes.md new file mode 100644 index 00000000000..566939604ce --- /dev/null +++ b/16/umbraco-cms/fundamentals/setup/server-setup/runtime-modes.md @@ -0,0 +1,170 @@ +--- +description: >- + This section describes how to use the runtime mode setting to optimize Umbraco + for the best development experience or optimal production environment. +--- + +# Runtime Modes + +## Configuring the runtime mode + +You can configure the runtime mode to optimize Umbraco for different development experiences and environments by setting `Umbraco:CMS:Runtime:Mode` to one of the available modes: + +* `BackofficeDevelopment` (default) +* `Development` +* `Production` + +This can be done via the `appsettings.json` file, environment variables, or any other .NET configuration provider (like Azure Key Vault/App Configuration). Although this setting affects how Umbraco behaves at runtime, some modes have prerequisites on how the project is built/published. Make sure to read the descriptions of each mode before changing this setting from the default `BackofficeDevelopment` mode, as incorrect configuration can result in your application not starting (by throwing a `BootFailedException`). + +## BackofficeDevelopment mode + +The `BackofficeDevelopment` mode is the default behavior for Umbraco: it does not optimize Umbraco for any specific environment and does not have any prerequisites. This mode allows for rapid development (without having to recompile/rebuild your project), including all development from within the backoffice. + +## Development mode + +The `Development` mode can be used when you're developing from an IDE (like Visual Studio, VS Code, or Rider) or the dotnet CLI (e.g. using `dotnet watch`). It is a recommended prerequisite if you want to use the `Production` mode in your production environment. + +This mode disables in-memory ModelsBuilder generation and validates the following setting: + +* `Umbraco:CMS:ModelsBuilder:ModelsMode` is **not set** to `InMemoryAuto`. + +If you want to use the generated models, use `SourceCodeAuto` or `SourceCodeManual`. These require manually recompiling the project after the models have changed (for example after updating Document Types, Media Types, Member Types, or Data Types). Razor views (`cshtml` files) will still be automatically compiled at runtime. They allow you to quickly iterate on the rendered output from templates, partial views, and view components. + +The recommended approach to enable `Development` mode is to update the `appsettings.json` file with the following settings: + +```json +{ + "Umbraco": { + "CMS": { + "Runtime": { + "Mode" : "Development" + }, + "ModelsBuilder":{ + "ModelsMode": "SourceCodeAuto" + } + } + } +} +``` + +Ensure your models are generated by running Umbraco and navigating to **Settings** > **Models Builder** > **Generate models**. You can remove the following properties from your `csproj` project file to enable the compilation of Razor views (which also ensures your views do not contain compilation errors and is a prerequisite for enabling `Production` mode): + +```xml +false +false +``` + +Fix any compilation errors you might get after this, e.g. if you accidentally referenced deleted models or properties. Running the application will still show the rendered content and you're now ready to optionally enable `Production` mode on your production environment. + +{% hint style="info" %} +Ensure you have the `true` property set in your `csproj` project file, so Razor views are always copied to the publish directory. This is required by the CMS to display the contents in the backoffice, for Forms to lookup custom theme views and for Deploy to be able to compare schemas (otherwise you'll get schema mismatches). +{% endhint %} + +## Production mode + +Use `Production` mode to ensure your production environment is running optimally by disabling development features and validating whether specific settings are configured to their recommended production values. + +This mode disables both in-memory ModelsBuilder generation (see [Development mode](runtime-modes.md#development-mode)) and Razor (cshtml) runtime compilation. Production mode requires you to compile your views at build/publish time and enforces the following settings for optimal performance/security: + +* The application is built/published in Release mode (with JIT optimization enabled), e.g. using `dotnet publish --configuration Release`; +* `Umbraco:CMS:WebRouting:UmbracoApplicationUrl` is set to a valid URL; +* `Umbraco:CMS:Global:UseHttps` is enabled; +* `Umbraco:CMS:ModelsBuilder:ModelsMode` is set to `Nothing`. + +{% hint style="info" %} +To compile your views at build/publish time, remove the `` and `` properties from your project file (see the [Development mode](runtime-modes.md#development-mode) section). If you don't, Umbraco can't find the templates and will return 404 (Page Not Found) errors. +{% endhint %} + +The recommended approach to enable `Production` mode is to update the `appsettings.Production.json` file (or create one) with the following settings: + +```json +{ + "Umbraco": { + "CMS": { + "Runtime": { + "Mode": "Production" + }, + "Hosting": { + "Debug": false + }, + "Global": { + "UseHttps": true + }, + "ModelsBuilder": { + "ModelsMode": "Nothing" + }, + "WebRouting": { + "UmbracoApplicationUrl": "https:///" + }, + "RuntimeMinification": { + "UseInMemoryCache": true, + "CacheBuster": "AppDomain" + } + } + } +} +``` + +Although you can still edit document types and views (if not running from the published output), changes won't be picked up until you've rebuilt your project or republished the application. + +{% hint style="info" %} +Models won't be generated by ModelsBuilder (because the mode is set to `Nothing`), requiring you to do all your changes while in `Development` mode.\ +\ +As Models Builder is set to `Nothing`, the Models Builder dashboard is disabled in the backoffice of live environment. + + + +\ +Also, templates cannot be edited on live environment as runtime compilation is not enabled and is set to Production. + + +{% endhint %} + +Also ensure the `UmbracoApplicationUrl` is updated to the primary URL of your production environment, as this is used when sending emails (password reset, notifications, health check results, etc.). + +## Customize/extend runtime mode validation + +Validation of the above-mentioned settings is done when determining the runtime level during startup using the new `IRuntimeModeValidationService` and when it fails, causes a `BootFailedException` to be thrown. The default implementation gets all registered `IRuntimeModeValidators` to do the validation, making it possible to remove default checks and/or add your own (inherit from `RuntimeModeProductionValidatorBase`, if you only want to validate against the production runtime mode). The following validators are added by default: + +* `JITOptimizerValidator` - Ensure the application is built/published in Release mode (with JIT optimization enabled) when in production runtime mode, e.g. using `dotnet publish --configuration Release`; +* `UmbracoApplicationUrlValidator` - ensure `Umbraco:CMS:WebRouting:UmbracoApplicationUrl` is configured when in production runtime mode; +* `UseHttpsValidator` - ensure `Umbraco:CMS:Global:UseHttps` is enabled when in production runtime mode; +* `ModelsBuilderModeValidator` - ensure `Umbraco:CMS:ModelsBuilder:ModelsMode` is not set to `InMemoryAuto` when in development runtime mode and set to `Nothing` when in production runtime mode. + +The following example removes the default `UmbracoApplicationUrlValidator` and adds a new custom `DisableElectionForSingleServerValidator`: + +```csharp +using System.Diagnostics.CodeAnalysis; +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Infrastructure.Runtime; +using Umbraco.Cms.Infrastructure.Runtime.RuntimeModeValidators; + +public class RuntimeModeValidatorComposer : IComposer +{ + public void Compose(IUmbracoBuilder builder) + => builder.RuntimeModeValidators() + .Remove() + .Add(); +} + +public class DisableElectionForSingleServerValidator : IRuntimeModeValidator +{ + private readonly IOptionsMonitor _globalSettings; + + public DisableElectionForSingleServerValidator(IOptionsMonitor globalSettings) => _globalSettings = globalSettings; + + public bool Validate(RuntimeMode runtimeMode, [NotNullWhen(false)] out string? validationErrorMessage) + { + if (runtimeMode == RuntimeMode.Production && _globalSettings.CurrentValue.DisableElectionForSingleServer == false) + { + validationErrorMessage = "Disable primary server election (and support for load balancing) to improve startup performance."; + return false; + } + + validationErrorMessage = null; + return true; + } +} +``` diff --git a/16/umbraco-cms/fundamentals/setup/upgrading/README.md b/16/umbraco-cms/fundamentals/setup/upgrading/README.md new file mode 100644 index 00000000000..39e65d4fbcf --- /dev/null +++ b/16/umbraco-cms/fundamentals/setup/upgrading/README.md @@ -0,0 +1,190 @@ +--- +description: This is the guide for upgrading existing installations in general. +--- + +# Upgrade your project + +In this article, you will find everything you need to upgrade your Umbraco CMS project. + +You will find instructions on how to upgrade to a new minor or major version as well as how to run upgrades unattended. + +* [Before you upgrade](./#before-you-upgrade) +* [Upgrade to a new Major](./#upgrade-to-a-new-major) +* [Upgrade to a new Minor](./#upgrade-to-a-new-minor) +* [Run an unattended upgrade](./#run-an-unattended-upgrade) + +## Before you upgrade + +The following lists a few things to be aware of before initiating an upgrade of your Umbraco CMS project. + +* Sometimes there are exceptions to general upgrade guidelines. These are listed in the [**version-specific guide**](version-specific/). Be sure to read this article before moving on. +* Check if your setup meets the [requirements](../requirements.md) for the new versions you will be upgrading your project to. +* Things may go wrong for different reasons. Be sure to **ALWAYS** keep a backup of both your site's files and the database. This way you can always return to a version that you know works. +* Before upgrading to a new major version, check if the packages you're using are compatible with the version you're upgrading to. On the package's download page, in the **Project compatibility** area, click **View details** to check version-specific compatibility. + +{% hint style="info" %} +It is necessary to run the upgrade installer on each environment of your Umbraco site. This means that you need to repeat the steps below on each of your environments in order to complete the upgrade. +{% endhint %} + +## Legacy Umbraco + +The steps outlined in this article apply to modern Umbraco from version 10 and later versions. + +Are you upgrading to a minor for Umbraco 6, 7, or 8 you can find the appropriate guide below: + +{% content-ref url="version-specific/minor-upgrades-for-umbraco-8.md" %} +[minor-upgrades-for-umbraco-8.md](version-specific/minor-upgrades-for-umbraco-8.md) +{% endcontent-ref %} + +{% content-ref url="version-specific/minor-upgrades-for-umbraco-7.md" %} +[minor-upgrades-for-umbraco-7.md](version-specific/minor-upgrades-for-umbraco-7.md) +{% endcontent-ref %} + +## Upgrade to a new Major + +You can upgrade to a new major of Umbraco CMS directly by using NuGet. + +It is recommended that you upgrade to the closest [Long-term Support (LTS) major](https://umbraco.com/products/knowledge-center/long-term-support-and-end-of-life/) version before upgrading to the latest version. For Umbraco 10, the closest long-term support version is Umbraco 13. Once upgraded to Umbraco 13, you can upgrade to Umbraco 14. + +{% hint style="warning" %} +Switching to a new major of Umbraco CMS also means switching to a new .NET version. You need to make sure that any packages used on your site are compatible with this version before upgrading. + +The package compatibility can be checked on the package's download page. Locate the **Project compatibility** area and select **View details** to check version-specific compatibility. +{% endhint %} + +### Choose the correct .NET version + +Use the table below to determine which .NET version to upgrade to when going through the steps below. + +| CMS version | .NET version | +| ----------- | ------------ | +| 15 | 9.0 | +| 14 | 8.0 | +| 13 | 8.0 | +| 12 | 7.0 | +| 11 | 7.0 | +| 10 | 6.0.5 | + +### Upgrade your project using Visual Studio + +{% hint style="info" %} +If you are upgrading a Cloud project locally from version 14 to 15, remove the `Umbraco.Cloud.Cms.PublicAccess` and `Umbraco.Cloud.Identity.Cms` packages. For more details, see [Step 3: Upgrade the project locally using Visual Studio](https://docs.umbraco.com/umbraco-cloud/product-upgrades/major-upgrades#step-3-upgrade-the-project-locally-using-visual-studio) in the Umbraco Cloud Documentation. +{% endhint %} + +It's recommended that you upgrade the site offline and test the upgrade fully before deploying it to the production environment. + +1. Stop your site in IIS to prevent any changes from being made while you are upgrading. +2. Open your Umbraco project in Visual Studio. +3. Right-click on the project name in the Solution Explorer and select **Properties**. +4. Select the **.NET** version from the **Target Framework** drop-down. +5. Go to **Tools** > **NuGet Package Manager** > **Manage NuGet Packages for Solution...** +6. Go to the **Installed** tab in the NuGet Package manager. +7. Upgrade **Umbraco.Cms**. + + a. Select the correct version from the **Version** drop-down. + + b. Click **Install** to upgrade your project. + +{% hint style="info" %} +If you have other packages like Umbraco Forms installed, upgrade them before upgrading **Umbraco.CMS**. Consult the [version specific upgrade notes for Umbraco Forms](https://docs.umbraco.com/umbraco-forms/upgrading/version-specific) if relevant. +{% endhint %} + +8. Make sure that your connection string has `TrustServerCertificate=True` in order to complete the upgrade successfully: + +```csharp +"ConnectionStrings": { + "umbracoDbDSN": "Server=YourLocalSQLServerHere;Database=NameOfYourDatabaseHere;User Id=NameOfYourUserHere;Password=YourPasswordHere;TrustServerCertificate=True" +} +``` + +9. Restart your site in IIS, then build and run your project to finish the installation. + +{% hint style="info" %} +In Umbraco 13, we have moved to using the [Minimal Hosting Model](https://github.com/umbraco/Umbraco-CMS/pull/14656). + +If you have added custom code to the `startup.cs` file, we recommend moving the code into a Composer after upgrading. +{% endhint %} + +{% hint style="warning" %} +If your database experiences timeout issues after an upgrade, it might be due to [ASP.NET Core Module's](https://learn.microsoft.com/en-us/aspnet/core/test/troubleshoot-azure-iis?#default-startup-limits) 'startupTimeLimit' configuration. + +To fix the issue, try increasing the [`startupTimeLimit`](https://learn.microsoft.com/en-us/aspnet/core/host-and-deploy/iis/web-config?) in the `web.config` file. Additionally, you can set the [`Connection Timeout`](https://learn.microsoft.com/en-us/dotnet/api/system.data.sqlclient.sqlconnection.connectiontimeout?) value in the [`ConnectionString`](https://learn.microsoft.com/en-us/dotnet/api/microsoft.data.sqlclient.sqlconnection.connectionstring?) in the `appsettings.json` file. +{% endhint %} + +## Upgrade to a new Minor + +NuGet installs the latest version of the package when you use the `dotnet add package` command unless you specify a package version: + +`dotnet add package Umbraco.Cms --version ` + +Add a package reference to your project by executing the `dotnet add package Umbraco.Cms` command in the directory that contains your project file. + +Run `dotnet restore` to install the package. + +{% hint style="warning" %} +For v9: If you are using SQL CE in your project you will need to run `dotnet add package Umbraco.Cms.SqlCe --version ` before running the `dotnet restore` command. From v10, SQL CE has been replaced with SQLite so a `dotnet restore` should be sufficient. If this is not working then you will need to run `dotnet add package Umbraco.Cms.Persistence.Sqlite --version ` and then `dotnet restore`. +{% endhint %} + +When the command completes, open the **.csproj** file to make sure the package reference was updated: + +{% code title=".csproj" %} +```xml + + + +``` +{% endcode %} + +## Run an unattended upgrade + +When upgrading your Umbraco project, it is possible to enable the upgrade to run unattended. This means that you will not need to run through the installation wizard when upgrading. + +Below you will find the steps you need to take in order to upgrade your project unattended. + +{% hint style="info" %} +Are you running a load balanced setup with multiple servers and environments? + +Check out the section about [Unattended upgrades in a load balanced setup](./#unattended-upgrades-in-a-load-balanced-setup). +{% endhint %} + +### Enable the unattended upgrade feature + +1. Add the `Umbraco:Cms:Unattended:UpgradeUnattended` configuration key. +2. Set the value of the key to `true`. + +{% code title="appsettings.json" %} +```json +{ + "Umbraco": { + "CMS": { + "Unattended": { + "UpgradeUnattended": true + } + } + } +} +``` +{% endcode %} + +### Run the upgrade + +With the correct configuration applied, the project will be upgraded on the next boot. + +#### Boot order + +The Runtime level will use `Run` instead of `Upgrade` to allow the website to continue to boot up directly after the migration is run. This happens instead of initiating the otherwise required restart. + +{% hint style="info" %} +The upgrade is run after Composers but before Components and the `UmbracoApplicationStartingNotification`. This is because the migration requires services that are registered in Composers and Components require that Umbraco and the database are ready. +{% endhint %} + +### Unattended upgrades in a load balanced setup + +Follow the steps outlined below to use unattended upgrades in a load balanced setup. + +1. Upgrade Umbraco via NuGet ([see instructions above](./#upgrade-to-a-new-major)) +2. Deploy to all environments. +3. Set the `Umbraco:CMS:Unattended:UpgradeUnattended` configuration key to `true` for **the Main server only**. +4. Boot the Main server and the upgrade will run automatically. +5. Wait for the upgrade to complete. +6. Boot the Read-Only servers and make sure they do not show the “upgrade required” screen. diff --git a/16/umbraco-cms/fundamentals/setup/upgrading/images/content-on-8_1 (1) (1) (1) (1).png b/16/umbraco-cms/fundamentals/setup/upgrading/images/content-on-8_1 (1) (1) (1) (1).png new file mode 100644 index 00000000000..1d8efa9c29d Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/upgrading/images/content-on-8_1 (1) (1) (1) (1).png differ diff --git a/16/umbraco-cms/fundamentals/setup/upgrading/images/content-on-8_1 (1) (1) (1).png b/16/umbraco-cms/fundamentals/setup/upgrading/images/content-on-8_1 (1) (1) (1).png new file mode 100644 index 00000000000..1d8efa9c29d Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/upgrading/images/content-on-8_1 (1) (1) (1).png differ diff --git a/16/umbraco-cms/fundamentals/setup/upgrading/images/content-on-8_1 (1) (1) (2).png b/16/umbraco-cms/fundamentals/setup/upgrading/images/content-on-8_1 (1) (1) (2).png new file mode 100644 index 00000000000..1d8efa9c29d Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/upgrading/images/content-on-8_1 (1) (1) (2).png differ diff --git a/16/umbraco-cms/fundamentals/setup/upgrading/images/content-on-8_1 (1) (1) (3).png b/16/umbraco-cms/fundamentals/setup/upgrading/images/content-on-8_1 (1) (1) (3).png new file mode 100644 index 00000000000..1d8efa9c29d Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/upgrading/images/content-on-8_1 (1) (1) (3).png differ diff --git a/16/umbraco-cms/fundamentals/setup/upgrading/images/content-on-8_1 (1) (1).png b/16/umbraco-cms/fundamentals/setup/upgrading/images/content-on-8_1 (1) (1).png new file mode 100644 index 00000000000..1d8efa9c29d Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/upgrading/images/content-on-8_1 (1) (1).png differ diff --git a/16/umbraco-cms/fundamentals/setup/upgrading/images/content-on-8_1 (1) (2).png b/16/umbraco-cms/fundamentals/setup/upgrading/images/content-on-8_1 (1) (2).png new file mode 100644 index 00000000000..1d8efa9c29d Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/upgrading/images/content-on-8_1 (1) (2).png differ diff --git a/16/umbraco-cms/fundamentals/setup/upgrading/images/content-on-8_1 (1).png b/16/umbraco-cms/fundamentals/setup/upgrading/images/content-on-8_1 (1).png new file mode 100644 index 00000000000..1d8efa9c29d Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/upgrading/images/content-on-8_1 (1).png differ diff --git a/16/umbraco-cms/fundamentals/setup/upgrading/images/content-on-8_1 (2).png b/16/umbraco-cms/fundamentals/setup/upgrading/images/content-on-8_1 (2).png new file mode 100644 index 00000000000..1d8efa9c29d Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/upgrading/images/content-on-8_1 (2).png differ diff --git a/16/umbraco-cms/fundamentals/setup/upgrading/images/content-on-8_1.png b/16/umbraco-cms/fundamentals/setup/upgrading/images/content-on-8_1.png new file mode 100644 index 00000000000..1d8efa9c29d Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/upgrading/images/content-on-8_1.png differ diff --git a/16/umbraco-cms/fundamentals/setup/upgrading/images/fresh-8_1-site (1) (1) (1) (1).png b/16/umbraco-cms/fundamentals/setup/upgrading/images/fresh-8_1-site (1) (1) (1) (1).png new file mode 100644 index 00000000000..7914e6644d3 Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/upgrading/images/fresh-8_1-site (1) (1) (1) (1).png differ diff --git a/16/umbraco-cms/fundamentals/setup/upgrading/images/fresh-8_1-site (1) (1) (1).png b/16/umbraco-cms/fundamentals/setup/upgrading/images/fresh-8_1-site (1) (1) (1).png new file mode 100644 index 00000000000..7914e6644d3 Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/upgrading/images/fresh-8_1-site (1) (1) (1).png differ diff --git a/16/umbraco-cms/fundamentals/setup/upgrading/images/fresh-8_1-site (1) (1) (2).png b/16/umbraco-cms/fundamentals/setup/upgrading/images/fresh-8_1-site (1) (1) (2).png new file mode 100644 index 00000000000..7914e6644d3 Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/upgrading/images/fresh-8_1-site (1) (1) (2).png differ diff --git a/16/umbraco-cms/fundamentals/setup/upgrading/images/fresh-8_1-site (1) (1) (3).png b/16/umbraco-cms/fundamentals/setup/upgrading/images/fresh-8_1-site (1) (1) (3).png new file mode 100644 index 00000000000..7914e6644d3 Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/upgrading/images/fresh-8_1-site (1) (1) (3).png differ diff --git a/16/umbraco-cms/fundamentals/setup/upgrading/images/fresh-8_1-site (1) (1).png b/16/umbraco-cms/fundamentals/setup/upgrading/images/fresh-8_1-site (1) (1).png new file mode 100644 index 00000000000..7914e6644d3 Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/upgrading/images/fresh-8_1-site (1) (1).png differ diff --git a/16/umbraco-cms/fundamentals/setup/upgrading/images/fresh-8_1-site (1) (2).png b/16/umbraco-cms/fundamentals/setup/upgrading/images/fresh-8_1-site (1) (2).png new file mode 100644 index 00000000000..7914e6644d3 Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/upgrading/images/fresh-8_1-site (1) (2).png differ diff --git a/16/umbraco-cms/fundamentals/setup/upgrading/images/fresh-8_1-site (1).png b/16/umbraco-cms/fundamentals/setup/upgrading/images/fresh-8_1-site (1).png new file mode 100644 index 00000000000..7914e6644d3 Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/upgrading/images/fresh-8_1-site (1).png differ diff --git a/16/umbraco-cms/fundamentals/setup/upgrading/images/fresh-8_1-site (2).png b/16/umbraco-cms/fundamentals/setup/upgrading/images/fresh-8_1-site (2).png new file mode 100644 index 00000000000..7914e6644d3 Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/upgrading/images/fresh-8_1-site (2).png differ diff --git a/16/umbraco-cms/fundamentals/setup/upgrading/images/fresh-8_1-site.png b/16/umbraco-cms/fundamentals/setup/upgrading/images/fresh-8_1-site.png new file mode 100644 index 00000000000..7914e6644d3 Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/upgrading/images/fresh-8_1-site.png differ diff --git a/16/umbraco-cms/fundamentals/setup/upgrading/images/nuget-overwrite-dialog (1) (1) (1).png b/16/umbraco-cms/fundamentals/setup/upgrading/images/nuget-overwrite-dialog (1) (1) (1).png new file mode 100644 index 00000000000..9b8dd02ba54 Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/upgrading/images/nuget-overwrite-dialog (1) (1) (1).png differ diff --git a/16/umbraco-cms/fundamentals/setup/upgrading/images/nuget-overwrite-dialog (1) (1) (2).png b/16/umbraco-cms/fundamentals/setup/upgrading/images/nuget-overwrite-dialog (1) (1) (2).png new file mode 100644 index 00000000000..9b8dd02ba54 Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/upgrading/images/nuget-overwrite-dialog (1) (1) (2).png differ diff --git a/16/umbraco-cms/fundamentals/setup/upgrading/images/nuget-overwrite-dialog (1) (1).png b/16/umbraco-cms/fundamentals/setup/upgrading/images/nuget-overwrite-dialog (1) (1).png new file mode 100644 index 00000000000..9b8dd02ba54 Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/upgrading/images/nuget-overwrite-dialog (1) (1).png differ diff --git a/16/umbraco-cms/fundamentals/setup/upgrading/images/nuget-overwrite-dialog (1).png b/16/umbraco-cms/fundamentals/setup/upgrading/images/nuget-overwrite-dialog (1).png new file mode 100644 index 00000000000..9b8dd02ba54 Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/upgrading/images/nuget-overwrite-dialog (1).png differ diff --git a/16/umbraco-cms/fundamentals/setup/upgrading/images/nuget-overwrite-dialog.png b/16/umbraco-cms/fundamentals/setup/upgrading/images/nuget-overwrite-dialog.png new file mode 100644 index 00000000000..9b8dd02ba54 Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/upgrading/images/nuget-overwrite-dialog.png differ diff --git a/16/umbraco-cms/fundamentals/setup/upgrading/images/nuget-upgrade-overwrite (1) (1) (1).png b/16/umbraco-cms/fundamentals/setup/upgrading/images/nuget-upgrade-overwrite (1) (1) (1).png new file mode 100644 index 00000000000..dbf7db39535 Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/upgrading/images/nuget-upgrade-overwrite (1) (1) (1).png differ diff --git a/16/umbraco-cms/fundamentals/setup/upgrading/images/nuget-upgrade-overwrite (1) (1) (2).png b/16/umbraco-cms/fundamentals/setup/upgrading/images/nuget-upgrade-overwrite (1) (1) (2).png new file mode 100644 index 00000000000..dbf7db39535 Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/upgrading/images/nuget-upgrade-overwrite (1) (1) (2).png differ diff --git a/16/umbraco-cms/fundamentals/setup/upgrading/images/nuget-upgrade-overwrite (1) (1).png b/16/umbraco-cms/fundamentals/setup/upgrading/images/nuget-upgrade-overwrite (1) (1).png new file mode 100644 index 00000000000..dbf7db39535 Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/upgrading/images/nuget-upgrade-overwrite (1) (1).png differ diff --git a/16/umbraco-cms/fundamentals/setup/upgrading/images/nuget-upgrade-overwrite (1).png b/16/umbraco-cms/fundamentals/setup/upgrading/images/nuget-upgrade-overwrite (1).png new file mode 100644 index 00000000000..dbf7db39535 Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/upgrading/images/nuget-upgrade-overwrite (1).png differ diff --git a/16/umbraco-cms/fundamentals/setup/upgrading/images/nuget-upgrade-overwrite.png b/16/umbraco-cms/fundamentals/setup/upgrading/images/nuget-upgrade-overwrite.png new file mode 100644 index 00000000000..dbf7db39535 Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/upgrading/images/nuget-upgrade-overwrite.png differ diff --git a/16/umbraco-cms/fundamentals/setup/upgrading/images/set-umbraco-version (1) (1) (1) (1).png b/16/umbraco-cms/fundamentals/setup/upgrading/images/set-umbraco-version (1) (1) (1) (1).png new file mode 100644 index 00000000000..c0dae2344e2 Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/upgrading/images/set-umbraco-version (1) (1) (1) (1).png differ diff --git a/16/umbraco-cms/fundamentals/setup/upgrading/images/set-umbraco-version (1) (1) (1).png b/16/umbraco-cms/fundamentals/setup/upgrading/images/set-umbraco-version (1) (1) (1).png new file mode 100644 index 00000000000..c0dae2344e2 Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/upgrading/images/set-umbraco-version (1) (1) (1).png differ diff --git a/16/umbraco-cms/fundamentals/setup/upgrading/images/set-umbraco-version (1) (1) (2).png b/16/umbraco-cms/fundamentals/setup/upgrading/images/set-umbraco-version (1) (1) (2).png new file mode 100644 index 00000000000..c0dae2344e2 Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/upgrading/images/set-umbraco-version (1) (1) (2).png differ diff --git a/16/umbraco-cms/fundamentals/setup/upgrading/images/set-umbraco-version (1) (1) (3).png b/16/umbraco-cms/fundamentals/setup/upgrading/images/set-umbraco-version (1) (1) (3).png new file mode 100644 index 00000000000..c0dae2344e2 Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/upgrading/images/set-umbraco-version (1) (1) (3).png differ diff --git a/16/umbraco-cms/fundamentals/setup/upgrading/images/set-umbraco-version (1) (1).png b/16/umbraco-cms/fundamentals/setup/upgrading/images/set-umbraco-version (1) (1).png new file mode 100644 index 00000000000..c0dae2344e2 Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/upgrading/images/set-umbraco-version (1) (1).png differ diff --git a/16/umbraco-cms/fundamentals/setup/upgrading/images/set-umbraco-version (1) (2).png b/16/umbraco-cms/fundamentals/setup/upgrading/images/set-umbraco-version (1) (2).png new file mode 100644 index 00000000000..c0dae2344e2 Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/upgrading/images/set-umbraco-version (1) (2).png differ diff --git a/16/umbraco-cms/fundamentals/setup/upgrading/images/set-umbraco-version (1).png b/16/umbraco-cms/fundamentals/setup/upgrading/images/set-umbraco-version (1).png new file mode 100644 index 00000000000..c0dae2344e2 Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/upgrading/images/set-umbraco-version (1).png differ diff --git a/16/umbraco-cms/fundamentals/setup/upgrading/images/set-umbraco-version (2).png b/16/umbraco-cms/fundamentals/setup/upgrading/images/set-umbraco-version (2).png new file mode 100644 index 00000000000..c0dae2344e2 Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/upgrading/images/set-umbraco-version (2).png differ diff --git a/16/umbraco-cms/fundamentals/setup/upgrading/images/set-umbraco-version.png b/16/umbraco-cms/fundamentals/setup/upgrading/images/set-umbraco-version.png new file mode 100644 index 00000000000..c0dae2344e2 Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/upgrading/images/set-umbraco-version.png differ diff --git a/16/umbraco-cms/fundamentals/setup/upgrading/images/upgrade-to-8_1 (1) (1).png b/16/umbraco-cms/fundamentals/setup/upgrading/images/upgrade-to-8_1 (1) (1).png new file mode 100644 index 00000000000..8104fa85371 Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/upgrading/images/upgrade-to-8_1 (1) (1).png differ diff --git a/16/umbraco-cms/fundamentals/setup/upgrading/images/upgrade-to-8_1 (1).png b/16/umbraco-cms/fundamentals/setup/upgrading/images/upgrade-to-8_1 (1).png new file mode 100644 index 00000000000..8104fa85371 Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/upgrading/images/upgrade-to-8_1 (1).png differ diff --git a/16/umbraco-cms/fundamentals/setup/upgrading/images/upgrade-to-8_1 (2).png b/16/umbraco-cms/fundamentals/setup/upgrading/images/upgrade-to-8_1 (2).png new file mode 100644 index 00000000000..8104fa85371 Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/upgrading/images/upgrade-to-8_1 (2).png differ diff --git a/16/umbraco-cms/fundamentals/setup/upgrading/images/upgrade-to-8_1.png b/16/umbraco-cms/fundamentals/setup/upgrading/images/upgrade-to-8_1.png new file mode 100644 index 00000000000..8104fa85371 Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/upgrading/images/upgrade-to-8_1.png differ diff --git a/16/umbraco-cms/fundamentals/setup/upgrading/images/upgrading-7_14 (1) (1) (1) (1).png b/16/umbraco-cms/fundamentals/setup/upgrading/images/upgrading-7_14 (1) (1) (1) (1).png new file mode 100644 index 00000000000..af0b40c8db7 Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/upgrading/images/upgrading-7_14 (1) (1) (1) (1).png differ diff --git a/16/umbraco-cms/fundamentals/setup/upgrading/images/upgrading-7_14 (1) (1) (1).png b/16/umbraco-cms/fundamentals/setup/upgrading/images/upgrading-7_14 (1) (1) (1).png new file mode 100644 index 00000000000..af0b40c8db7 Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/upgrading/images/upgrading-7_14 (1) (1) (1).png differ diff --git a/16/umbraco-cms/fundamentals/setup/upgrading/images/upgrading-7_14 (1) (1) (2).png b/16/umbraco-cms/fundamentals/setup/upgrading/images/upgrading-7_14 (1) (1) (2).png new file mode 100644 index 00000000000..af0b40c8db7 Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/upgrading/images/upgrading-7_14 (1) (1) (2).png differ diff --git a/16/umbraco-cms/fundamentals/setup/upgrading/images/upgrading-7_14 (1) (1) (3).png b/16/umbraco-cms/fundamentals/setup/upgrading/images/upgrading-7_14 (1) (1) (3).png new file mode 100644 index 00000000000..af0b40c8db7 Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/upgrading/images/upgrading-7_14 (1) (1) (3).png differ diff --git a/16/umbraco-cms/fundamentals/setup/upgrading/images/upgrading-7_14 (1) (1).png b/16/umbraco-cms/fundamentals/setup/upgrading/images/upgrading-7_14 (1) (1).png new file mode 100644 index 00000000000..af0b40c8db7 Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/upgrading/images/upgrading-7_14 (1) (1).png differ diff --git a/16/umbraco-cms/fundamentals/setup/upgrading/images/upgrading-7_14 (1) (2).png b/16/umbraco-cms/fundamentals/setup/upgrading/images/upgrading-7_14 (1) (2).png new file mode 100644 index 00000000000..af0b40c8db7 Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/upgrading/images/upgrading-7_14 (1) (2).png differ diff --git a/16/umbraco-cms/fundamentals/setup/upgrading/images/upgrading-7_14 (1).png b/16/umbraco-cms/fundamentals/setup/upgrading/images/upgrading-7_14 (1).png new file mode 100644 index 00000000000..af0b40c8db7 Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/upgrading/images/upgrading-7_14 (1).png differ diff --git a/16/umbraco-cms/fundamentals/setup/upgrading/images/upgrading-7_14 (2).png b/16/umbraco-cms/fundamentals/setup/upgrading/images/upgrading-7_14 (2).png new file mode 100644 index 00000000000..af0b40c8db7 Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/upgrading/images/upgrading-7_14 (2).png differ diff --git a/16/umbraco-cms/fundamentals/setup/upgrading/images/upgrading-7_14.png b/16/umbraco-cms/fundamentals/setup/upgrading/images/upgrading-7_14.png new file mode 100644 index 00000000000..af0b40c8db7 Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/upgrading/images/upgrading-7_14.png differ diff --git a/16/umbraco-cms/fundamentals/setup/upgrading/images/v7-content (1) (1) (1) (1).png b/16/umbraco-cms/fundamentals/setup/upgrading/images/v7-content (1) (1) (1) (1).png new file mode 100644 index 00000000000..350e15dc720 Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/upgrading/images/v7-content (1) (1) (1) (1).png differ diff --git a/16/umbraco-cms/fundamentals/setup/upgrading/images/v7-content (1) (1) (1).png b/16/umbraco-cms/fundamentals/setup/upgrading/images/v7-content (1) (1) (1).png new file mode 100644 index 00000000000..350e15dc720 Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/upgrading/images/v7-content (1) (1) (1).png differ diff --git a/16/umbraco-cms/fundamentals/setup/upgrading/images/v7-content (1) (1) (2).png b/16/umbraco-cms/fundamentals/setup/upgrading/images/v7-content (1) (1) (2).png new file mode 100644 index 00000000000..350e15dc720 Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/upgrading/images/v7-content (1) (1) (2).png differ diff --git a/16/umbraco-cms/fundamentals/setup/upgrading/images/v7-content (1) (1) (3).png b/16/umbraco-cms/fundamentals/setup/upgrading/images/v7-content (1) (1) (3).png new file mode 100644 index 00000000000..350e15dc720 Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/upgrading/images/v7-content (1) (1) (3).png differ diff --git a/16/umbraco-cms/fundamentals/setup/upgrading/images/v7-content (1) (1).png b/16/umbraco-cms/fundamentals/setup/upgrading/images/v7-content (1) (1).png new file mode 100644 index 00000000000..350e15dc720 Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/upgrading/images/v7-content (1) (1).png differ diff --git a/16/umbraco-cms/fundamentals/setup/upgrading/images/v7-content (1) (2).png b/16/umbraco-cms/fundamentals/setup/upgrading/images/v7-content (1) (2).png new file mode 100644 index 00000000000..350e15dc720 Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/upgrading/images/v7-content (1) (2).png differ diff --git a/16/umbraco-cms/fundamentals/setup/upgrading/images/v7-content (1).png b/16/umbraco-cms/fundamentals/setup/upgrading/images/v7-content (1).png new file mode 100644 index 00000000000..350e15dc720 Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/upgrading/images/v7-content (1).png differ diff --git a/16/umbraco-cms/fundamentals/setup/upgrading/images/v7-content (2).png b/16/umbraco-cms/fundamentals/setup/upgrading/images/v7-content (2).png new file mode 100644 index 00000000000..350e15dc720 Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/upgrading/images/v7-content (2).png differ diff --git a/16/umbraco-cms/fundamentals/setup/upgrading/images/v7-content.png b/16/umbraco-cms/fundamentals/setup/upgrading/images/v7-content.png new file mode 100644 index 00000000000..350e15dc720 Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/upgrading/images/v7-content.png differ diff --git a/16/umbraco-cms/fundamentals/setup/upgrading/version-specific/README.md b/16/umbraco-cms/fundamentals/setup/upgrading/version-specific/README.md new file mode 100644 index 00000000000..e0d59217ade --- /dev/null +++ b/16/umbraco-cms/fundamentals/setup/upgrading/version-specific/README.md @@ -0,0 +1,1721 @@ +--- +description: >- + This document covers specific upgrade steps if a version requires them. Most + versions do not require specific upgrade steps and you will be able to upgrade + directly from your current version. +--- + +# Version Specific Upgrades + +## Version Specific Upgrades + +Use the information below to learn about any potential breaking changes and common pitfalls when upgrading your Umbraco CMS project. + +If any specific steps are involved with upgrading to a specific version they will be listed below. + +Use the [general upgrade guide](../) to complete the upgrade of your project. + +### Breaking changes + +
+ +Umbraco 15 + +**Snapshots are removed** + +Snapshots have been removed, meaning any code using `IPublishedSnapshot`, and by extension `IPublishedSnapshotAccessor`, must be updated. Inject `IPublishedContentCache` or `IPublishedMediaCache` and use those directly instead. + +**Modelsbuilder models needs to be rebuilt** + +Models generated by ModelsBuilder used the `IPublishedSnapshot` interface, which has been removed. This means that the models need to be rebuilt. The approach to this will differ depending on the mode chosen: + +**InMemoryAuto** + +Remove the `umbraco\Data\TEMP\InMemoryAuto` folder to trigger a rebuild of the models. + +**SourceCodeAuto and SourceCodeManual** + +Remove the old models located in the `\umbraco\models` folder by default. This will cause your views to no longer be able to build due to missing types. To get around this you can disable the precompiled view temporarily by adding the following to your `.csproj` file: + +```xml + + false + false + +``` + +This will allow your site to start up, but you will still see an error page when loading a page. + +1. Disregard the error. +2. Enter the backoffice. +3. Rebuild the models from the ModelsBuilder dashboard. + +You can now re-enable precompiled views and rebuild your site. + +If you have custom C# code that references the models this will also not build. You can either comment out your custom code temporarily until the models have been rebuilt or fix the models manually. To fix the models manually you need to find and replace `IPublishedSnapshotAccessor` with `IPublishedContentTypeCache`. + +**Handling Precompressed Files** + +When upgrading from Umbraco 14 to 15, you might notice that `JavaScript` and `CSS` files are automatically precompressed, adding additional `.br` and `.gz` files. This behavior is introduced in ASP.NET Core version 9, where static files are fingerprinted and precompressed by default at build and publish time. + +To disable this feature, set `false` in your project file. If you are using Umbraco's templates - `dotnet new umbraco`, this setting is already included. + +Set `false` in your Umbraco project to avoid compressing backoffice files unnecessarily. For your own web project, set it to `true` to improve performance by serving precompressed assets to users. + +For more details, see the [ASP.NET Core Documentation](https://learn.microsoft.com/en-us/aspnet/core/migration/80-90?view=aspnetcore-9.0&tabs=visual-studio#replace-usestaticfiles-with-mapstaticassets). + +
+ +
+ +Umbraco 14 + +Read more about the release of Umbraco 14 in the [Blog Post](https://umbraco.com/blog/umbraco-14-release/). + +Below you can find the list of breaking changes introduced in Umbraco 14 CMS. + +* [**AngularJS removed: A new backoffice built with Web Components, Lit, and fueled by the Umbraco UI Library**](https://github.com/umbraco/Umbraco.CMS.Backoffice) + +This is by far the most impactful update of Umbraco in years. We’ve fundamentally changed the way you extend Umbraco. If you are experienced in developing Web Components you can now use your preferred framework for this. If you are unsure how to proceed, you can implement it with Typescript and the Lit library like we’ve done. In this case, please start with this article on how to [customize the Backoffice](https://docs.umbraco.com/umbraco-cms/extending/customize-backoffice). + +The new Backoffice (Bellissima) is entirely built on the Umbraco UI Library. This means that you might experience some of your components not being rendered on the page because the name has been changed. You should be able to find equivalents to what you were used to. For example, the `umb-button` is now called `uui-button`, and `umb-box` is now `uui-box`. When extending the Backoffice, we encourage you to use our [Umbraco UI Library](https://uui.umbraco.com/) to ensure the same look and feel in your extensions. The UI Library is Open Source and [hosted on GitHub](https://github.com/umbraco/Umbraco.UI), so feel free to contribute with new components or raise issues or discussions. + +* **Icons are based on Lucide.** + +Umbraco 13 and earlier used sets of icons ranging from custom SVGs to Font Awesome. This has all been converged into the [Lucide icon pack](https://docs.umbraco.com/umbraco-cms/extending/ui-documentation#ui-icons) with icon names mapped from Umbraco 13. + +* **Custom icons** + +To add custom icons to the Backoffice, you must add an extension type called “icons”, which can provide icons given a Name and a File. The file can reside anywhere on disk or in RCLs the only requirements being that it must be routable and it must be an SVG. + +* **User provided translations** + +Translations used in the UI (which are most of them) have been migrated from XML to JavaScript modules. This means that if you wish to override any of the built-in translation keys for the UI, you have to add an extension type called “localization”. It is still possible to add XML translations, but they can no longer be used in the Backoffice UI. However, you may still find usage for them in server-to-server scenarios. Umbraco also keeps its e-mail templates as XML translations. Package and extension developers working with localization will find many benefits from this change seeing that you can add logic to JavaScript modules making your localization files more dynamic and even making them able to react to input parameters. + +You can read more about [localization on the Umbraco Documentation](https://docs.umbraco.com/umbraco-cms/extending/language-files). + +* **BackOffice controllers have been replaced with the Management API** + +Following the implementation of the new Backoffice (Bellissima), Umbraco has now internally upgraded Headless to a first-class citizen. This means that all controllers previously available under the `/umbraco/api` route have been removed and replaced with controllers in the Management API. You can read more about the Management API on the [Management API](https://docs.umbraco.com/umbraco-cms/reference/management-api) article. You can also check out the Swagger UI in your local Umbraco instance available under `/umbraco/swagger`. + +* **A new way of writing authorized controllers** + +If you have implemented API controllers in Umbraco before, we recommend you update or rewrite these. Please follow the [Documenting your Controllers](https://docs.umbraco.com/umbraco-cms/tutorials/creating-a-backoffice-api/documenting-your-controllers) article, as you’ll then ensure the same cool documentation of your APIs. Notice, that we’ve made a much better separation of concern between controllers and services so that there is no more business logic in controllers. + +* [**Migration from Newtonsoft.Json to the System.Text.Json which removes Nested Content and Grid value converter and so on**](https://github.com/umbraco/Umbraco-CMS/pull/15728) + +Although this sounds like it's not a big change, it’s one of the most breaking changes on the backend. Whereas Newtonsoft.Json was flexible and error-tolerant by default, System.Text.Json is strict but more secure by default. You can therefore run into things that will not be serialized. You can [read more about the differences between Newtonsoft.Json and System.Text.Json here](https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json/migrate-from-newtonsoft?pivots=dotnet-9-0). + +* **Nested Content and Grid Layout have been removed** + +These two property editors have been deprecated in Umbraco for some time as you can read in our breaking change announcements for [Nested Content](https://github.com/umbraco/Announcements/issues/6) and [Grid Layout](https://github.com/umbraco/Announcements/issues/7). The recommended action is to use blocks instead - Block Grid for the grid layout and either Block Grid or Block List for Nested Content. + +* [**The legacy media picker has been removed**](https://github.com/umbraco/Umbraco-CMS/pull/15835) + +We have for some time [encouraged to not use the legacy Media Picker](https://github.com/umbraco/Announcements/issues/8), and now it’s fully removed. You should use the default Media Picker instead. + +* **Macros and Partial View Macros have been removed. Use partial views and/or blocks in the Rich Text Editor (RTE)** + +Depending on the usage of macros, you’ll be able to use either partial views or blocks in the Rich Text Editor. They are not the same kind of functionality, but they cover all the identified use cases in a way more consistent and supportable way. + +* **XPath has been removed** + +An alternative is using the Dynamic Roots in the Multinode Treepicker and for ContentXPath the alternative is [IContentLastChanceFinder](https://docs.umbraco.com/umbraco-cms/tutorials/custom-error-page). + +* [**The package manifest format has changed**](https://docs.umbraco.com/umbraco-cms/extending/property-editors/package-manifest) + +The `package.manifest` file is no longer supported and has been replaced with the `umbraco-package.json` file. The format is similar and after building your Umbraco solution, you have access to a JSON schema file which you can reference and thereby have type-safety in the file. You can read more about the new format on the [Package Manifest](https://docs.umbraco.com/umbraco-cms/extending/property-editors/package-manifest) article. + +* **Smidge is no longer a default dependency** + +[Smidge has been removed from the default installation](https://github.com/umbraco/Umbraco-CMS/pull/15788) along with the RuntimeMinification setting and related classes. Smidge used to bundle up Backoffice and package assets before, however, with the Bellissima, we have migrated entirely to ESModules. This means we can no longer predict how modules work in automated bundles. + +It's recommended that you bundle up your Backoffice static assets for instance by a tool called Vite. You can read more about this on the [Vite Package Setup](https://docs.umbraco.com/umbraco-cms/extending/customize-backoffice/vite-package-setup) article. You can still use libraries like Smidge for frontend static assets by manually installing the package from NuGet. + +You can read the [Smidge documentation](https://github.com/Shazwazza/Smidge/wiki) on how to set up a similar setting to RuntimeMinification.\ +For sites being upgraded from V13 or below, please remove [these two lines](https://github.com/umbraco/Umbraco-CMS/blob/04ed514a21279ae82d95b34c55cb2ba96545eb39/src/Umbraco.Web.UI/Views/\_ViewImports.cshtml#L7-L8) from the `_ViewImports.cshtml` file. + +* **Base classes for Backoffice controllers have been removed** + +The `UmbracoAuthorizedApiController` and `UmbracoAuthorizedJsonController` classes have been removed. We recommend basing your Backoffice APIs on the `ManagementApiControllerBase` class from the `Umbraco.Cms.Api.Management` project. + +Read the [Creating a Backoffice API article](https://docs.umbraco.com/umbraco-cms/tutorials/creating-a-backoffice-api) for a comprehensive guide to writing APIs for the Management API. + +* **Removal of certain AppSettings** + +Some AppSettings have been removed or found a new place. In general, any UI-related app settings will now have to be configured as [extensions through the manifest system](https://docs.umbraco.com/umbraco-cms/extending/backoffice-setup/extension-types). + +* **RichTextEditor** + +The global configuration of TinyMCE has been removed in order to support more rich text editors in the future. Instead, a new extension type called “tinyMcePlugin” has been added. This extension type gives you access to each instance of TinyMCE allowing you to configure it exactly as you see fit. You can even make it dependent on more factors such as Document Type, user group, environment, and much more. You have access to the full array of contexts in the Backoffice. + +* **ShowDeprecatedPropertyEditors** + +There are no deprecated property editors in Bellissima and this configuration will no longer have an effect. + +* **HideBackOfficeLogo** + +This configuration will no longer have an effect. Instead, you can add a CSS file where you can modify the default CSS variables in the Backoffice. The Backoffice loads a CSS file which can be overwritten by placing a similar file in your project at `/umbraco/backoffice/css/user-defined.css` with the following content: + +```css +:root { + --umb-header-logo-display: none; +} +``` + +* **IconsPath** + +This configuration will no longer have an effect. Instead, you should add icons through the “icons” extension type. + +* **AllowedMediaHosts** + +This configuration will no longer have an effect. + +* **Notifications** + +Notifications have changed their behavior to an extent. You can still implement notifications such as `ContentSavingNotification` to react to changes for related systems or add messages to be shown in the Backoffice. You can no longer modify the models being transferred through the API. + +If you wish to modify the Backoffice UI, register the extensions through the manifest system that hook on to the desired areas of the Backoffice UI. If you used to modify the models because you needed more data on the models, it's recommended that you build your own API controller to achieve this. You can read more about [building custom API controllers on the Umbraco Documentation](https://docs.umbraco.com/umbraco-cms/reference/custom-swagger-api) and even learn how to register your controllers in the Swagger UI. + +* **Property editors have been split in two** + +The new Backoffice and the Management API ushers in a shift in responsibility. Traditionally, a lot of UI responsibility has been misplaced on the server. + +For example, it is hardly a server concern how many rows a text area should span by default. + +To this end, property editors have been split into two, individually reusable parts; the server implementation and the client implementation. + +As a consequence, a property editor must now define both which client and server implementation to use. Details can be found in [Creating a Property Editor](https://docs.umbraco.com/umbraco-cms/tutorials/creating-a-property-editor) article. + +* **Property value converters for package.manifest based property editors** + +The `package.manifest` file format is no longer known on the server side. It has been changed to be a purely client-side responsibility (and it has adopted a new format and a new name). If you have implemented a property value converter for a property editor defined in a `package.manifest` file, you will likely need to make a small change to the property value converter. + +The property value converter must implement `IsConverter()` to determine if it can handle a given property. In this implementation, it is common to use the EditorAlias of the passed in IPublishedPropertyType. However, in Umbraco 14 you should use the EditorUiAlias instead. + +More details can be found in [this article](https://docs.umbraco.com/umbraco-cms/tutorials/creating-a-property-editor/custom-value-conversion-for-rendering). + +* **UmbracoApiController breakage** + +Due to the shift from `Newtonsoft.Json` to the `System.Text.Json`, the `UmbracoApiController` will yield camel-cased output in Umbraco 14, where it was pascal-cased in the previous versions. + +Make sure to perform thorough testing of all usages of `UmbracoApiController`. As the class has now been marked as obsolete, we recommend these controllers to be based on the Controller class from ASP.NET Core moving forward. + +* **Two-Factor Authentication requires a client registration** + +The C# models for Two-Factor Authentication previously supported setting a custom AngularJS view to setup the QR code. This has been moved to the Backoffice client and requires a registration through the new extension type [`mfaProvider`](../../../../extending/property-editors/package-manifest/): + +```typescript + { + "type": "mfaLoginProvider", + "alias": "my.2fa.provider", + "name": "My 2fa Provider", + "forProviderName": "UmbracoUserAppAuthenticator", + "meta": { + "label": "Authenticate with a 2FA code" + } + } +``` + +This will use Umbraco’s default configuration of the two-factor provider. The user scans a QR code using an app such as Google Authenticator or Authy by Twilio. + +It is additionally possible to register your own configurator similar to Umbraco 13. You can achieve this by providing a custom JavaScript element through the `elementJs` property. + +More details and code examples can be found in the [Two-Factor Authentication](../../../../reference/security/two-factor-authentication.md) article. + +* **External Login Providers require a client registration** + +The C# models for External Login Providers have changed and no longer hold configuration options for the “Sign in with XYZ” button. To show a button in the Backoffice to sign in with an external provider, you need to register this through the extension type called [`authProvider`](../../../../customizing/package-manifest.md) : + +```typescript + { + "type": "authProvider", + "alias": "My.AuthProvider.Google", + "name": "Google Auth Provider", + "forProviderName": "Umbraco.Google", + "meta": { + "label": "Google", + "defaultView": { + "icon": "icon-google" + }, + "linking": { + "allowManualLinking": true + } + } + } + +``` + +This will use Umbraco’s default button to sign in with the provider. You can also choose to provide your own element to show in the login form. You can achieve this by adding the `elementJs` property. + +Additionally, on the backend side, there is an additional helper available to do proper error handling. You can utilize this by using the options pattern to configure the provider. + +More details and code examples can be found in the [External Login Providers](../../../../reference/security/external-login-providers.md) article.\\ + +* **Deprecated SQLite provider name removed** + +In previous versions of Umbraco the `umbracoDbDSN_ProviderName` (and `umbracoCommerceDbDSN_ProviderName`) value could be `Microsoft.Data.SQLite` or `Microsoft.Data.Sqlite` with the former being deprecated in Umbraco 12. + +The deprecated version, `Microsoft.Data.SQlite`, has been removed and will require the value to be updated to `Microsoft.Data.Sqlite` for installations using an SQLite database. + +```json +{ + ... + "ConnectionStrings": { + "umbracoDbDSN": "Data Source=|DataDirectory|/Umbraco.sqlite.db;Cache=Private;Foreign Keys=True;Pooling=True", + "umbracoDbDSN_ProviderName": "Microsoft.Data.Sqlite", + "umbracoCommerceDbDSN": "Data Source=|DataDirectory|/Umbraco.Commerce.sqlite.db;Mode=ReadWrite;Foreign Keys=True;Pooling=True;Cache=Private", + "umbracoCommerceDbDSN_ProviderName": "Microsoft.Data.Sqlite" + }, + ... +} + +``` + +**In-depth and further breaking changes for Umbraco 14 can be found on the** [**CMS GitHub**](https://github.com/umbraco/Umbraco-CMS/pulls?q=is%3Apr+base%3Av14%2Fdev+label%3Acategory%2Fbreaking) **repository and on** [**Our Website**](https://our.umbraco.com/download/releases/1400)**.** + +
+ +
+ +Umbraco 14 RC Versions + +Below you can find the list of breaking changes introduced in Umbraco 14 RC release versions. + +**RC 5** + +* [Bellissima (frontend/backoffice) changes](https://github.com/umbraco/Umbraco.CMS.Backoffice/releases/tag/v14.0.0-rc5) +* [Backend (CMS) changes](https://github.com/umbraco/Umbraco-CMS/releases/tag/release-14.0.0-rc5) + +**RC 4** + +* [Bellissima (frontend/backoffice) changes](https://github.com/umbraco/Umbraco.CMS.Backoffice/releases/tag/v14.0.0-rc4) +* [Backend (CMS) changes](https://github.com/umbraco/Umbraco-CMS/releases/tag/release-14.0.0-rc4) + +**RC 3** + +* [Bellissima (frontend/backoffice) changes](https://github.com/umbraco/Umbraco.CMS.Backoffice/releases/tag/v14.0.0-rc3) +* [Backend (CMS) changes](https://github.com/umbraco/Umbraco-CMS/releases/tag/release-14.0.0-rc3) + +**RC 2** + +* [Bellissima (frontend/backoffice) changes](https://github.com/umbraco/Umbraco.CMS.Backoffice/releases/tag/v14.0.0-rc2) +* [Backend (CMS) changes](https://github.com/umbraco/Umbraco-CMS/releases/tag/release-14.0.0-rc2) + +**RC 1**\ +First RC release - 17th of April. Breaking changes since Beta 3: + +* [Bellissima (frontend/backoffice) changes](https://github.com/umbraco/Umbraco.CMS.Backoffice/releases/tag/v14.0.0-rc1) +* [Backend (CMS) changes](https://github.com/umbraco/Umbraco-CMS/releases/tag/release-14.0.0-rc1) + +
+ +
+ +Umbraco 14 Beta Versions + +Below you can find the list of breaking changes introduced in Umbraco 14 Beta release versions. + +**Beta 3** + +* [Bellissima (frontend/backoffice) changes](https://github.com/umbraco/Umbraco.CMS.Backoffice/releases/tag/v14.0.0-beta003) +* [Backend (CMS) changes](https://github.com/umbraco/Umbraco-CMS/releases/tag/release-14.0.0-beta003) + +**Beta 2** + +There are a few breaking changes since **Beta 1**. Most of the changes concern property editors and getting them to work with migrations as well as new values. + +* [Bellissima (frontend/backoffice) changes](https://github.com/umbraco/Umbraco.CMS.Backoffice/releases/tag/v14.0.0-beta002) +* [Backend (CMS) changes](https://github.com/umbraco/Umbraco-CMS/releases/tag/release-14.0.0-beta002) + +**Beta 1**\ +Official release of Beta, 6th March 2023. + +* [Bellissima (frontend/backoffice) changes](https://github.com/umbraco/Umbraco.CMS.Backoffice/releases/tag/v14.0.0-beta001) +* [Backend (CMS) changes](https://github.com/umbraco/Umbraco-CMS/releases/tag/release-14.0.0-beta001) + +
+ +
+ +Umbraco 13 + +Below you can find the list of breaking changes introduced in Umbraco 13. + +* [Use ISO codes instead of language IDs for fallback languages and translations](https://github.com/umbraco/Umbraco-CMS/issues/13751) +* [Breaking changes for the Delivery API](https://github.com/umbraco/Umbraco-CMS/issues/14745) +* [V13: New login screen](https://github.com/umbraco/Umbraco-CMS/issues/14780) +* [Updated NuGet Dependencies](https://github.com/umbraco/Umbraco-CMS/issues/14795) +* [Fix \`JsonNetSerializer\` settings leaking into derived implementations](https://github.com/umbraco/Umbraco-CMS/issues/14814) +* [Add default property value converters for all value types](https://github.com/umbraco/Umbraco-CMS/issues/14869) +* [V13: Add config to limit concurrent logins](https://github.com/umbraco/Umbraco-CMS/issues/14989) +* [Updates and support for re-use of CMS logic in Deploy](https://github.com/umbraco/Umbraco-CMS/issues/14990) +* [Dont explicitly index nested property by default](https://github.com/umbraco/Umbraco-CMS/issues/15028) +* [Blocks in the Rich Text Editor](https://github.com/umbraco/Umbraco-CMS/issues/15029) +* [Fix FurthestAncestorOrSelfDynamicRootQueryStep and FurthestDescendantOrSelfDynamicRootQueryStep](https://github.com/umbraco/Umbraco-CMS/issues/15113) +* [Remove parameter value/return nullability in \`IImageSourceParser\`, \`ILocalLinkParser\` and \`IMacroParser\`](https://github.com/umbraco/Umbraco-CMS/issues/15130) +* [Update PackageMigrationsPlans collection to be Weighted and not Lazy](https://github.com/umbraco/Umbraco-CMS/issues/15138) +* [Move IContextCache parameter to base Deploy interfaces and add checksum to artifact dependency](https://github.com/umbraco/Umbraco-CMS/issues/15144) +* [V13: Update IWebHookService to proper casing](https://github.com/umbraco/Umbraco-CMS/issues/15169) +* [V13: Implement webhook as i entity](https://github.com/umbraco/Umbraco-CMS/issues/15267) +* [Change \`WebhookEventCollectionBuilder\` to set collection](https://github.com/umbraco/Umbraco-CMS/issues/15351) +* [V13: Log webhook firing exceptions when they happen](https://github.com/umbraco/Umbraco-CMS/issues/15393) +* [Remove date header from webhook request and use constants](https://github.com/umbraco/Umbraco-CMS/issues/15407) + +You can find more information about all breaking changes for v13.0.0 on [Our Umbraco](https://our.umbraco.com/download/releases/1300) website. + +**Note:** You need to be aware of some things if you are using EF Core, and have installed the `Microsoft.EntityFrameworkCore.Design 8.0.0` package: + +* This package has a transient dependency to `Microsoft.CodeAnalysis.Common` which clashes with the same transient dependency from `Umbraco.Cms 13.0.0`. This happens because `Microsoft.EntityFrameworkCore.Design 8.0.0` requires `Microsoft.CodeAnalysis.CSharp.Workspaces` in v4.5.0 or higher. +* If there are no other dependencies that need that package then it installs it in the lowest allowed version (4.5.0). That package then has a strict dependency on `Microsoft.CodeAnalysis.Common` version 4.5.0. The problem is `Umbraco.Cms` through its own transient dependencies that require the version of `Microsoft.CodeAnalysis.Common` to be >= 4.8.0. +* This can be fixed by installing `Microsoft.CodeAnalysis.CSharp.Workspaces` version 4.8.0 as a specific package instead of leaving it as a transient dependency. This is because it will then have a strict transient dependency on `Microsoft.CodeAnalysis.Common` version 4.8.0, which is the same that Umbraco has. + +
+ +
+ +Umbraco 12 + +Umbraco 12 does not include many binary breaking changes, but there are some. + +Most notable is a functional breaking change in Migrations, that from Umbraco 12. Each translation will be executed in its own transactions instead of all migrations in one big transaction. This change has been made to ease the support for Sqlite. + +**A type, enum, record, or struct visible outside the assembly is missing in the compared assembly when required to be present.** + +* PagedModel has moved namespace from Umbraco.New.Cms.Core.Models to Umbraco.Cms.Core.Models +* Umbraco.Cms.Infrastructure.Migrations.PostMigrations.ClearCsrfCookies is removed. The functionality can be archived by implementing a notification handler for the new UmbracoPlanExecutedNotification. +* Umbraco.Cms.Core.Cache.DistributedCacheBinder is now divided into separate files for each notification handler +* Umbraco.Cms.Infrastructure.Migrations.PostMigrations.DeleteLogViewerQueryFile was a no-op method removed. +* Umbraco.Cms.Infrastructure.Migrations.PostMigrations.RebuildPublishedSnapshot replaced with a RebuildCache flag on the MigrationBase + +**A member that is visible outside of the assembly is missing in the compared assembly when required to be present.** + +* Umbraco.Cms.Core.Migrations.IMigrationPlanExecutor.Execute(Umbraco.Cms.Infrastructure.Migrations.MigrationPlan,System.String) replaced with Umbraco.Cms.Core.Migrations.IMigrationPlanExecutor.ExecutePlan(Umbraco.Cms.Infrastructure.\* \* Migrations.MigrationPlan,System.String) that returns an rich object instead of a string +* Umbraco.Cms.Infrastructure.Migrations.IMigrationContext.AddPostMigration\`\`1 Removed and replaced with notification +* Umbraco.Cms.Infrastructure.Migrations.MigrationPlan.AddPostMigration\`\`1 +* Removed and replaced with notification +* Umbraco.Cms.Infrastructure.Migrations.MigrationPlan.get\_PostMigrationTypes removed. +* Umbraco.Cms.Infrastructure.Migrations.Upgrade.Upgrader.Execute(Umbraco.Cms.Core.Migrations.IMigrationPlanExecutor,Umbraco.Cms.Core.Scoping.IScopeProvider,Umbraco.Cms.Core.Services.IKeyValueService) was obsolete and is replaced by method taking a ICoreScopeProvider instead of a IScopeProvider + +**An abstract member was added to the right side of the comparison to an unsealed type.** + +* PublishedPropertyBase now requires inheritors to implement GetDeliveryApiValue(System.Boolean,System.String,System.String) + +**A member was added to an interface without a default implementation.** + +* Umbraco.Cms.Core.Events.IEventAggregator.Publish`2(System.Collections.Generic.IEnumerable{`0}) +* Umbraco.Cms.Core.Events.IEventAggregator.PublishAsync`2(System.Collections.Generic.IEnumerable{`0},System.Threading.CancellationToken) +* Umbraco.Cms.Core.Models.PublishedContent.IPublishedProperty.GetDeliveryApiValue(System.Boolean,System.String,System.String) +* Umbraco.Cms.Core.Models.PublishedContent.IPublishedPropertyType.ConvertInterToDeliveryApiObject(Umbraco.Cms.Core.Models.PublishedContent.IPublishedElement,Umbraco.Cms.Core.PropertyEditors.PropertyCacheLevel,System.Object,System.Boolean,System.Boolean) +* Umbraco.Cms.Core.Models.PublishedContent.IPublishedPropertyType.ConvertInterToDeliveryApiObject(Umbraco.Cms.Core.Models.PublishedContent.IPublishedElement,Umbraco.Cms.Core.PropertyEditors.PropertyCacheLevel,System.Object,System.Boolean) +* Umbraco.Cms.Core.Models.PublishedContent.IPublishedPropertyType.DeliveryApiCacheLevel +* Umbraco.Cms.Core.Scoping.ICoreScope.Locks +* Umbraco.Cms.Core.Migrations.IMigrationPlanExecutor.ExecutePlan(Umbraco.Cms.Infrastructure.Migrations.MigrationPlan,System.String) +* Umbraco.Cms.Infrastructure.Search.IUmbracoIndexingHandler.RemoveProtectedContent +* Umbraco.Cms.Infrastructure.Examine.IUmbracoIndex.SupportProtectedContent + +
+ +
+ +Umbraco 11 + +Most breaking changes are introduced due to **updated dependencies**. The breaking changes in .NET 7 and ASP.NET Core 7 are documented by [Microsoft](https://learn.microsoft.com/en-us/dotnet/core/compatibility/7.0). + +Besides the documented changes, we have also seen a few method signatures that are changed to support Nullable-Reference-Types. + +If you are using **TinyMCE** plugins or custom TinyMCE configuration you need to migrate to the latest version. Learn more about this in the [Rich Text Editor documentation](../../../backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor/). + +The breaking changes in TinyMCE are also documented in the official migration guides for [version 4 to 5](https://www.tiny.cloud/docs/migration-from-4x/) and from [version 5 to 6](https://www.tiny.cloud/docs/tinymce/6/migration-from-5x/). + +The breaking changes in Umbraco 11 are mainly the removal of classes, methods, and so on, marked as obsolete in Umbraco 9. + +A few methods and classes have also been moved and changed namespace. Decoupled dependencies are documented on the [Umbraco Announcements repository](https://github.com/umbraco/Announcements/issues/5). + +The full list of API-breaking changes can be found below. + +**Obsolete code removed** + +The following have been removed after having been obsoleted since Umbraco 9. + +**Umbraco.Extensions** + +```csharp +Umbraco.Extensions.ServiceCollectionExtensions.AddUnique(Microsoft.Extensions.DependencyInjection.IServiceCollection) + +Umbraco.Extensions.EnumExtensions.HasFlagAll(T, T) + +Umbraco.Extensions.FriendlyImageCropperTemplateExtensions.GetLocalCropUrl(Umbraco.Cms.Core.Models.MediaWithCrops, string, string?) +``` + +**Umbraco.Cms.Core** + +```csharp +Umbraco.Cms.Core.Constants.Conventions.Member.IsApproved +Umbraco.Cms.Core.Constants.Conventions.Member.IsApprovedLabel +Umbraco.Cms.Core.Constants.Conventions.Member.IsLockedOut +Umbraco.Cms.Core.Constants.Conventions.Member.IsLockedOutLabel +Umbraco.Cms.Core.Constants.Conventions.Member.LastLoginDate +Umbraco.Cms.Core.Constants.Conventions.Member.LastLoginDateLabel +Umbraco.Cms.Core.Constants.Conventions.Member.LastPasswordChangeDate +Umbraco.Cms.Core.Constants.Conventions.Member.LastPasswordChangeDateLabel +Umbraco.Cms.Core.Constants.Conventions.Member.LastLockoutDate +Umbraco.Cms.Core.Constants.Conventions.Member.LastLockoutDateLabel +Umbraco.Cms.Core.Constants.Conventions.Member.FailedPasswordAttempts +Umbraco.Cms.Core.Constants.Conventions.Member.FailedPasswordAttemptsLabel + +Umbraco.Cms.Core.WebAssets.IRuntimeMinifier.Reset() + +Umbraco.Cms.Core.Services.IExternalLoginService + +Umbraco.Cms.Core.Services.ExternalLoginService.ExternalLoginService( + Umbraco.Cms.Core.Scoping.ICoreScopeProvider, + Microsoft.Extensions.Logging.ILoggerFactory, + Umbraco.Cms.Core.Events.IEventMessagesFactory, + Umbraco.Cms.Core.Persistence.Repositories.IExternalLoginRepository) + +Umbraco.Cms.Core.Services.ExternalLoginService.GetExternalLogins(int) + +Umbraco.Cms.Core.Services.ExternalLoginService.GetExternalLoginTokens(int) + +Umbraco.Cms.Core.Services.ExternalLoginService.Save(int, + System.Collections.Generic.IEnumerable) + +Umbraco.Cms.Core.Services.ExternalLoginService.Save(int, + System.Collections.Generic.IEnumerable) + +Umbraco.Cms.Core.Services.ExternalLoginService.DeleteUserLogins(int) + +Umbraco.Cms.Core.Services.IMacroWithAliasService + +Umbraco.Cms.Core.Services.ITwoFactorLoginService2 + +Umbraco.Cms.Core.Services.LocalizedTextService.LocalizedTextService( + System.Collections.Generic.IDictionary>>, + Microsoft.Extensions.Logging.ILogger) + +Umbraco.Cms.Core.Services.ServiceContext.ServiceContext( + System.Lazy?, + System.Lazy?, + System.Lazy?, + System.Lazy?, + System.Lazy?, + System.Lazy?, + System.Lazy?, + System.Lazy?, + System.Lazy?, + System.Lazy?, + System.Lazy?, + System.Lazy?, + System.Lazy?, + System.Lazy?, + System.Lazy?, + System.Lazy?, + System.Lazy?, + System.Lazy?, + System.Lazy?, + System.Lazy?, + System.Lazy?, + System.Lazy?, + System.Lazy?, + System.Lazy?, + System.Lazy?, + System.Lazy?, + System.Lazy?) + +Umbraco.Cms.Core.Services.ServiceContext.CreatePartial( + Umbraco.Cms.Core.Services.IContentService?, + Umbraco.Cms.Core.Services.IMediaService?, + Umbraco.Cms.Core.Services.IContentTypeService?, + Umbraco.Cms.Core.Services.IMediaTypeService?, + Umbraco.Cms.Core.Services.IDataTypeService?, + Umbraco.Cms.Core.Services.IFileService?, + Umbraco.Cms.Core.Services.ILocalizationService?, + Umbraco.Cms.Core.Services.IPackagingService?, + Umbraco.Cms.Core.Services.IEntityService?, + Umbraco.Cms.Core.Services.IRelationService?, + Umbraco.Cms.Core.Services.IMemberGroupService?, + Umbraco.Cms.Core.Services.IMemberTypeService?, + Umbraco.Cms.Core.Services.IMemberService?, + Umbraco.Cms.Core.Services.IUserService?, + Umbraco.Cms.Core.Services.ITagService?, + Umbraco.Cms.Core.Services.INotificationService?, + Umbraco.Cms.Core.Services.ILocalizedTextService?, + Umbraco.Cms.Core.Services.IAuditService?, + Umbraco.Cms.Core.Services.IDomainService?, + Umbraco.Cms.Core.Services.IMacroService?, + Umbraco.Cms.Core.Services.IPublicAccessService?, + Umbraco.Cms.Core.Services.IExternalLoginService?, + Umbraco.Cms.Core.Services.IServerRegistrationService?, + Umbraco.Cms.Core.Services.IRedirectUrlService?, + Umbraco.Cms.Core.Services.IConsentService?, + Umbraco.Cms.Core.Services.IKeyValueService?, + Umbraco.Cms.Core.Services.IContentTypeBaseServiceProvider?) + +Umbraco.Cms.Core.Services.TwoFactorLoginService.TwoFactorLoginService( + Umbraco.Cms.Core.Persistence.Repositories.ITwoFactorLoginRepository, + Umbraco.Cms.Core.Scoping.ICoreScopeProvider, + System.Collections.Generic.IEnumerable, + Microsoft.Extensions.Options.IOptions, + Microsoft.Extensions.Options.IOptions) + +Umbraco.Cms.Core.Routing.DefaultUrlProvider.DefaultUrlProvider( + Microsoft.Extensions.Options.IOptionsMonitor, + Microsoft.Extensions.Logging.ILogger, + Umbraco.Cms.Core.Routing.ISiteDomainMapper, + Umbraco.Cms.Core.Web.IUmbracoContextAccessor, + Umbraco.Cms.Core.Routing.UriUtility) + +Umbraco.Cms.Core.Persistence.Repositories.IExternalLoginRepository + +Umbraco.Cms.Core.Persistence.Repositories.IMacroWithAliasRepository + +Umbraco.Cms.Core.Persistence.Repositories.IMemberRepository.SetLastLogin(string, System.DateTime) + +Umbraco.Cms.Core.Notifications.UmbracoApplicationComponentsInstallingNotification + +Umbraco.Cms.Core.Notifications.UmbracoApplicationMainDomAcquiredNotification + + +Umbraco.Cms.Core.Notifications.UmbracoApplicationStartingNotification.UmbracoApplicationStartingNotification(Umbraco.Cms.Core.RuntimeLevel) + +Umbraco.Cms.Core.Notifications.UmbracoApplicationStoppingNotification.UmbracoApplicationStoppingNotification() + +Umbraco.Cms.Core.Models.IContentTypeWithHistoryCleanup + +Umbraco.Cms.Core.Models.Language.Language(Umbraco.Cms.Core.Configuration.Models.GlobalSettings, string) + +Umbraco.Cms.Core.Models.RelationType.RelationType(string, string, bool, System.Nullable, System.Nullable) + +Umbraco.Cms.Core.Models.PublishedContent.PublishedContentType.PublishedContentType(int, string, + Umbraco.Cms.Core.Models.PublishedContent.PublishedItemType, + System.Collections.Generic.IEnumerable, + System.Collections.Generic.IEnumerable, + Umbraco.Cms.Core.Models.ContentVariation, + bool) + +Umbraco.Cms.Core.Models.PublishedContent.PublishedContentType.PublishedContentType(int, string, + Umbraco.Cms.Core.Models.PublishedContent.PublishedItemType, System.Collections.Generic.IEnumerable, + System.Func>, + Umbraco.Cms.Core.Models.ContentVariation, + bool) + +Umbraco.Cms.Core.Models.Mapping.ContentTypeMapDefinition.ContentTypeMapDefinition( + Umbraco.Cms.Core.Models.Mapping.CommonMapper, + Umbraco.Cms.Core.PropertyEditors.PropertyEditorCollection, + Umbraco.Cms.Core.Services.IDataTypeService, + Umbraco.Cms.Core.Services.IFileService, + Umbraco.Cms.Core.Services.IContentTypeService, + Umbraco.Cms.Core.Services.IMediaTypeService, + Umbraco.Cms.Core.Services.IMemberTypeService, + Microsoft.Extensions.Logging.ILoggerFactory, + Umbraco.Cms.Core.Strings.IShortStringHelper, + Microsoft.Extensions.Options.IOptions, + Umbraco.Cms.Core.Hosting.IHostingEnvironment) + +Umbraco.Cms.Core.Models.ContentEditing.UserGroupPermissionsSave.Validate(System.ComponentModel.DataAnnotations.ValidationContext) + +Umbraco.Cms.Core.Install.InstallSteps.TelemetryIdentifierStep.TelemetryIdentifierStep( + Microsoft.Extensions.Logging.ILogger, + Microsoft.Extensions.Options.IOptions, + Umbraco.Cms.Core.Configuration.IConfigManipulator) + +Umbraco.Cms.Core.IO.ViewHelper.ViewHelper(Umbraco.Cms.Core.IO.IFileSystem) + +Umbraco.Cms.Core.HealthChecks.Checks.Security.BaseHttpHeaderCheck.BaseHttpHeaderCheck( + Umbraco.Cms.Core.Hosting.IHostingEnvironment, + Umbraco.Cms.Core.Services.ILocalizedTextService, + string, + string, + string, + bool) + +Umbraco.Cms.Core.DependencyInjection.UmbracoBuilderExtensions.AddOEmbedProvider(Umbraco.Cms.Core.DependencyInjection.IUmbracoBuilder) + +Umbraco.Cms.Core.DependencyInjection.UmbracoBuilderExtensions.OEmbedProviders(Umbraco.Cms.Core.DependencyInjection.IUmbracoBuilder) + +Umbraco.Cms.Core.Configuration.Models.RequestHandlerSettings.CharCollection.get +Umbraco.Cms.Core.Configuration.Models.RequestHandlerSettings.CharCollection.set + +Umbraco.Cms.Core.Composing.IUserComposer + +Umbraco.Cms.Core.Security.BackOfficeUserStore.BackOfficeUserStore( + Umbraco.Cms.Core.Scoping.ICoreScopeProvider, + Umbraco.Cms.Core.Services.IUserService, + Umbraco.Cms.Core.Services.IEntityService, + Umbraco.Cms.Core.Services.IExternalLoginService, + Microsoft.Extensions.Options.IOptions, + Umbraco.Cms.Core.Mapping.IUmbracoMapper, + Umbraco.Cms.Core.Security.BackOfficeErrorDescriber, + Umbraco.Cms.Core.Cache.AppCaches) + +Umbraco.Cms.Core.Security.MemberUserStore.MemberUserStore( + Umbraco.Cms.Core.Services.IMemberService, + Umbraco.Cms.Core.Mapping.IUmbracoMapper, + Umbraco.Cms.Core.Scoping.ICoreScopeProvider, + Microsoft.AspNetCore.Identity.IdentityErrorDescriber, + Umbraco.Cms.Core.PublishedCache.IPublishedSnapshotAccessor, + Umbraco.Cms.Core.Services.IExternalLoginService) + +Umbraco.Cms.Core.Logging.Viewer.ILogViewer.GetLogLevel() + +Umbraco.Cms.Core.Logging.Viewer.SerilogLogViewerSourceBase.SerilogLogViewerSourceBase( + Umbraco.Cms.Core.Logging.Viewer.ILogViewerConfig, + Serilog.ILogger) + +Umbraco.Cms.Core.Logging.Viewer.SerilogLogViewerSourceBase.GetLogLevel() + +Umbraco.Cms.Core.Configuration.JsonConfigManipulator.JsonConfigManipulator(Microsoft.Extensions.Configuration.IConfiguration) +``` + +**Umbraco.Cms.Infrastructure** + +```csharp +Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement.MemberRepository.SetLastLogin(string, System.DateTime) + +Umbraco.Cms.Infrastructure.Packaging.PackageMigrationBase.PackageMigrationBase( + Umbraco.Cms.Core.Services.IPackagingService, + Umbraco.Cms.Core.Services.IMediaService, + Umbraco.Cms.Core.IO.MediaFileManager, + Umbraco.Cms.Core.PropertyEditors.MediaUrlGeneratorCollection, + Umbraco.Cms.Core.Strings.IShortStringHelper, + Umbraco.Cms.Core.Services.IContentTypeBaseServiceProvider, + Umbraco.Cms.Infrastructure.Migrations.IMigrationContext) + +Umbraco.Cms.Infrastructure.Migrations.Install.DatabaseSchemaCreator.DatabaseSchemaCreator( + Umbraco.Cms.Infrastructure.Persistence.IUmbracoDatabase?, + Microsoft.Extensions.Logging.ILogger, + Microsoft.Extensions.Logging.ILoggerFactory, + Umbraco.Cms.Core.Configuration.IUmbracoVersion, + Umbraco.Cms.Core.Events.IEventAggregator) + +Umbraco.Cms.Infrastructure.Migrations.Install.DatabaseSchemaCreatorFactory.DatabaseSchemaCreatorFactory( + Microsoft.Extensions.Logging.ILogger, + Microsoft.Extensions.Logging.ILoggerFactory, + Umbraco.Cms.Core.Configuration.IUmbracoVersion, + Umbraco.Cms.Core.Events.IEventAggregator) + +Umbraco.Cms.Infrastructure.HostedServices.RecurringHostedServiceBase.RecurringHostedServiceBase( + System.TimeSpan, + System.TimeSpan) + +Umbraco.Cms.Infrastructure.HostedServices.ReportSiteTask.ReportSiteTask( + Microsoft.Extensions.Logging.ILogger, + Umbraco.Cms.Core.Configuration.IUmbracoVersion, + Microsoft.Extensions.Options.IOptions) +``` + +**Umbraco.Cms.Web** + +```csharp +Umbraco.Cms.Web.Common.Security.ConfigureIISServerOptions + +Umbraco.Cms.Web.Common.RuntimeMinification.SmidgeRuntimeMinifier.Reset() + +Umbraco.Cms.Web.Common.Middleware.UmbracoRequestMiddleware.UmbracoRequestMiddleware( + Microsoft.Extensions.Logging.ILogger, + Umbraco.Cms.Core.Web.IUmbracoContextFactory, + Umbraco.Cms.Core.Cache.IRequestCache, + Umbraco.Cms.Core.Events.IEventAggregator, + Umbraco.Cms.Core.Logging.IProfiler, + Umbraco.Cms.Core.Hosting.IHostingEnvironment, + Umbraco.Cms.Core.Routing.UmbracoRequestPaths, + Umbraco.Cms.Infrastructure.WebAssets.BackOfficeWebAssets, + Microsoft.Extensions.Options.IOptionsMonitor, + Umbraco.Cms.Core.Services.IRuntimeState, + Umbraco.Cms.Core.Models.PublishedContent.IVariationContextAccessor, + Umbraco.Cms.Core.PublishedCache.IDefaultCultureAccessor) + +Umbraco.Cms.Web.Website.Controllers.UmbLoginController.UmbLoginController( + Umbraco.Cms.Core.Web.IUmbracoContextAccessor, + Umbraco.Cms.Infrastructure.Persistence.IUmbracoDatabaseFactory, + Umbraco.Cms.Core.Services.ServiceContext, + Umbraco.Cms.Core.Cache.AppCaches, + Umbraco.Cms.Core.Logging.IProfilingLogger, + Umbraco.Cms.Core.Routing.IPublishedUrlProvider, + Umbraco.Cms.Web.Common.Security.IMemberSignInManager) + +Umbraco.Cms.Web.BackOffice.Trees.MemberTypeAndGroupTreeControllerBase.MemberTypeAndGroupTreeControllerBase( + Umbraco.Cms.Core.Services.ILocalizedTextService, + Umbraco.Cms.Core.UmbracoApiControllerTypeCollection, + Umbraco.Cms.Core.Trees.IMenuItemCollectionFactory, + Umbraco.Cms.Core.Events.IEventAggregator) + +Umbraco.Cms.Web.BackOffice.Controllers.CurrentUserController.CurrentUserController( + Umbraco.Cms.Core.IO.MediaFileManager, + Microsoft.Extensions.Options.IOptions, + Umbraco.Cms.Core.Hosting.IHostingEnvironment, + Umbraco.Cms.Core.Media.IImageUrlGenerator, + Umbraco.Cms.Core.Security.IBackOfficeSecurityAccessor, + Umbraco.Cms.Core.Services.IUserService, + Umbraco.Cms.Core.Mapping.IUmbracoMapper, + Umbraco.Cms.Core.Security.IBackOfficeUserManager, + Microsoft.Extensions.Logging.ILoggerFactory, + Umbraco.Cms.Core.Services.ILocalizedTextService, + Umbraco.Cms.Core.Cache.AppCaches, + Umbraco.Cms.Core.Strings.IShortStringHelper, + Umbraco.Cms.Web.Common.Security.IPasswordChanger) + +Umbraco.Cms.Web.BackOffice.Controllers.EntityController.GetUrlsByUdis(Umbraco.Cms.Core.Udi[], string?) + +Umbraco.Cms.Web.BackOffice.Controllers.HelpController.HelpController(Microsoft.Extensions.Logging.ILogger) + +Umbraco.Cms.Web.BackOffice.Controllers.LanguageController.LanguageController( + Umbraco.Cms.Core.Services.ILocalizationService, + Umbraco.Cms.Core.Mapping.IUmbracoMapper, + Microsoft.Extensions.Options.IOptionsSnapshot) + +Umbraco.Cms.Web.BackOffice.Controllers.LogViewerController.LogViewerController(Umbraco.Cms.Core.Logging.Viewer.ILogViewer) +Umbraco.Cms.Web.BackOffice.Controllers.LogViewerController.GetLogLevel() + +Umbraco.Cms.Web.BackOffice.Controllers.MediaController.GetPagedReferences(int, string, int, int) + +Umbraco.Cms.Web.BackOffice.Controllers.MemberTypeController.GetAllTypes() + +Umbraco.Cms.Web.BackOffice.Controllers.TemplateController.TemplateController( + Umbraco.Cms.Core.Services.IFileService, + Umbraco.Cms.Core.Mapping.IUmbracoMapper, + Umbraco.Cms.Core.Strings.IShortStringHelper) +``` + +**Umbraco.Cms.Tests** + +```csharp +Umbraco.Cms.Tests.Common.Testing.TestOptionAttributeBase.ScanAssemblies +``` + +**Code moved to new assemblies and namespaces** + +The following have been moved to new assemblies and their namespaces have been updated accordingly. + +**Umbraco.Extensions** + +```csharp +Umbraco.Extensions.NPocoDatabaseExtensions.ConfigureNPocoBulkExtensions() + +Umbraco.Extensions.UmbracoBuilderExtensions.AddUmbracoImageSharp(Umbraco.Cms.Core.DependencyInjection.IUmbracoBuilder) +``` + +**Umbraco.Cms.Web** + +```csharp +Umbraco.Cms.Web.Common.Media.ImageSharpImageUrlGenerator + +Umbraco.Cms.Web.Common.ImageProcessors.CropWebProcessor + +Umbraco.Cms.Web.Common.DependencyInjection.ConfigureImageSharpMiddlewareOptions +Umbraco.Cms.Web.Common.DependencyInjection.ConfigurePhysicalFileSystemCacheOptions +``` + +**Umbraco.Cms.Infrastructure** + +```csharp +Umbraco.Cms.Infrastructure.Persistence.LocalDb +Umbraco.Cms.Infrastructure.Persistence.FaultHandling.RetryPolicyFactory +Umbraco.Cms.Infrastructure.Persistence.FaultHandling.ThrottlingMode +Umbraco.Cms.Infrastructure.Persistence.FaultHandling.ThrottlingType +Umbraco.Cms.Infrastructure.Persistence.FaultHandling.ThrottledResourceType +Umbraco.Cms.Infrastructure.Persistence.FaultHandling.ThrottlingCondition +Umbraco.Cms.Infrastructure.Persistence.FaultHandling.Strategies.NetworkConnectivityErrorDetectionStrategy +Umbraco.Cms.Infrastructure.Persistence.FaultHandling.Strategies.SqlAzureTransientErrorDetectionStrategy +``` + +**New interface methods** + +A few interfaces have been merged, adding new members to the original interfaces. + +**Umbraco.Cms.Core** + +```csharp +Umbraco.Cms.Core.Services.IMacroService.GetAll(params string[]) + +Umbraco.Cms.Core.Persistence.Repositories.IMacroRepository.GetByAlias(string) +Umbraco.Cms.Core.Persistence.Repositories.IMacroRepository.GetAllByAlias(string[]) + +Umbraco.Cms.Core.Services.ITwoFactorLoginService.DisableWithCodeAsync(string, System.Guid, string) +Umbraco.Cms.Core.Services.ITwoFactorLoginService.ValidateAndSaveAsync(string, System.Guid, string, string) + +Umbraco.Cms.Core.Models.IContentType.HistoryCleanup + +Umbraco.Cms.Core.Media.IImageDimensionExtractor.SupportedImageFileTypes +``` + +**No-Operation methods removed** + +A method not doing anything for the last couple of major releases have been removed. + +**Umbraco.Cms.Core** + +```csharp +Umbraco.Cms.Core.Services.IMembershipMemberService.SetLastLogin(string, System.DateTime) +``` + +**Changes due to models made immutable** + +A single model have been made immutable, so the default constructor and the setters are not available anymore. + +**Umbraco.Cms.Infrastructure** + +```csharp +Umbraco.Cms.Infrastructure.PublishedCache.DataSource.ContentData.ContentData() +Umbraco.Cms.Infrastructure.PublishedCache.DataSource.ContentData.Name.set +Umbraco.Cms.Infrastructure.PublishedCache.DataSource.ContentData.UrlSegment.set +Umbraco.Cms.Infrastructure.PublishedCache.DataSource.ContentData.VersionId.set +Umbraco.Cms.Infrastructure.PublishedCache.DataSource.ContentData.VersionDate.set +Umbraco.Cms.Infrastructure.PublishedCache.DataSource.ContentData.WriterId.set +Umbraco.Cms.Infrastructure.PublishedCache.DataSource.ContentData.TemplateId.set +Umbraco.Cms.Infrastructure.PublishedCache.DataSource.ContentData.Published.set +Umbraco.Cms.Infrastructure.PublishedCache.DataSource.ContentData.Properties.set +Umbraco.Cms.Infrastructure.PublishedCache.DataSource.ContentData.CultureInfos.set +``` + +**Classes that does not inherit from base type anymore** + +The following classes now directly inherit from OEmbedProviderBase instead of EmbedProviderBase. + +**Umbraco.Cms.Core** + +```csharp +Umbraco.Cms.Core.Media.EmbedProviders.DailyMotion +Umbraco.Cms.Core.Media.EmbedProviders.Flickr +Umbraco.Cms.Core.Media.EmbedProviders.GettyImages +Umbraco.Cms.Core.Media.EmbedProviders.Giphy +Umbraco.Cms.Core.Media.EmbedProviders.Hulu +Umbraco.Cms.Core.Media.EmbedProviders.Issuu +Umbraco.Cms.Core.Media.EmbedProviders.Kickstarter +Umbraco.Cms.Core.Media.EmbedProviders.Slideshare +Umbraco.Cms.Core.Media.EmbedProviders.Soundcloud +Umbraco.Cms.Core.Media.EmbedProviders.Ted +Umbraco.Cms.Core.Media.EmbedProviders.Twitter +Umbraco.Cms.Core.Media.EmbedProviders.Vimeo +Umbraco.Cms.Core.Media.EmbedProviders.YouTube +``` + +
+ +
+ +Umbraco 10 + +[**Update 'diff' from 3.5.0 to 5.0.0**](https://github.com/umbraco/Umbraco-CMS/issues/12337) + +The `diff` library used in the Backoffice client has been updated and introduces a breaking change since the exposed global object has been renamed from `JsDiff` to `Diff`. + +[**Content Schedule performance**](https://github.com/umbraco/Umbraco-CMS/pull/11398) + +Removes mutable ContentSchedule property from `IContent/Content` to `read/write` content schedules. + +Use _IContentService.GetContentScheduleByContentId && IContentService.PersistContentSchedule_ or the optional _contentSchedule parameter_ on _IContentService.Save_ instead. + +[**Removed redundant event handling code**](https://github.com/umbraco/Umbraco-CMS/pull/11842) + +* Removed public methods: `PublishedSnapshotServiceEventHandler.Dispose`, `PublishedSnapshotServiceEventHandler.Dispose(bool)`, and `.PublishedSnapshotServiceEventHandler.Initialize`. +* Removed public `ctor`. + +[**Scope provider cleanup**](https://github.com/umbraco/Umbraco-CMS/pull/11859) + +* Some public classes in the `Cms.Core.Services` namespace have moved assembly from **`Umbraco.Cms.Infrastructure`** to **`Umbraco.Cms.Core`**. +* These same public classes have changed namespace from **`Umbraco.Cms.Core.Services.Implement`** to **`Umbraco.Cms.Core.Services`**. + +[**Update to NPoco5**](https://github.com/umbraco/Umbraco-CMS/pull/11880) + +NPoco types and interfaces are part of our public interface which means that this upgrade imposes breaking changes. + +[**SQLite support**](https://github.com/umbraco/Umbraco-CMS/pull/11922) + +* Removed support for Microsoft SQL Server Compact (SQL CE). +* Removed `ReadLock` and `WriteLock` methods from `ISqlSyntaxProvider` interface. Use `IDistributedLockingMechanism` (or IScope which delegates to `IDistributedLockingMechanism`) instead. +* Constants for SQL Server provider name moved+consolidated from `Core.Constants.DatabaseProviders` and `Core.Constants.-DbProviderNames` to `Umbraco.Cms.Persistence.SqlServer.Constants` +* Some SQL Server related services moved from the `Umbraco.Infrastructure` project to the new `Umbraco.Cms.Persistence`. +* SqlServer project with altered namespaces e.g. `SqlServerSyntaxProvider`, `SqlServerBulkSqlInsertProvider`, `SqlServerDatabaseCreator`. + +**Added the following methods/properties to ISqlSyntaxProvider. These must be implemented in any downstream implementation e.g:** + +* `ISqlSyntaxProvider.HandleCreateTable(IDatabase,TableDefinition,Boolean)` +* `ISqlSyntaxProvider.GetFieldNameForUpdate()` +* `ISqlSyntaxProvider.GetColumn(DatabaseType,String,String,String,String,Boolean)` +* `ISqlSyntaxProvider.InsertForUpdateHint(Sql)` +* `ISqlSyntaxProvider.AppendForUpdateHint(Sql)` +* `ISqlSyntaxProvider.LeftJoinWithNestedJoin(Sql,Func,String)` + +[**Update to ImageSharp v2**](https://github.com/umbraco/Umbraco-CMS/pull/12185) + +**Update dependency versions**: + +* `SixLabors.ImageSharp` from 1.0.4 to 2.1.1 +* `SixLabors.ImageSharp.Web` from 1.0.5 to 2.0.0 + +Renamed the `CachedNameLength` property to `CacheHashLength` on **ImagingCacheSettings**. + +Moved **ImageSharpImageUrlGenerator** from project `Umbraco.Infrastructure` to `Umbraco.Web.Common` and updated the corresponding namespace and DI registration (from `AddCoreInitialServices()` to `AddUmbracoImageSharp()`); + +Moved **ImageSharp** configuration from the `AddUmbracoImageSharp()` extension method into separate `IConfigureOptions<>` implementations: + +* The middleware is configured in ConfigureImageSharpMiddlewareOptions (which also replaces ImageSharpConfigurationOptions that previously only set the default ImageSharp configuration); +* The default physical cache is configured in ConfigurePhysicalFileSystemCacheOptions. + +[**Migrate Member properties to columns on the Member table**](https://github.com/umbraco/Umbraco-CMS/pull/12205) + +This is breaking because it is no longer possible to access the properties listed below through the _IMember.Properties_ collection. You must now access them through their specific properties that is _IMember.IsLockedOut_. + +* `umbracoMemberFailedPasswordAttempts` +* `umbracoMemberApproved` +* `umbracoMemberLockedOut` +* `umbracoMemberLastLockoutDate` +* `umbracoMemberLastLogin` +* `umbracoMemberLastPasswordChangeDate` + +Additionally, when previously you resolved a Member as published content, all the default properties would be there twice. For instance, `IsLockedOut` would be there both as a property with the alias `umbracoMemberLockedOut` and with the alias `IsLockedOut`. Now it'll only be there once, with the alias being the name of the property, so `IsLockedOut` in this instance. + +Lastly the nullable dates on a user, i.e. `LastLoginLate` will now be null instead of `DateTime.MinValue` when getting a user with the UserService. + +[**Update examine to version 3**](https://github.com/umbraco/Umbraco-CMS/pull/12307) + +**Examine 3 breaking changes:** + +* `ValueSet` immutable. +* `ValueSetValidationResult` is renamed to `ValueSetValidationStatus` and `ValueSetValidationResult` is now a type. + +[**Async support for content finders**](https://github.com/umbraco/Umbraco-CMS/pull/12340) + +```CSharp +bool TryFindContent(IPublishedRequestBuilder request); +``` + +Has changed to: + +```CSharp +Task TryFindContent(IPublishedRequestBuilder request); +``` + +[**Improve redirect Content finder scalability**](https://github.com/umbraco/Umbraco-CMS/pull/12341) + +* Added more methods to `IRedirectUrlRepository` and `IRedirectUrlService.cs`. + +[**Fix Block List settings exception and optimize PVCs**](https://github.com/umbraco/Umbraco-CMS/pull/12342) + +* Added a new method on `IPublishedModelFactory`: Type `GetModelType(string? alias)`; +* The generic types of a `BlockListItem`instance in the`BlockListModel`returned by`BlockListPropertyValueConverter`is now determined by calling this new method, which can be different and cause a`ModelBindingException\` in your views. + +[**Async tree search**](https://github.com/umbraco/Umbraco-CMS/pull/12344) + +```CSharp +IEnumerable Search(string query, int pageSize, long pageIndex, out long totalFound, string? searchFrom += null) +``` + +Has changed to: + +```CSharp +Task SearchAsync(string query, int pageSize, long pageIndex, string? searchFrom = null); +``` + +[**Moved StackQueue to correct namespace**](https://github.com/umbraco/Umbraco-CMS/pull/12347) + +StackQueue has been moved from `Umbraco.Core.Collections` to the `Umbraco.Cms.Core.Collections` namespace. + +**Globalsetting SqlWriteLockTimeOut has been removed** + +This setting has been superseded by `DistributedLockingWriteLockDefaultTimeout`. + +**GlobalSetting UmbracoPath cannot be configured** + +It is no longer possible to rename the `/Umbraco` folder path using configuration. The property still exists but is hardcoded to `/Umbraco` and will be removed in Umbraco 12, planned for release in June 2023. + +
+ +## Release notes + +You can find a list of all the released Umbraco versions on [Our Umbraco](https://our.umbraco.com/download/releases/) website. When you visit Our Umbraco website, click on the version number to view the changes made in that specific version. + +## Find your upgrade path + +Are you looking to upgrade an Umbraco Cloud project from 9 to 10? Follow the guide made for [Upgrading your project from Umbraco 9 to 10](https://docs.umbraco.com/umbraco-cloud/product-upgrades/major-upgrades) instead, as it requires a few steps specific to Umbraco Cloud. + +
+ +13.latest to the latest version + +**Update \_ViewImports.cshtml file** + +In Umbraco 14, Smidge has been removed from the CMS. + +In the `_ViewImports.cshtml` of your project, remove the following lines: + +``` +@addTagHelper *, Smidge +@inject Smidge.SmidgeHelper SmidgeHelper +``` + +Otherwise, it will cause an error on the front end. + +**Update program.cs file** + +Remove `u.UseInstallerEndpoints();` from the `program.cs` file to avoid issues when running the project + + + +**Update code using Angular JS** + +Angular JS has been removed in Umbraco 14. If you have extended your Umbraco project using Angular JS, it must be updated. for more information read the [Customize Backoffice](../../../../customizing/extend-and-customize-editing-experience.md) documentation. + +**Deprecated property editors** + +**Nested Content** and **Grid Layout** have been removed. We recommend rebuilding it using Block Grid for the grid layout and either Block Grid or Block List for Nested Content. + +The **legacy Media Picker** has been removed, use the default Media Picker. + +**Macros and partial views macros removed** + +Macros and partial views macros have been removed in Umbraco 14. We recommend using partial views or blocks in the Rich Text Editor (RTE). + +For more information on what has changed in Umbraco 14 read the [Breaking changes in Umbraco 14](./#umbraco-14). + +**Block Editor data format has changes** + +In Umbraco 15, the internal data format for [Block Editors](../../../../fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/block-editor/README.md) has changed. This causes a content migration to run when upgrading. + +This content migration can take a while to complete on a large site, causing it to be unresponsive for the duration. To speed up the migration, it is advised to [clean up old content versions](../../../../fundamentals/data/content-version-cleanup.md) before upgrading. + +While we don't recommend this, it might be possible for you to skip the content migration. More details can be found in the [Migrate content to Umbraco 15](migrate-content-to-umbraco-15.md) article. + +
+ +
+ +10.latest to 13.latest + +It might be necessary to delete all of the `bin` and `obj` directories in each of the projects of your solution. It has been observed that Visual Studio's "Clean Solution" option is sometimes not enough. + +You can upgrade from Umbraco 10 to the latest version directly. If you choose to skip upgrading to versions 11 and 12, you will no longer receive warning messages for obsolete features. However, if you do skip these versions, any breaking changes will no longer compile. + +It is recommended that you upgrade to the closest [Long-term Support (LTS) major](https://umbraco.com/products/knowledge-center/long-term-support-and-end-of-life/) version before upgrading to the latest version. For Umbraco 10, the closest long-term support version is Umbraco 13 so a direct upgrade is possible. + +
+ +
+ +9.latest to 10 + +**Important**: .NET version 6.0.5 is the minimum required version for Umbraco 10 to be able to run. You can check with `dotnet --list-sdks` what your latest installed Software Development Kit (SDK) version is. The latest SDK version 6.0.301 includes .NET 6.0.6, while SDK version 6.0.300 includes .NET 6.0.5. + +Watch the ['Upgrading from Umbraco 9 to Umbraco 10 video tutorial'](https://www.youtube.com/watch?v=075H\_ekJBKI\&ab\_channel=UmbracoLearningBase) for a complete walk-through of all the steps. + +The upgrade path between Umbraco 9 and Umbraco 10 can be done directly by upgrading your project using NuGet. You will need to ensure the packages you are using are available in Umbraco 10. + +**SQL CE is no longer a supported database engine** + +There is no official migration path from SQL CE to another database engine. + +The following options may suit your needs: + +* Follow a community guide to migrate from a SQL CE database to SQL Server, like the [article by Jan Reilink](https://www.saotn.org/convert-sqlce-database-to-sql-server/) +* Setup a new database for v10 and use [uSync](https://jumoo.co.uk/usync/) to transfer document types and content across. +* Setup a new database for v10 and use a premium tool such as [redgate SQL Data Compare](https://www.red-gate.com/products/sql-development/sql-data-compare/) to copy database contents across. +* Setup a new database for v10 and use a premium tool such as [Umbraco Deploy](https://umbraco.com/products/umbraco-deploy) to transfer document types and content across. + +**Steps to upgrade using Visual Studio** + +It's recommended that you upgrade the site offline, and test the upgrade fully before deploying it to the production environment. + +1. Stop your site in IIS to prevent any changes being made to the database or filesystem while you are upgrading. +2. Open your Umbraco 9 project in Visual Studio. +3. Right-click on the project name in the Solution Explorer and select **Properties**. +4. Select **.NET 6.0** from the **Target Framework** drop-down. +5. Go to **Tools** > **NuGet Package Manager** > **Manage NuGet Packages for Solution...** +6. Go to the **Installed** tab in the NuGet Package manager. +7. Choose **Umbraco.Cms**. +8. Select **10.0.0** from the **Version** drop-down and click **Install** to upgrade your project to version 10. +9. Update `Program.cs` to the following: + +```csharp +public class Program +{ + public static void Main(string[] args) + => CreateHostBuilder(args) + .Build() + .Run(); + + // The calls to `ConfigureUmbracoDefaults` and `webBuilder.UseStaticWebAssets()` are new. + public static IHostBuilder CreateHostBuilder(string[] args) => + Host.CreateDefaultBuilder(args) + .ConfigureUmbracoDefaults() + .ConfigureWebHostDefaults(webBuilder => + { + webBuilder.UseStaticWebAssets(); + webBuilder.UseStartup(); + }); +} +``` + +10. Remove the following files and folders: + * `/wwwroot/umbraco` + * `/umbraco/PartialViewMacros` + * `/umbraco/UmbracoBackOffice` + * `/umbraco/UmbracoInstall` + * `/umbraco/UmbracoWebsite` + * `/umbraco/config/lang` + * `/umbraco/config/appsettings-schema.json` +11. If using Umbraco Forms, update your files and folders according to the [Upgrading - version specific](https://docs.umbraco.com/umbraco-forms/installation/version-specific) for version 10 article. +12. Restart your site in IIS, build and run your project to finish the installation of Umbraco 10. + +To re-enable the appsettings IntelliSense, you must update your schema reference in the `appsettings.json` file and any other `appsettings.{Environment}.json` files from: + +```json +"$schema": "./umbraco/config/appsettings-schema.json", +``` + +To: + +```json +"$schema": "./appsettings-schema.json", +``` + +To upgrade to Umbraco 10, your database needs to be at least on Umbraco 8.18. + +**Upgrade of any publicly hosted environment** + +When the upgrade is completed and tested, and prior to deploying to any publicly accessible environment, you should consider the following: + +1. Ensure you have backups for both the database and the file system. +2. Stop the site so it is not accessible during the upgrade process. +3. Delete the relevant folders from the filesystem prior to deploying: + * `/wwwroot/umbraco` + * `/umbraco/PartialViewMacros` + * `/umbraco/UmbracoBackOffice` + * `/umbraco/UmbracoInstall` + * `/umbraco/UmbracoWebsite` + * `/umbraco/config/lang` + * `/umbraco/config/appsettings-schema.json` +4. If you are using Umbraco Forms, update your files and folders according to the [Upgrading - version specific](https://docs.umbraco.com/umbraco-forms/installation/version-specific) for version 10 article. +5. Deploy the site how you normally would to your public facing environment. +6. Start the site. At this point it will launch and upgrade the database, after which the site should become accessible and your upgrade is complete. +7. Check the logs for any errors which may have occurred during the upgrade process. + +
+ +
+ +8.latest to 9 + +There is no direct upgrade path from Umbraco 8 to Umbraco 9. It is however possible to migrate from Umbraco 8 sites to Umbraco 9 sites. + +You can reuse your content by restoring your Umbraco 8 database into a new database used for an Umbraco 9 site. + +You need to ensure the packages you are using are available in Umbraco 9, and you will need to reimplement your custom code and templates. + +The direct upgrade path is not possible because the codebase has been fundamentally updated in Umbraco 9. The underlying web framework has been updated from ASP.NET to ASP.NET Core. + +It is not possible to take this step while maintaining full compatibility with Umbraco 8. + +
+ +
+ +8.0.0 to 8.1.0 + +There are a few breaking changes from 8.0.x to 8.1.0. Make sure to check the [full list](https://github.com/umbraco/Umbraco-CMS/issues?q=is%3Aissue+label%3Arelease%2F8.1.0+is%3Aclosed+label%3Acategory%2Fbreaking). + +**IPublishedContent breaking changes in 8.1.0** + +Due to the [changes in `IPublishedContent`](https://github.com/umbraco/Umbraco-CMS/issues/5170) there are a few steps you will need to take, to make sure that your site works. + +The `IPublishedContent` interface is central to Umbraco, as it represents published content and media items at the rendering layer level. This could be in controllers or views. In other words, it is the interface that is used everywhere when building sites. + +The introduction of multilingual support in version 8 required changes to the interface. For instance, a property value could be obtained with `GetPropertyValue(alias)` in version 7. Version 8 requires a new parameter for culture, and the call thus became `Value(alias, culture)`. + +In the excitement of the version 8 release, we assumed that `IPublishedContent` was "done". By our tests, everything was looking good. However, feedback from early testers showed that the interface was in some places odd or inconsistent or had issues. + +Fixing the bugs is a requirement. Some of the required bug fixes could not be achieved without introducing some breaking changes. + +At that point, we decided to give `IPublishedContent` some love. We fixed the bugs and made it clean, friendly, discoverable, and predictable for the entire life of version 8. + +Breaking changes to such a central interface is not something we take lightly. Even though they do not impact the "concepts" nor require heavy refactoring, they may demand an amount of small fixes here and there. + +The general idea underlying these changes is that: + +* The proper way to retrieve "something" from an `IPublishedContent` instance is always through a method, for example: `Children()`. And, when that method can be multilingual, the method accepts a `culture` parameter, which can be left `null` to get the "current" culture value. +* To reduce the amount of breaking changes, and to simplify things for non-multilingual sites, existing properties such as `document.Name` and `document.Children` (and others) still exist, and return the value for the current culture. In other words, these properties are now implemented as `document.Name => document.Name()` or `document.Children => document.Children()`. + +The rest of this document presents each change in details. + +**More interfaces** + +It was possible to mock and test the `IPublishedContent` interface in version 7. It has been improved in version 8, but it still relies on concrete `PublishedContentType` and `PublishedPropertyType` classes to represent the content types, which complicates things. + +In version 8.1, these two classes are abstracted as `IPublishedContentType` and `IPublishedPropertyType`, thus making `IPublishedContent` easier to mock and test. + +**CHANGE**: This impacts every method accepting or returning a content type. For instance, the signature of most `IPropertyValueConverter` methods changes. References to `PublishedContentType` must be replaced with references to `IPublishedContentType`. + +The following `IPublishedContent` members change: + +**Name** + +The `document.Name` property is complemented by the `document.Name(string culture = null)` extension method. The property returns the name for the current culture. The `document.GetCulture(...).Name` syntax is removed. + +**CHANGE**: Calls to `document.GetCulture(culture).Name` must be replaced with `document.Name(culture)`. + +**UrlSegment** + +The `document.UrlSegment` property is complemented by the `document.UrlSegment(string culture = null)` extension method. The property returns the Url segment for the current culture. The `document.GetCulture(...).UrlSegment` syntax is removed. + +**CHANGE**: Calls to `document.GetCulture(culture).UrlSegment` must be replaced with `document.UrlSegment(culture)`. + +**Culture** + +The `document.GetCulture()` method is removed. The proper way to get a culture date is `document.CultureDate(string culture = null)`. The `document.Cultures` property now returns the invariant culture, for invariant documents. + +**CHANGE**: Calls to `document.GetCulture(culture).Date` must be replaced with `document.CultureDate(culture)`. Calls to `document.Cultures` must take into account the invariant culture. + +**Children** + +The `document.Children` property is complemented by the `document.Children(string culture = null)` extension method which, when a culture is specified always return children available for the specified culture. The property returns the children available for the current culture. + +A new `document.ChildrenForAllCultures` property is introduced, which returns _all_ children, regardless of whether they are available for a culture or not. + +**CHANGE**: Calls to `document.Children` may have to be replaced by `document.ChildrenForAllCultures` depending on if the 8.0.x usage of this was relying on it returning unfiltered/all children regardless of the current routed culture. + +**Url** + +The `document.Url` property is complemented by the `document.Url(string culture = null, UrlMode mode = UrlMode.Auto)` extension method. The `document.GetUrl(...)` and `document.UrlAbsolute()` methods are removed. The `UrlProviderMode` enumeration is renamed `UrlMode`. + +**CHANGE**: Calls to `document.GetUrl(...)` must be replaced with `document.Url(...)`. Calls to `document.UrlAbsolute()` must be replaced with `document.Url(mode: UrlMode.Absolute)`. + +**UmbracoContext** + +Due to the `UrlProviderMode` enumeration being renamed `UrlMode`, the signature of some overloads of the `Url(...)` method has changed. Methods that do not have a mode parameter remain unchanged. + +**CHANGE**: Code such as `context.Url(1234, UrlProviderMode.Absolute)` must become `context.Url(1234, UrlMode.Absolute)`. + +The `UmbracoContext` class gives access to the rendering layer, which is more than a "cache". To reflect this, its `ContentCache` and `MediaCache` properties are renamed `Content` and `Media`. However, the old properties remain as obsolete properties. + +**CHANGE**: None required in 8.1, but code such as `context.ContentCache.GetById(1234)` should eventually be converted to `context.Content.GetById(1234)` as the obsolete properties may be removed in a further release. + +**GetCulture** + +Version 7 had a `document.GetCulture()` method that was deriving a culture from domains configured in the tree. Somehow, that method was lost during version 8 development (issue [#5269](https://github.com/umbraco/Umbraco-CMS/issues/5269)). + +Because that method is useful, especially when building traditional, non-multilingual sites, it has been re-introduced in version 8.1 as `document.GetCultureFromDomains()`. + +**CHANGE**: None. + +**DomainHelper** + +`DomainHelper` has been replaced with a static `DomainUtilities` class. + +**CHANGE**: It is rare that `DomainHelper` is used in code since it only contains one public method but if developers are using this, it can no longer be injected since it's now a static class called `DomainUtilities`. + +**Models Builder** + +If you're using ModelsBuilder in `dll` mode you need to delete the dlls before upgrading. Otherwise, they're going to be wrong and cause your whole site to throw errors. + +If you're using ModelsBuilder in `AppData` mode and you have your generated models in your solution you need to update them after upgrading. `PublishedContentType` will need to be replaced with `IPublishedContentType`. If you have an implementation of the `PropertyValueConverter` class, you need to replace all references to `PublishedPropertyType` with `IPublishedPropertyType` within that class. Only after you do that will your solution build again. + +**AutoMapper** + +Umbraco 8.1 replaces AutoMapper with [UmbracoMapper](../../../../reference/mapping.md). This in itself will not break anything on your site. If you have used AutoMapper in your own code you will have to either include the package yourself or switch your implementation to use UmbracoMapper. + +**Follow the** [**upgrade guide for Umbraco 8**](minor-upgrades-for-umbraco-8.md) **to complete the upgrade** + +
+ +
+ +7.latest to 8.0.0 + +There is no direct upgrade path from Umbraco 7 to Umbraco 8. It is however possible to migrate content from Umbraco 7 sites to Umbraco 8 sites. We have added content migrations in Umbraco 8.1.0 enabling you to migrate your content from Umbraco 7 to Umbraco 8. + +It is not possible to upgrade an Umbraco 7 site to Umbraco 8 because the codebase has been fundamentally updated in Umbraco 8. A lot of outdated code and technology has been removed and instead new, faster, and more secure technology has been implemented. + +In Umbraco 8 we have added improvements and updated dependencies. We have also done a thorough clean-up to make it simpler for you to work with and extend your Umbraco project. + +[**Migrate your content to Umbraco 8**](migrate-content-to-umbraco-8.md) + +
+ +
+ +7.6.3 to 7.7.0 + +Version 7.7.0 introduces User Groups, better user management, and security facilities. This means that anything to do with "User Types" no longer exists including APIs that work with User Types. If your code or any package's code refers to "User Type" APIs, you need to make changes to your code. In many cases, we've added backward compatibility for these scenarios and obsoleted APIs that should no longer be used. + +We are now by default using the e-mail address and not the username for the credentials. When trying to login to the backoffice you need to use the e-mail address as opposed to the username. If you do an upgrade from an older version and would like to keep using the username, change the `true` setting to **false**. + +For a full list of breaking changes see: [the list on the issue tracker](https://issues.umbraco.org/issues/?q=\&project=U4\&tagValue=\&release=7.7.0\&issueType=\&search=search) + +Version 7.7.2 no longer ships with the `CookComputing.XmlRpcV2` assembly. If you reference this assembly or have a package that requires this assembly, you need to copy it back into your website. + +This version also ships with far fewer client files that were only relevant for older versions of Umbraco (i.e. < 7.0.0). There might be some packages that were referencing these old client files. If you see missing image references you may need to contact the vendor of the package in question to update their references. + +Follow the [**upgrade guide for Umbraco 7**](minor-upgrades-for-umbraco-7.md) to complete the upgrade. + +
+ +
+ +7.6.0 to 7.6.3 + +In short: + +In Umbraco version 7.6.2 we made a mistake in the Property Value Converts (PVCs). This was corrected 2 days later in version 7.6.3. If you were having problems with querying the following Data Types on the frontend, make sure to upgrade to 7.6.3: + +* Multi Node Tree Picker +* Related Links +* Member Picker + +Depending on whether you tried to fix the problem with those, you will need to fix them after you upgrade to 7.6.3. + +**Property Value Converters (PVC)** + +Umbraco stores data for Data Types in different ways. For a lot of pickers it will store `1072` or `1083,1283`. These numbers refer to the identifier of the item in Umbraco. In the past, when building your templates, you would manually have to take that value and find the content item it belongs to. Then you would be able to get the data you wanted from there. An example of that is shown below: + +```csharp +@{ + IPublishedContent contactPage; + var contactPageId = Model.Content.GetPropertyValue("contactPagePicker"); + if (contactPageId > 0) + { + contactPage = Umbraco.TypedContent(contactPageId); + } +} + +

+ @contactPage.Name +

+``` + +In Umbraco 7.6.0, this is what you would do instead: + +```html +

+ @Model.ContactPagePicker.Name +

+``` + +This is possible using Models Builder and through the inclusion of [core property value converters](https://our.umbraco.com/projects/developer-tools/umbraco-core-property-value-converters/), a package by community member Jeavon Leopold. + +To not break everybody's sites (the results of queries are different when PVCs are enabled), we disabled these PVCs by default. + +Umbraco 7.6.0 also came with new pickers that store their data as a [UDI (Umbraco Identifier)](https://our.umbraco.com/Documentation/Reference/Querying/Udi). We wanted to simplify the use of these new pickers and by default we wanted PVC's to always be enabled for those pickers. + +We noticed that some new pickers also got their PVC's disabled when the configuration setting was set to false (`false`). + +To make everything consistent, we made sure that the UDI pickers would always use PVC's in 7.6.2, this however reversed the behavior. So when PVC's were enabled, the property would not be converted and when PVC's were disabled, the property would be converted after all. This is the exact opposite behavior of 7.6.2. + +So we have fixed this now in 7.6.3. + +This issue only affects: + +* Multi Node Tree Picker +* Related Links +* Member Picker + +Have you already upgraded to 7.6.2 and fixed queries for those three Data Types? Then you have to do that again in version 7.6.3. + +Follow the [**upgrade guide for Umbraco 7**](minor-upgrades-for-umbraco-7.md) to complete the upgrade. + +
+ +
+ +7.4.0 to 7.6.0 + +Find a list of all the breaking changes below and [a list of the items is also available on the tracker](http://issues.umbraco.org/issues/U4?q=Due+in+version%3A+7.6.0+Backwards+compatible%3F%3A+No+) + +The three most important things to note are: + +1. In web.config do not change `useLegacyEncoding` to `false` if it is currently set to `true` - changing the password encoding will cause you not being able to log in any more. +2. In umbracoSettings.config leave `EnablePropertyValueConverters` set to `false` - this will help your existing content queries to still work. +3. In tinyMceConfig.config make sure to remove `umbracolink` so that the rich text editor works as it should. + +**Breaking Changes** + +**Dependencies** + +**UrlRewriting.Net (**[**U4-9004**](https://issues.umbraco.org/issue/U4-9004)**)** + +`UrlRewriting` was old, leaking memory, and slowing down website startup when dealing with more than a few rules. It's entirely replaced by the [IIS Url Rewrite](https://www.iis.net/downloads/microsoft/url-rewrite) extension. + +**Json.Net (**[**U4-9499**](https://issues.umbraco.org/issue/U4-9499)**)** + +Json.Net has been updated to version 10.0.0 to benefit from improvements in features, fixes, and performances (see [release notes](https://github.com/JamesNK/Newtonsoft.Json/releases)). This might be a breaking change for people relying on one of the changed functionality. + +**Log4net (**[**U4-1324**](https://issues.umbraco.org/issue/U4-1324)**)** + +Umbraco has used a custom build of an old (1.2.11) version of log4net that supported Medium Trust. However, Umbraco itself does not support Medium Trust anymore, and therefore log4net has been upgraded to the standard, latest build of log4net 2.0.8. + +**ImageProcessor (**[**U4-8963**](https://issues.umbraco.org/issue/U4-8963)**)** + +An optional parameter has been added to the `GetCropUrl` method in order to support the background color parameter. This breaks the method signature and therefore might require a recompile of user's code. + +**HtmlAgilityPack (**[**U4-9655**](https://issues.umbraco.org/issue/U4-9655)**)** + +The HtmlAgilityPack has been upgraded to version 1.4.9.5. The Umbraco upgrade process should take care of setting up the binding redirects appropriately. + +**Core** + +**Membership Provider Encoding (**[**U4-6566**](https://issues.umbraco.org/issue/U4-6566)**)** + +The Membership Provider `useLegacyEncoding` setting is now `false` by default, as the legacy password encoding has weaknesses. + +This change only impacts new installs (no change for upgrades). + +**Property Value Converters (**[**U4-7318**](https://issues.umbraco.org/issue/U4-7318)**)** + +A large amount of property value converters contributed by the community have been merged in and are now the default value converters. These converters change the object types returned by `GetPropertyValue` for more convenient types. + +Instead of returning comma-separated string values like it did before, the `SliderValueConverter` now returns a `decimal` or a `Range` value that can be used directly in views. + +This change only impacts new installs (no change for upgrades). + +The new property value converters are controlled by an `umbracoSettings.config` setting. In the section `settings/content`, setting `EnablePropertyValueConverters` needs to be present and `true` to activate them. + +**Database (**[**U4-9201**](https://issues.umbraco.org/issue/U4-9201)**)** + +Umbraco has been using a PetaPoco-managed `UmbracoDatabase` instance since version 7 came out. We realized that some of our legacy code still bypassed that mechanism and used parallel, out-of-band database connections, causing issues with transactions. + +The legacy code has been refactored to rely on the `UmbracoDatabase` instance. However, because that database is disposed of during `EndRequest`, the code that ran after it has been disposed may not work anymore. This should then be updated to use either an `HttpModule` event that occurs before `EndRequest` or the new `UmbracoModule.EndRequest` event. + +More details are available on [issue 146](https://github.com/kipusoep/UrlTracker/issues/146) on the 301 Redirect Tracker GitHub issue tracker. + +**Scopes (**[**U4-9406**](https://issues.umbraco.org/issue/U4-9406)**)** + +Version 7.6 introduces the notion of _scopes_, which allow for wrapping multiple service-level operations in one single transaction. The scopes API is partially public. Scopes are not meant for public use at this stage and we need a few more releases to ensure that the APIs are stable. + +Scopes _should not_ change how Umbraco functions. + +Introducing scopes means that some public APIs signatures are changing. Most of these changes target internal and/or non-breaking APIs (as per our [guidelines](https://our.umbraco.com/Documentation/Development-Guidelines/breaking-changes)). This should therefore have no impact on sites but may break unit tests. + +**Property Editors storing UDI instead of ID (**[**U4-9310**](https://issues.umbraco.org/issue/U4-9310)**)** + +The property editors for pickers for content, media, members, and related links have been updated to store UDI instead of the node ID. Pickers in sites being upgraded have been marked as obsolete but will continue to work as they always did. + +New sites will have the obsolete pickers filtered out from the list of available property editors, but they can be enabled by a configuration flag. + +**Rich Text Editor (RTE) Images attributes (**[**U4-6228**](https://issues.umbraco.org/issue/U4-6228)**,** [**U4-6595**](http://issues.umbraco.org/issue/U4-6595)**)** + +For a long time, we had a `rel` attribute on an `` tag when inserted into the RTE. This is invalid HTML markup. We worked around this by stripping this attribute using a Property Editor Value converter. Some developers relied on this attribute so we didn't change it to a "data-id" attribute which would have been valid. In 7.6 we are not storing integer IDs in these attributes. Instead of storing UDI values so with this change we no longer use `rel` or `data-id` and instead there will be a "data-udi" attribute. This change should affect only a small amount of people that were previously relying on the values from the "rel" attribute. + +**Others** + +We are shipping with SignalR in the core at version 2.2.1. If you already have SignalR installed into your app and are using an older version there may be conflicts. + +The creation and editing of WebForms templates will no longer be supported as for version 7.6.0. + +**Upgrading via NuGet** + +This is an important one and there was no perfect solution to this. We have removed the UrlRewriting dependency and no longer ship with it. However, if you are using it we didn't want to have NuGet delete all of your rewrites. The good news is that if you are using it, the NuGet upgrade will not delete your rewrite file and everything should continue to work. + +However, if you are not using it, **you will get an error after upgrading. Here's how to fix it:** + +Since you aren't using UrlRewriting you will have probably never edited the UrlRewriting file. In this case, NuGet will detect that and remove it. However you will need to manually remove these UrlRewriting references from your `web.config`: + +```xml +
+``` + +and + +```xml + +``` + +Remove the following `httpModules` from your `web.config`: + +```xml + + + + ... + + +``` + +and + +```xml + + + + + ... + + +``` + +**Forms** + +Umbraco Forms 6.0.0 has been released to be compatible with Umbraco 7.6. It is a new major version release of Forms primarily due to the strict dependency on 7.6+. If you are using Forms, you will need to update it to version 6.0.0 + +There are [**important Forms upgrade documentation that you will need to read.**](https://docs.umbraco.com/umbraco-forms/installation/version-specific.md#version-4-to-version-6). + +**Courier** + +Umbraco Courier 3.1.0 has been released to be compatible with Umbraco 7.6. If you are using Courier, you will need to update it to version 3.1.0. + +**Follow the** [**upgrade guide for Umbraco 7**](minor-upgrades-for-umbraco-7.md) **to complete the upgrade** + +
+ +
+ +7.3.0 to 7.4.0 + +For manual upgrades: + +* Copy the new folder `~/App_Plugins/ModelsBuilder` into the site +* Do not forget to merge `~/Config/trees.config` and `~/Config/Dashboard.config` - they contain new and updated entries that are required to be there + * If you forget `trees.config` you will either not be able to browse the Developer section or you will be logged out immediately when trying to go to the developer section +* You may experience an error saying `Invalid object name 'umbracoUser'` - this can be fixed by [clearing your cookies on localhost](http://issues.umbraco.org/issue/U4-8031) + +Follow the [**upgrade guide for Umbraco 7**](minor-upgrades-for-umbraco-7.md) to complete the upgrade. + +
+ +
+ +7.2.0 to 7.3.0 + +Make sure to manually clear your cookies after updating all the files, otherwise you might an error relating to `Umbraco.Core.Security.UmbracoBackOfficeIdentity.AddUserDataClaims()`. The error looks like: `Value cannot be null. Parameter name: value`. + +NuGet will do the following for you. If you're upgrading manually make sure to also: + +* Delete `bin/Microsoft.Web.Helpers.dll` +* Delete `bin/Microsoft.Web.Mvc.FixedDisplayModes.dll` +* Delete `bin/System.Net.Http.dll` +* Delete `bin/System.Net.Http.*.dll` (all dll files starting with `System.Net.Http`) **except** for `System.Net.Http.Formatting.dll` +* Delete `bin/umbraco.XmlSerializers.dll` +* Add this in the `appSetting` section of your `web.config` file: `` + +Other considerations: + +* WebApi has been updated, normally you don’t have to do anything unless you have custom webapi configuration: + * See this article if you are using `WebApiConfig.Register`: [https://www.asp.net/mvc/overview/releases/how-to-upgrade-an-aspnet-mvc-4-and-web-api-project-to-aspnet-mvc-5-and-web-api-2](https://www.asp.net/mvc/overview/releases/how-to-upgrade-an-aspnet-mvc-4-and-web-api-project-to-aspnet-mvc-5-and-web-api-2) + * You need to update your `web.config` file to have the correct WebApi version references - this should be done by doing a compare/merge of your `~/web.config` file with the `~/web.config` file in the release +* MVC has been updated to MVC5 + * You need to update your `web.config` file to have the correct MVC version references - this should be done by doing a compare/merge of your `~/web.config` file with the `~/web.config` file in the release + * The upgrader will take care of updating all other web.config’s (in all other folders, for example, the `Views` and `App_Plugins` folders) to have the correct settings + * For general ASP.NET MVC 5 upgrade details see: [https://www.asp.net/mvc/overview/releases/how-to-upgrade-an-aspnet-mvc-4-and-web-api-project-to-aspnet-mvc-5-and-web-api-2](https://www.asp.net/mvc/overview/releases/how-to-upgrade-an-aspnet-mvc-4-and-web-api-project-to-aspnet-mvc-5-and-web-api-2) +* It is not required that you merge the changes for the Examine index paths in the ExamineIndex.config file. However, if you do, your indexes will be rebuilt on startup because Examine will detect that they don’t exist at the new location. +* It's highly recommended to clear the browser cache - the ClientDependency version is automatically bumped during installation which should force the browser cache to refresh, however in some edge cases this might not be enough. + +Follow the [**upgrade guide for Umbraco 7**](minor-upgrades-for-umbraco-7.md) to complete the upgrade. + +
+ +
+ +7.1.0 to 7.2.0 + +* Copy in the `/Views/Partials/Grid` (contains Grid rendering views). + +Follow the [**upgrade guide for Umbraco 7**](minor-upgrades-for-umbraco-7.md) to complete the upgrade. + +
+ +
+ +7.0.2 to 7.1.0 + +* Remove the `/Install` folder. + +Follow the [**upgrade guide for Umbraco 7**](minor-upgrades-for-umbraco-7.md) to complete the upgrade. + +
+ +
+ +7.0.1 to 7.0.2 + +* There was an update to the `/umbraco/config/create/ui.xml` which needs to be manually updated. The original element had this text: + +```xml + +
User
+ /create/simple.ascx + + + + +
+``` + +* The `usercontrol` value has changed to: `/create/user.ascx`. This is a required change otherwise creating a new user will not work. +* There is a breaking change to be aware of, full details can be found [here](https://umbraco.com/blog/heads-up-breaking-change-coming-in-702-and-62/). + +Follow the [**upgrade guide for Umbraco 7**](minor-upgrades-for-umbraco-7.md) to complete the upgrade. + +
+ +
+ +7.0.0 to 7.0.1 + +* Remove all uGoLive dlls from `/bin` + * These are not compatible with V7 +* Move `appSettings/connectionStrings` back to `web.config` + * If you are on 7.0.0 you should migrate these settings into the web.config instead of having them in separate files in `/config/` + * The keys in `config/AppSettings.config` need to be moved back to the web.config `` section and similarly, the `config/ConnectionStrings.config` holds the Umbraco database connections in v7.0.0 and they should be moved back to the web.config `` section. + * `/config/AppSettings.config` and `/config/ConnectionString.config` can be removed after the contents have been moved back to `web.config`. +* Delete all files in `~/App_Data/TEMP/Razor/` + * Related to issues with razor macros + +Follow the [**upgrade guide for Umbraco 7**](minor-upgrades-for-umbraco-7.md) to complete the upgrade. + +
+ +
+ +6.latest to 7 + +Read and follow [the full v7 upgrade guide](minor-upgrades-for-umbraco-7.md) + +
+ +
+ +4.latest to 6 + +* If your site was ever a version between 4.10.0 and 4.11.4 and you have upgraded to 6.0.0 install the [fixup package](https://our.umbraco.com/projects/developer-tools/path-fixup) and run it after the upgrade process is finished. +* The DocType Mixins package is **not** compatible with v6+ and will cause problems in your Document Types. + +
+ +
+ +Version 4 + +**Version 4.10.x to 4.11.x** + +* If your site was ever a version between 4.10.0 and 4.11.4 install the [fixup package](https://our.umbraco.com/projects/developer-tools/path-fixup) and run it after the upgrade process is finished. + +**Version 4.8.0 to 4.10.0** + +* Delete the `bin/umbraco.linq.core.dll` file +* Copy the new files and folders from the zip file into your site's folder + * `/App_Plugins` + * `/Views` + * `Global.asax` +* Remove the `Config/formHandlers.config` file + +**Version 4.7.2 to 4.8.0** + +* Delete the `bin/App_Browsers.dll` file +* Delete the `bin/App_global.asax.dll` file +* Delete the `bin/Fizzler.Systems.HtmlAgilityPack.dll` file +* For people using uComponents 3.1.2 or below, 4.8.0 breaks support for it. Either upgrade to a newer version beforehand or follow the workaround [posted here](https://our.umbraco.com/projects/backoffice-extensions/ucomponents/questionssuggestions/33021-Upgrading-to-Umbraco-48-breaks-support-for-uComponents) + +**Version 4.7.1.1 to 4.7.2** + +* Delete the `bin/umbraco.MacroEngines.Legacy.dll` file + +**Version 4.6.1 to 4.7.1.1** + +* Delete `bin/Iron*.dll` (all dll files starting with "Iron") +* Delete `bin/RazorEngine*.dll` (all dll files starting with "RazorEngine") +* Delete `bin/umbraco.MacroEngines.Legacy.dll` +* Delete `bin/Microsoft.Scripting.Debugging.dll` +* Delete `bin/Microsoft.Dynamic.dll` + +
diff --git a/16/umbraco-cms/fundamentals/setup/upgrading/version-specific/images/content-on-8_1.png b/16/umbraco-cms/fundamentals/setup/upgrading/version-specific/images/content-on-8_1.png new file mode 100644 index 00000000000..1d8efa9c29d Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/upgrading/version-specific/images/content-on-8_1.png differ diff --git a/16/umbraco-cms/fundamentals/setup/upgrading/version-specific/images/fresh-8_1-site.png b/16/umbraco-cms/fundamentals/setup/upgrading/version-specific/images/fresh-8_1-site.png new file mode 100644 index 00000000000..7914e6644d3 Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/upgrading/version-specific/images/fresh-8_1-site.png differ diff --git a/16/umbraco-cms/fundamentals/setup/upgrading/version-specific/images/nuget-overwrite-dialog.png b/16/umbraco-cms/fundamentals/setup/upgrading/version-specific/images/nuget-overwrite-dialog.png new file mode 100644 index 00000000000..9b8dd02ba54 Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/upgrading/version-specific/images/nuget-overwrite-dialog.png differ diff --git a/16/umbraco-cms/fundamentals/setup/upgrading/version-specific/images/nuget-upgrade-overwrite.png b/16/umbraco-cms/fundamentals/setup/upgrading/version-specific/images/nuget-upgrade-overwrite.png new file mode 100644 index 00000000000..dbf7db39535 Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/upgrading/version-specific/images/nuget-upgrade-overwrite.png differ diff --git a/16/umbraco-cms/fundamentals/setup/upgrading/version-specific/images/set-umbraco-version.png b/16/umbraco-cms/fundamentals/setup/upgrading/version-specific/images/set-umbraco-version.png new file mode 100644 index 00000000000..c0dae2344e2 Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/upgrading/version-specific/images/set-umbraco-version.png differ diff --git a/16/umbraco-cms/fundamentals/setup/upgrading/version-specific/images/upgrade-to-8_1.png b/16/umbraco-cms/fundamentals/setup/upgrading/version-specific/images/upgrade-to-8_1.png new file mode 100644 index 00000000000..8104fa85371 Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/upgrading/version-specific/images/upgrade-to-8_1.png differ diff --git a/16/umbraco-cms/fundamentals/setup/upgrading/version-specific/images/upgrading-7_14.png b/16/umbraco-cms/fundamentals/setup/upgrading/version-specific/images/upgrading-7_14.png new file mode 100644 index 00000000000..af0b40c8db7 Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/upgrading/version-specific/images/upgrading-7_14.png differ diff --git a/16/umbraco-cms/fundamentals/setup/upgrading/version-specific/images/v7-content.png b/16/umbraco-cms/fundamentals/setup/upgrading/version-specific/images/v7-content.png new file mode 100644 index 00000000000..350e15dc720 Binary files /dev/null and b/16/umbraco-cms/fundamentals/setup/upgrading/version-specific/images/v7-content.png differ diff --git a/16/umbraco-cms/fundamentals/setup/upgrading/version-specific/migrate-content-to-umbraco-15.md b/16/umbraco-cms/fundamentals/setup/upgrading/version-specific/migrate-content-to-umbraco-15.md new file mode 100644 index 00000000000..0992507869c --- /dev/null +++ b/16/umbraco-cms/fundamentals/setup/upgrading/version-specific/migrate-content-to-umbraco-15.md @@ -0,0 +1,94 @@ +--- +description: >- + This article will help you migrate content to Umbraco 15, and outline options to skip this content migration +--- + +# Migrate content to Umbraco 15 + +Umbraco 15 changes the internal data format of all [Block Editors](../../../../fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/block-editor/README.md). + +If you maintain a large Umbraco site with extensive Block Editor usage, the upgrade to Umbraco 15+ might require a long-running content migration. For the duration of the migration, your site will be unresponsive and unable to serve requests. + +You can track the progress of the migration in the logs. + +It is advised to [clean up old content versions](../../../../fundamentals/data/content-version-cleanup.md) before upgrading. This will make the migration run faster. + +## Parallelizing the content migration + +It is possible to parallelize the content migration. This will speed up the migration for large sites. + +For certain content structures, parallel content migration will fail. Therefore, parallel content migration is strictly opt-in. + +If parallel content migration fails, the database state will be rolled back to the last known good state. You can then disable parallel content migration, and try the migration again. + +To enable parallel content migration, add an `IComposer` implementation to configure the `ConvertBlockEditorPropertiesOptions` before initiating the upgrade process: + +```csharp +using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_15_0_0; + +namespace UmbracoDocs.Samples; + +public class DisableBlockEditorMigrationComposer : IComposer +{ + [Obsolete] + public void Compose(IUmbracoBuilder builder) + => builder.Services.Configure(options => + { + // setting this to true will parallelize the migration of all Block Editors + options.ParallelizeMigration = true; + }); +} +``` + +## Opting out of the content migration + +It is strongly recommended to let the migration run as part of the upgrade. However, if you are upgrading to Umbraco versions 15, 16, or 17, you _can_ opt out of the migration. Your site will continue to work, albeit with a certain degree of performance degradation. + +{% hint style="warning" %} +Blocks in Rich Text Editors might not work as expected if you opt out of the content migration. +{% endhint %} + +You can opt out of migrating each Block Editor type individually. To opt-out, add an `IComposer` implementation to configure the `ConvertBlockEditorPropertiesOptions` before initiating the upgrade process: + +{% code title="DisableBlockEditorMigrationComposer.cs" %} + +```csharp +using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Infrastructure.Migrations.Upgrade.V_15_0_0; + +namespace UmbracoDocs.Samples; + +public class DisableBlockEditorMigrationComposer : IComposer +{ + [Obsolete] + public void Compose(IUmbracoBuilder builder) + => builder.Services.Configure(options => + { + // setting this to true will skip the migration of all Block List properties + options.SkipBlockListEditors = false; + + // setting this to true will skip the migration of all Block Grid properties + options.SkipBlockGridEditors = false; + + // setting this to true will skip the migration of all Rich Text Editor properties + options.SkipRichTextEditors = false; + }); +} +``` + +{% endcode %} + +Subsequently, you are responsible for performing the content migration yourself. This _must_ be done before upgrading past Umbraco 17. + +Custom code is required to perform the content migration. You can find inspiration in the core migrations: + +- [`ConvertBlockListEditorProperties`](https://github.com/umbraco/Umbraco-CMS/blob/contrib/src/Umbraco.Infrastructure/Migrations/Upgrade/V_15_0_0/ConvertBlockListEditorProperties.cs) for Block List properties. +- [`ConvertBlockGridEditorProperties`](https://github.com/umbraco/Umbraco-CMS/blob/contrib/src/Umbraco.Infrastructure/Migrations/Upgrade/V_15_0_0/ConvertBlockGridEditorProperties.cs) for Block Grid properties. +- [`ConvertRichTextEditorProperties`](https://github.com/umbraco/Umbraco-CMS/blob/contrib/src/Umbraco.Infrastructure/Migrations/Upgrade/V_15_0_0/ConvertRichTextEditorProperties.cs) for Rich Text Editor properties. + +{% hint style="warning" %} +This custom code should not run while editors are working in the Umbraco backoffice. + +The site may require a restart once the content migration is complete. +{% endhint %} diff --git a/16/umbraco-cms/fundamentals/setup/upgrading/version-specific/migrate-content-to-umbraco-8.md b/16/umbraco-cms/fundamentals/setup/upgrading/version-specific/migrate-content-to-umbraco-8.md new file mode 100644 index 00000000000..679e87aa685 --- /dev/null +++ b/16/umbraco-cms/fundamentals/setup/upgrading/version-specific/migrate-content-to-umbraco-8.md @@ -0,0 +1,220 @@ +--- +description: >- + This guide will show you how to migrate the content from your Umbraco 7 site + to a site running Umbraco 8. +--- + +# Migrate content to Umbraco 8 + +Umbraco 8 contains a lot of breaking changes and a lot of code has been cleaned up compared to Umbraco 7. Due to this, it will not be possible to do a direct upgrade from Umbraco 7 to Umbraco 8. You need to **migrate your content** from your Umbraco 7 site into your Umbraco 8 site and then recreate the rest in the new version. + +A content migration tool has been implemented in Umbraco 8.1.0, to help you with the transition. + +In this guide you can read more about the tool, its limitations, and how to use it in practice. + +{% hint style="info" %} +#### Migrating Umbraco Cloud sites + +Follow the [steps outlined in the Umbraco Cloud documentation](https://docs.umbraco.com/umbraco-cloud/upgrades/migrate-from-umbraco-7-to-8) to upgrade your Umbraco 7 site on Cloud. +{% endhint %} + +## What are the limitations? + +In the following section, you can learn more about the limitations of migrating content from Umbraco 7 to Umbraco 8. + +### Versions supported + +The content migration tool is a database migration, which is made for the database schema of Umbraco 7.14+. This means that in order to do the migration you need to ensure your Umbraco 7 site is running at least Umbraco 7.14. + +### Database types supported + +Umbraco 8 does not support MySQL databases. This means that the migration will not work when moving from an Umbraco 7 site using MySQL to Umbraco 8 on SQL Server + +The database types that are supported are SQL Server and SQL CE. + +### Known issues + +Feedback from user testing has shown that some databases are harder to migrate than others. + +We are collecting [a list of these known issues on our GitHub Issue Tracker](https://github.com/umbraco/Umbraco-CMS/issues?utf8=%E2%9C%93\&q=label%3Acategory%2Fcontent-migration+). There is a community package: [Pre-migration health checks](https://our.umbraco.com/packages/developer-tools/pre-migration-health-checks/) that you can install on your Umbraco 7 site before migration. This will help identify and resolve some of these common issues before triggering the migration steps detailed below. + +{% hint style="info" %} +A migration was introduced in Umbraco 8.6 which can break the migration process. See [Issue #7914](https://github.com/umbraco/Umbraco-CMS/issues/7914) for more details. + +There are two ways to work around this issue: + +* Migrate to version 8.5 as a first step and then post-migration, carry out a normal Umbraco upgrade to the latest version of Umbraco 8, or +* Install the following community Nuget Package: [ProWorks Umbraco 8 Migrations](https://www.nuget.org/packages/ProWorks.Umbraco8.Migrations) into your Umbraco 8 project before running the migration (no configuration required). This package was created by Umbraco Gold Partner [ProWorks](https://www.proworks.com/) and patches the migration process so you can migrate directly from the latest Umbraco 7 to Umbraco 8.6+ without encountering the above issue. [Learn more about the package and the migration process on Prowork's blog](https://www.proworks.com/blog/archive/how-to-upgrade-umbraco-version-7-to-version-8). +{% endhint %} + +### Third party property editors + +The migration will transform the data stored in third party editors as well. However, it will be stored as it was in Umbraco 7. If the structure has changed or the property editor doesn't exist, you will still be able to find the data in the database. It will, however, not be available in the backoffice. + +
+ +Learn more about that in the Data Types Migrations + +**Migrating data types** + +When migrating content from Umbraco 7 to Umbraco 8, the Data Type 'pre-value' structure has changed. In Umbraco 8, the term 'pre-values' no longer exists and is instead referred to as `property editor configuration`. + +In Umbraco 8, property editor configuration is a strongly typed object. There are plenty of examples in the [Umbraco-CMS codebase](https://github.com/umbraco/Umbraco-CMS/blob/v8/dev/src/Umbraco.Web/PropertyEditors/ContentPickerConfiguration.cs). + +This configuration is stored differently in Umbraco 8 than it was in Umbraco 7. In Umbraco 7, each pre-value property was stored as a different row in a different database table. In Umbraco 8 this is simplified and property editor configuration is stored as the JSON serialized version of the strongly typed configuration object. + +When upgrading from Umbraco 7 to Umbraco 8, Umbraco has no way of knowing how custom property editors have intended to structure their configuration data. During the upgrade, Umbraco will convert the key/value pairs from the old pre-value database table into a serialized JSON version of those values. There is a reasonable chance that the end result of this data conversion is not compatible with the custom property editor. + +There are 3 options that a developer can choose to do to work around this automatic data conversion: + +**1: Implement a custom `IPreValueMigrator`** + +This option requires you to create a custom C# migrator for each of your custom property editors that store custom configuration data. It will also require that you implement these migrators before you run the Umbraco 8 content migration. + +To do this, you will create an implementation of `IPreValueMigrator` or inherit from the base class [`DefaultPreValueMigrator`](https://github.com/umbraco/Umbraco-CMS/blob/v8/dev/src/Umbraco.Core/Migrations/Upgrade/V\_8\_0\_0/DataTypes/DefaultPreValueMigrator.cs). + +There are plenty of examples of this in the [Umbraco-CMS codebase](https://github.com/umbraco/Umbraco-CMS/tree/v8/dev/src/Umbraco.Core/Migrations/Upgrade/V\_8\_0\_0/DataTypes). + +You will then need to register them in a composer: + +```csharp +[RuntimeLevel(MinLevel = RuntimeLevel.Upgrade, MaxLevel = RuntimeLevel.Upgrade)] // only on upgrades +public class PreValueMigratorComposer : IUserComposer +{ + public void Compose(Composition composition) + { + composition.WithCollectionBuilder() + // Append all of the migrators required + .Append() + .Append(); + } +} +``` + +When running the migrations and encountering a custom configuration, Umbraco will utilize the `PreValueMigrator` when converting the old pre-values into the new JSON format. + +**2: Update your Angular configuration (pre-value) and property editor** + +This option means that you will choose to use the automatically converted JSON data format. In this case, it will mean updating your pre-value and property editors to use the new JSON configuration data. The converted data won't be much different than the original/intended data format so this might not be too much work. + +**3: Update the Angular configuration (pre-value) editor** + +With this option the configuration/pre-value editor needs to be updated to transform the JSON converted data into the data structure you want. When this is done and when the Data Type is saved again, the JSON data structure will be saved back to the database. Your property editor will then continue to work. + +This will require you to update and save all custom pre-value editors to transform the converted structures back to your intended data structure. + +
+ +## What will happen + +When the migrations are running, Umbraco will go through your entire Umbraco 7 database and update it to the format required for Umbraco 8. The schema will be remodeled and transformed into the correct format and your existing compatible data will be transformed to fit with Umbraco 8. + +These migrations will be running directly on your database. They are transforming schema and data - not transferring. Therefore always ensure that you have a backup before attempting to do this. In case something goes wrong, you will be able to rollback and try again. + +It is highly recommended to clean up your site before running this as it will be quicker. + +* Empty Content recycle bin +* Empty Media recycle bin +* Clean up the database version history (can be done with a script or a package like [Unversion](https://our.umbraco.com/packages/website-utilities/unversion/)) + +## How it works + +In the following guide we will migrate the content of an Umbraco 7.13.1 site to Umbraco 8.1.0. + +### Step 1: Upgrading to 7.14+ + +Before the content migration can start the site has to run Umbraco 7.14+. Make sure to **always take a backup of the database** before doing an upgrade, and then check the [version specific upgrade instructions](./). + +The site in this example is an Umbraco 7.13.1 site, and we will use Nuget to update it. + +![v7 site with content](images/v7-content.png) + +Following the [general upgrade instructions](../) we will now upgrade via Nuget until we get to this point: + +![Upgrading to v7.14](images/upgrading-7_14.png) + +{% hint style="warning" %} +When upgrading an old website, check if you are using obsolete properties in your Data Types. These should be changed to their updated counterparts. The migration **will fail if you are still using obsolete properties.** + +The updated properties are: + +* Content Picker +* Media Picker +* Member Picker +* Multinode Treepicker +* Nested Content +* Folder Browser +* Related Links + +You can see if your site is using the obsolete properties from the `(Obsolete)` prefix in their name. +{% endhint %} + +Install the [Pre-migration health checks plugin](https://our.umbraco.com/packages/developer-tools/pre-migration-health-checks/), and run it health check from the Developer section of the backoffice. This is done to identify and resolve some common database schema issues before migration. + +Once it is upgraded and you have verified everything is working, move on to the next step. + +### Step 2: Migrating content to Umbraco 8 + +The first thing to do is to spin up a fresh new Umbraco 8.1+ site. Make sure everything works and that no content is there. + +![Fresh 8.1 site](images/fresh-8_1-site.png) + +{% hint style="warning" %} +If you have customized the `UsersMembershipProvider` on your Umbraco 7 site you need to copy that over to the 8.1 `web.config` as well. Additionally you need to update the `type` attribute to be `type="Umbraco.Web.Security.Providers.UsersMembershipProvider, Umbraco.Web"`. + +This also includes the attribute `useLegacyEncoding` value. Make sure that this setting is copied into your new Umbraco 8 site, as it is needed in order to log in. +{% endhint %} + +Take a backup of your database from the **Umbraco 7.14 site**. Take the information for the backup database and add that to the connectionstring for the **Umbraco 8.1 site**. If you are running SQL CE, you will have to copy the database over to the new site as well. + +Once the connectionstring is set, the final step is to change the Umbraco version number in the `web.config` on the **Umbraco 8.1 site**. Chang it to `7.14.0`. This will indicate that there is an upgrade pending and it needs to run the migration. + +![Set Umbraco version in the web.config](images/set-umbraco-version.png) + +The version will be set to 8.1.0, and you need to change it to the version you are currently migrating from. + +When you start the site it will ask you to login and then show you this screen: + +![Upgrade database to 8.1](images/upgrade-to-8_1.png) + +From here, the automatic migration will take over, and after a little bit you can log in and see your content: + +![Content is on 8.1](images/content-on-8_1.png) + +{% hint style="info" %} +Please be aware that this is a **content migration**. If you go to the frontend after following these steps, it will throw errors. + +At this point you will have the content but nothing else. +{% endhint %} + +## Step 3: Files migration + +Before moving on to this step, make sure that the Umbraco 8 project is no longer running. + +The following files/folders need to be copied into the Umbraco 8 project: + +* `~/Views` - do **not** overwrite the default Macro and Partial View Macro files, unless changes have been made to these. +* `~/Media` +* Any files/folders related to Stylesheets and JavaScripts. +* Any custom files/folders the Umbraco 7 project uses, that aren't in the `~/Config` or `~/bin`. +* `~/App_Data/UmbracoForms` - in the case Umbraco Forms was used on the Umbraco 7 site. + +**Merge the configuration files carefully** to ensure any custom settings are migrated while none of the default configurations for Umbraco 8 is overwritten. + +You'll have to revisit all templates and custom implementations to get the site up and running, as all markup is still Umbraco 7-specific. + +{% hint style="info" %} +Are you planning on continuing the migration to the latest version on Umbraco CMS? + +Then you can skip the step to revisit the template files and custom implementation. We highly recommend waiting with this step until you've reached the latest version. + +If you're stopping at Umbraco 8, you can learn more about [rendering content on the Legacy Docs site](https://our.umbraco.com/Documentation/Fundamentals/Design/Rendering-Content/). +{% endhint %} + +### Step 4: Post-migration checks + +As you are updating your template files and custom implementation, you should also verify your configuration files and settings. + +Umbraco 8 contains a few changes regarding the Sections in the Umbraco Backoffice. Because of this, you should also check your User Groups and make sure they have access to the appropriate sections. + +Learn more about the Section in the [Sections article](../../../backoffice/sections.md) diff --git a/16/umbraco-cms/fundamentals/setup/upgrading/version-specific/minor-upgrades-for-umbraco-7.md b/16/umbraco-cms/fundamentals/setup/upgrading/version-specific/minor-upgrades-for-umbraco-7.md new file mode 100644 index 00000000000..45600da6366 --- /dev/null +++ b/16/umbraco-cms/fundamentals/setup/upgrading/version-specific/minor-upgrades-for-umbraco-7.md @@ -0,0 +1,103 @@ +--- +description: >- + This article provides details on how to upgrade to the next minor version when + using Umbraco 7. +--- + +# Minor upgrades for Umbraco 7 + +Sometimes there are exceptions to these guidelines, which are listed in the [**version-specific guide**](./). + +## Note + +It is necessary to run the upgrade installer on each environment of your Umbraco site. If you want to update your staging and live site then you need to repeat the steps below and make sure that you click through the install screens to complete the upgrade. + +## Contents + +In this article you will find instructions for 2 different ways of upgrading: + +* [Upgrade using NuGet](minor-upgrades-for-umbraco-7.md#upgrade-using-nuget) +* [Upgrade manually from a Zip file](minor-upgrades-for-umbraco-7.md#upgrade-manually-from-a-zip-file) + +## Upgrade using NuGet + +1. Open up the **Package Console** and type: `Update-Package UmbracoCms` +2. Choose **"No to All"** by pressing the **"L"** when prompted. + * If there are any specific configuration changes required for the version you are upgrading to then they will be noted in the [**version-specific guide**](./). + +Alternatively, you can use the Visual Studio **NuGet Package Manager** to upgrade: + +1. Open the **NuGet Package Manager** and select the **Updates** pane to get a list of available updates. +2. Choose the package called **UmbracoCms** and select update. + +The upgrade will run through all the files and make sure you have the latest changes while leaving the files you have updated. + +### Upgrades to versions lower than 7.2.0 + +If you're not upgrading to 7.2.0 or higher then you should follow these extra instructions. If you are upgrading to 7.2.0+ then you can skip this and go to [Merge UI.xml and language](minor-upgrades-for-umbraco-7.md#merge-ui-xml-and-language). + +You will be asked to overwrite your web.config file and the files in /config, make sure to answer **No** to those questions. + +For some inexplicable reason, the installation will fail if you click "No to All" (in the GUI) or answer "L" (in the package manager console) to the question: "File 'Web.config' already exists in project 'MySite'. Do you want to overwrite it?" So make sure to only answer "**No**" (in the GUI) or "**N**" (in the package manager console). + +![File conflict dialog with a web.config file in conflict](<../../../../../../10/umbraco-cms/fundamentals/setup/upgrading/images/nuget-overwrite-dialog (1) (1).png>) ![File conflict console message with multiple files in conflict](<../../../../../../10/umbraco-cms/fundamentals/setup/upgrading/images/nuget-upgrade-overwrite (1).png>) + +We will overwrite the `web.config` file. We'll back it up so don't worry. You can find the backup in `App_Data\NuGetBackup\20140320-165450\`. The `20140320-165450` bit is the date and time when the backup occurred, which varies. You can then merge your config files and make sure they're up to date. + +## Upgrade manually from a zip file + +Download the .zip file for the new version you are upgrading to from [https://our.umbraco.com/download](https://our.umbraco.com/download) + +Copy the following folders from inside the .zip file over the existing folders in your site: + +* `/bin` +* `/Umbraco` +* `/Umbraco_Client` + +{% hint style="info" %} +There are hosting providers (we know of one: RackSpace Cloud) that require proper casing of file and folder names. Generally, on Windows, this is not a problem. Is your hosting provider forcing proper casing? You'll then need to verify that folders and files are named in the same casing as the version you're upgrading to. +{% endhint %} + +## Merge configuration files + +You can expect some changes to the following configuration files: + +* Any file in the `/Config` folder +* The `/Global.asax` file +* The `web.config` file in the root of your site **(Important: make sure to copy back the version number, and the connection string as they were.)** +* In rare cases, the `web.config` file in the Views folder + +Use a tool like [WinMerge](http://winmerge.org/) to check changes between all of the config files. Depending on when you last did this there may have been updates to a few of them. + +There's also the possibility that files in the `/Config` folder are new or have been removed(we note this in the release notes). WinMerge (and other diff tools) is able to compare folders as well so you can spot these differences. + +Up until version 6.0.0 it was necessary to change the version number in `ClientDependency.config`. This was to clear the cached HTML/CSS/JS files in the backoffice. Change the current version number to one that's higher than that. Make sure not to skip this step as you might get strange behavior in the backoffice otherwise. + +## Merge UI.xml and language + +Some packages (like Contour and Umbraco Forms) add dialogs to the `UI.xml`. Make sure to merge those changes back in from your backup during the upgrade so that the packages continue to work. This file can be found in: `/Umbraco/Config/Create/UI.xml`. + +Packages like Contour, Umbraco Forms, and Courier also make changes to the language files located in: `/Umbraco/Config/Lang/*.xml` (typically `en.xml`). + +## Finalize + +After copying the files and making the config changes, you can open your site. You should see the installer which will guide you through the upgrade. + +The installer will do two things: + +* Update the version number in the `web.config` +* Upgrade your database in case there are any changes + +We are aware that, currently, the installer is asking you for the database details of a **blank database** while upgrading. In the near future this will be pre-filled with your existing details and the wording will be updated. So no need to be scared. Enter the details of your existing database and Umbraco will upgrade it to the latest version when necessary. + +## Post installation + +One important recommendation is to always remove the `install` folder immediately after upgrading Umbraco and never to upload it to a live server. + +## Potential issues and gotchas + +### Browser cache + +Google Chrome has notoriously aggressive caching. If something doesn't seem to work well in the backoffice, make sure to clear cache and cookies thoroughly (for other browsers as well). Normally the browser cache problem is automatically handled in an Umbraco upgrade by modifying the config/ClientDependency.config version number. If you wish to re-force this update you can increment this version number. This will ensure that any server-side cache of JavaScript and stylesheets gets cleared as well. + +One way to nudge the cache in Chrome is to open the developer tools (F12) and go to the settings (the cog icon). There will be a checkbox that says "Disable cache (while DevTools is open)". Once this checkbox is on you can refresh the page and the cache should be invalidated. To force it even more, the "reload" button next to your address bar now has extra options when you right-click it. It should have "Normal reload", "Hard reload" and "Empty cache and hard reload" now. The last option is the most thorough and you might want to try that. diff --git a/16/umbraco-cms/fundamentals/setup/upgrading/version-specific/minor-upgrades-for-umbraco-8.md b/16/umbraco-cms/fundamentals/setup/upgrading/version-specific/minor-upgrades-for-umbraco-8.md new file mode 100644 index 00000000000..507f7feaecb --- /dev/null +++ b/16/umbraco-cms/fundamentals/setup/upgrading/version-specific/minor-upgrades-for-umbraco-8.md @@ -0,0 +1,148 @@ +--- +description: This article provides details on how to upgrade to the next minor version when using Umbraco 8. +--- + +# Minor upgrades for Umbraco 8 + +Sometimes there are exceptions to these guidelines, which are listed in the **[version-specific guide](README.md)**. + +## Note + +It is necessary to run the upgrade installer on each environment of your Umbraco site. If you want to update your staging and live site you need to repeat the steps below. Make sure you click through the install screens so that your upgrade is complete. + +## Contents + +In this article you will find instructions for 3 different ways of upgrading: + +* [Upgrade using NuGet](#upgrade-using-nuget) +* [Upgrade manually from a Zip file](#upgrade-manually-from-a-zip-file) +* [Run an unattended upgrade (v8.12+)](#run-an-unattended-upgrade) + +## Upgrade using NuGet + +1. Open up the **Package Console** and type: `Update-Package UmbracoCms` +2. Choose **"No to All"** by pressing the **"L"** when prompted. + * If there are any specific configuration changes required for the version you are upgrading to then they will be noted in the **[version-specific guide](README.md)**. + +Alternatively, you can use the Visual Studio **NuGet Package Manager** to upgrade: + +1. Open the **NuGet Package Manager** and select the **Updates** pane to get a list of available updates. +2. Choose the package called **UmbracoCms** and select update. + +The upgrade will run through all the files and make sure you have the latest changes while leaving files you have updated. + +## Upgrade manually from a zip file + +Download the `.zip` file for the new version you are upgrading to from [https://our.umbraco.com/download](https://our.umbraco.com/download) + +Copy the following folders from inside the `.zip` file over the existing folders in your site: + +* `/bin` +* `/Umbraco` + +{% hint style="info" %} +There are hosting providers (we know of one: RackSpace Cloud) that require proper casing of file and folder names. Normally on Windows this is not a problem. If your hosting provider however forces proper casing, you will need to verify that the folder and file names are in the same casing as in the newest version you're upgrading to. +{% endhint %} + +### Merge configuration files + +You can expect some changes to the following configuration files: + +* Any file in the `/Config` folder +* The `/Global.asax` file +* The `web.config` file in the root of your site **(Important: make sure to copy back the version number, and the connection string as they were.)** +* In rare cases, the `web.config` file in the `/Views` folder + +Use a tool like [WinMerge](http://winmerge.org/ "WinMerge") to check changes between all of the config files. Depending on when you last did this there may have been updates to few of them. + +There's also the possibility that some files in the `/Config` folder are new or some have been removed (we do make a note of this in the release notes). WinMerge (and other diff tools) can compare folders as well so you can spot these differences. + +### Merge UI.xml and language files + +Some packages like Umbraco Forms add dialogs to the `UI.xml`. Make sure to merge those changes back in from your backup during the upgrade so that the packages continue to work. This file can be found in: `/Umbraco/Config/Create/UI.xml`. + +Packages like Umbraco Forms and Courier also make changes to the language files located in: `/Umbraco/Config/Lang/*.xml` (typically `en.xml`). + +### Finalize + +After copying the files and making the config changes, you can open your site. You should see the installer which will guide you through the upgrade. + +The installer will do two things: + +* Update the version number in the `web.config` +* Upgrade your database in case there are any changes + +We are aware that, currently, the installer is asking you for the database details of a **blank database** while upgrading. In the near future this will be pre-filled with your existing details and the wording will be updated. So no need to be scared. Enter the details of your existing database and Umbraco will upgrade it to the latest version when necessary. + +## Run an unattended upgrade + +When upgrading your Umbraco project to Umbraco v8.12+ it is possible to enable the upgrade to run unattended. This means that you will not need to run through the installation wizard when upgrading. + +Below you will find the steps you need to take in order to upgrade your project unattended. + +{% hint style="info" %} +Are you running a load balanced setup with multiple servers and environments? + +Check out the section about [Unattended upgrades in a load balanced setup](#unattended-upgrades-in-a-load-balanced-setup). +{% endhint %} + +### Enable the feature + +1. Add the `Umbraco.Core.RuntimeState.UpgradeUnattended` key to `appSettings` in your web.config file. +2. Set the value of the key to `true`. + +```xml + +``` + +### Check the `ConfigurationStatus` + +In order to trigger the actual upgrade, the correct version number needs to be set. + +It is important to use the version number of the version that you are upgrading to. If this is not set, the upgrade will not run even if the `UpgradeUnattended` key has been set to `true`. + +1. Locate the `ConfigurationStatus` key in the `appSettings` section in your web.config file. +2. Update the value to match the Umbraco version that you are upgrading to. + +```xml + +``` + +### Run the upgrade + +With the correct configuration applied, the project will be upgraded on the next boot. + +{% hint style="info" %} +While the upgrade processes are running, any requests made to the site will be "put on hold", meaning that no content will be returned before the upgrade is complete. +{% endhint %} + +#### Boot order + +The Runtime level will use `Run` instead of `Upgrade` in order to allow the website to continue to boot up directly after the migration is run, instead of initiating the otherwise required restart. + +{% hint style="info" %} +The upgrade is run after Composers but before Components. This is because the migration requires services that are registered in Composers and Components requires that Umbraco and the database is ready. +{% endhint %} + +### Unattended upgrades in a load balanced setup + +Follow the steps outlined below to use run unattended upgrades in a load balanced setup. + +1. Upgrade Umbraco via NuGet in Visual Studio. Make sure the `Umbraco.Core.ConfigurationStatus` key in `appSetting` in the `web.config` file is updated to match the **target version**. +2. Deploy to all environments, including the updated `appSetting` for `Umbraco.Core.ConfigurationStatus`. +3. Set the `Umbraco.Core.RuntimeState.UpgradeUnattended` key in `appSetting` in the `web.config` to `true` for **the Main server only**. +4. Request a page on the Main server and the upgrade will run automatically. +5. Wait for the upgrade to complete. +6. Browse the Read-Only servers and make sure they do not show the “upgrade required” screen. + +## Post installation + +One important recommendation is to always remove the `install` folder immediately after upgrading Umbraco and never to upload it to a live server. + +## Potential issues and gotchas + +### Browser cache + +Google Chrome has notoriously aggressive caching, so if something doesn't seem to work well in the backoffice, make sure to clear cache and cookies thoroughly (for other browsers as well). Normally the browser cache problem is automatically handled in an Umbraco upgrade by modifying the config/ClientDependency.config version number. If you however wish to re-force this update you can increment this version number which will ensure that any server-side cache of JavaScript and stylesheets gets cleared as well. + +One way to nudge the cache in Chrome is to open the developer tools (F12) and go to the settings (the cog icon). There will be a checkbox that says "Disable cache (while DevTools is open)". Once this checkbox is on you can refresh the page and the cache should be invalidated. To force it even more, the "reload" button next to your address bar now has extra options when you right-click it. It should have "Normal reload", "Hard reload" and "Empty cache and hard reload" now. The last option is the most thorough and you might want to try that. diff --git a/16/umbraco-cms/fundamentals/setup/upgrading/version-specific/upgrade-from-8-to-latest.md b/16/umbraco-cms/fundamentals/setup/upgrading/version-specific/upgrade-from-8-to-latest.md new file mode 100644 index 00000000000..8da11efdc80 --- /dev/null +++ b/16/umbraco-cms/fundamentals/setup/upgrading/version-specific/upgrade-from-8-to-latest.md @@ -0,0 +1,110 @@ +--- +description: >- + Learn how to upgrade your Umbraco 8 project to Umbraco 10. +--- + +# Upgrade from Umbraco 8 to Umbraco 10 + +{% hint style="danger" %} +It is currently not possible to upgrade directly from **Umbraco 8 to the latest version**. + +The recommended approach for upgrading from version 8 to the latest version is to use this guide to upgrade from _Umbraco 8 to Umbraco 10_. Umbraco 10 contains the [database migrations](https://github.com/umbraco/Umbraco-CMS/blob/release-10.0.0/src/Umbraco.Infrastructure/Migrations/Upgrade/UmbracoPlan.cs#L66-L73) that must be upgraded from Umbraco 8. You can then use the [Upgrading to Major](../#upgrade-to-a-new-major) steps to upgrade from _Umbraco 10 to the latest version_. +{% endhint %} + +Since the underlying framework going from Umbraco 8 to the latest version has changed, there is no direct upgrade path. That said, it is possible to re-use the database from your Umbraco 8 project on your new project in order to maintain the content. + +It is not possible to migrate the custom code as the underlying web framework has been updated from ASP.NET to ASP.NET Core. All templates and custom code will need to be reimplemented. + +You also need to make sure that the packages you are using are available on the latest version. + +## Prerequisites + +* A Umbraco 8 project running **the latest version of Umbraco 8**. +* A backup of your Umbraco 8 project database. +* A clean installation of the latest version of Umbraco. + +{% hint style="info" %} +If you use Umbraco Forms, then on the clean installation of Umbraco, you will need to install `Umbraco.Forms` package as well. +{% endhint %} + +## Video Tutorial + +{% hint style="warning" %} +The video below shows how to complete the upgrade on an Umbraco Cloud project. Most of the process is the same, however, the video does contain some Cloud-specific elements. +{% endhint %} + +{% embed url="https://www.youtube-nocookie.com/embed/wD9SGeRQR7o" %} +A video tutorial guiding you through the steps of upgrading from version 8 to the latest version on Umbraco Cloud. +{% endembed %} + +## Step 1: Content Migration + +{% hint style="warning" %} +If you use Umbraco Forms, make sure to have [`StoreUmbracoFormsInDbset`](https://docs.umbraco.com/umbraco-forms/developer/forms-in-the-database#enable-storing-forms-definitions-in-the-database)to `True` before **step 1**. +{% endhint %} + +1. Create a backup of the database from your Umbraco 8 project (after you have upgraded to the latest version of v8). For this, you can use the [database backup guide](https://docs.umbraco.com/umbraco-cloud/databases/backups#backup-with-sql-server-management-studio). +2. Import the database backup into SQL Server Management Studio. +3. Update the connection string in the new projects `appsettings.json` file so that it connects to the Umbraco 8 database: + +```json +"ConnectionStrings": { + "umbracoDbDSN": "Server=YourLocalSQLServerHere;Database=NameOfYourDatabaseHere;User Id=NameOfYourUserHere;Password=YourPasswordHere;TrustServerCertificate=True" +} +``` + +{% hint style="info" %} +You can also add the connection details if you spin up a clean installation. +{% endhint %} + +4. Run the new project and login to authorize the upgrade. +5. Select "Upgrade" when the upgrade wizard appears. +6. Once the upgrade has been completed, it's recommended to login to the backoffice to verify if your project is upgraded to new version. + +{% hint style="success" %} +This is **only content migration** and the database will be migrated. + +You need to manually update the view files and custom code implementation. For more information, see Step 3 of this guide. +{% endhint %} + +## Step 2: File Migration + +1. The following files/folders need to be copied from the Umbraco 8 project into the new project: + * `~/Views` - **Do not** overwrite the default Macro and Partial View Macro files unless changes have been made to these. + * `~/Media` - Media folder from v8 needs to be copied over into the `wwwroot - media` folder + * Any files/folders related to Stylesheets and JavaScript. +2. Migrate custom configuration from the Umbraco 8 configuration files (`.config`) into the `appsettings.json` file on the new project. + * As of Umbraco version 9, the configuration no longer lives in the `Web.Config` file and has been replaced by the `appsettings.json` file. Learn more about this in the [Configuration](../../../../reference/configuration/) article. +3. [Migrate Umbraco Forms data to the database](https://docs.umbraco.com/umbraco-forms/developer/forms-in-the-database#migrating-forms-in-files-into-a-site), if relevant. + * As of Umbraco Forms version 9, it is only possible to store Forms data in the database. If Umbraco Forms was used on the Umbraco 8 project, the files need to be migrated to the database. +4. Run the new project. + * It **will** give you an error screen on the frontend as none of the Template files have been updated. Follow **Step 3** to resolve the errors. + +## Step 3: Custom Code in the latest version + +The latest version of Umbraco is different from Umbraco 8 in many ways. With all the files and data migrated it is now time to rewrite and re-implement all custom code and templates. + +### Examples of changes + +One of the changes is how published content is rendered through Template files. Due to this, it will be necessary to update **all** the Template files (`.cshtml`) to reflect these changes. + +Read more about these changes in the [IPublishedContent](../../../../reference/querying/ipublishedcontent/) section of the Umbraco CMS documentation. + +* Template files need to inherit from `Umbraco.Cms.Web.Common.Views.UmbracoViewPage` instead of `Umbraco.Web.Mvc.UmbracoViewPage` +* Template files need to use `ContentModels = Umbraco.Cms.Web.Common.PublishedModels` instead of `ContentModels = Umbraco.Web.PublishedModels` + +{% hint style="info" %} +For more information on the correct namespaces or custom code, you can find the references in the [API Documentation](../../../../reference/api-documentation.md) article. +{% endhint %} + +Depending on the extent of the project and the amount of custom code and implementations, this step is going to require a lot of work. + +Once the new project runs without errors on a local setup it is time to deploy the website to production. + +This concludes this tutorial. Find related information and further reading in the section below. + +## Related Information + +* [Issue tracker for known issues with Content Migration](https://github.com/umbraco/UmbracoDocs/issues) +* [Configuration in modern Umbraco](../../../../reference/configuration/) +* [Configuration in legacy Umbraco](https://our.umbraco.com/documentation/Reference/Configuration-for-Umbraco-7-and-8/) diff --git a/16/umbraco-cms/fundamentals/setup/upgrading/version-specific/upgrade-to-umbraco-7.md b/16/umbraco-cms/fundamentals/setup/upgrading/version-specific/upgrade-to-umbraco-7.md new file mode 100644 index 00000000000..01ba69a520a --- /dev/null +++ b/16/umbraco-cms/fundamentals/setup/upgrading/version-specific/upgrade-to-umbraco-7.md @@ -0,0 +1,176 @@ +--- +description: >- + This document should be used as a reference, not a step by step guide. + Upgrading will largely depend on what version of Umbraco you are currently + running, what packages you have installed and the many +--- + +# Upgrade to Umbraco 7 + +The [standard upgrade instructions](../) still apply to this process as well. + +## Backup + +It is critical that you back up your website and database before upgrading. There are database changes made during installation and you cannot revert an Umbraco 7 database to an Umbraco 6 database. + +## .Net 4.5 + +Umbraco 7 is built on .Net 4.5 and your development environment will require this version installed in order to operate. Visual Studio users may require 2012 or higher. + +## HTML 5 browser support + +Umbraco 7 requires browsers with proper HTML 5 support, these include Chrome, Firefox, IE10+ + +## Breaking changes + +Before you upgrade be sure to read the list of breaking changes. This is especially recommended if you have removed or modified code in the core or if one of these breaking changes directly affects your installation. + +[See the list of breaking changes](https://our.umbraco.com/contribute/releases/700) for more details. + +## Examine + +It is recommended to rebuild all Examine indexes after completing the upgrade. + +## Xml Cache rebuild + +You should re-generate the XML cache. This can be done by following the prompts when visiting the following URL: + +`your-domain.com/umbraco/dialogs/republish.aspx?xml=true` + +## Configuration changes + +It is recommended that you use a Diff tool to compare the configuration file changes with your own current configuration files. + +* `/web.config` updates + * Details are listed here: [https://issues.umbraco.org/issue/U4-2900](https://issues.umbraco.org/issue/U4-2900) + * You will need to compare the new Umbraco 7 `web.config` with your current `web.config`. Here is a quick reference of what needs to change: + * Remove the `section name="BaseRestExtensions"` section + * Remove the `section name="FileSystemProviders"` section + * Remove the `sectionGroup name="system.web.webPages.razor"` section + * Remove the `` element + * Remove the `BaseRestExtensions` element + * Remove the `add key="umbracoUseMediumTrust"` element + * Remove the `system.web.extensions` element + * Removes the `xhtmlConformance` element + * Remove the `system.codedom` element + * Remove the `compilation` assemblies, `/compilation` + * Remove the `system.web.webPages.razor` element + * New: `sectionGroup name="umbracoConfiguration"` section + * New: `umbracoConfiguration` element + * Ensure that the `targetFramework="4.5"` is added to the `httpRuntime` element + * Add `add key="ValidationSettings:UnobtrusiveValidationMode" value="None"` to the `appSettings` element +* `/config/clientdependency.config` changes + * remove `add name="CanvasProvider"` element +* `/views/web.config` updates +* New `macroscripts/web.config` +* `config/umbracoSettings.config` + * Umbraco is now shipped with minimal settings but the [full settings](https://our.umbraco.com/documentation/Using-Umbraco/Config-files/umbracoSettings/) are still available + * `umbracoSettings` is now a true ASP.NET configuration section [https://issues.umbraco.org/issue/U4-58](https://issues.umbraco.org/issue/U4-58) + * Remove the `EnableCanvasEditing` element + * Remove the `webservices` element +* Removed `xsltExtensions.config` + * [https://issues.umbraco.org/issue/U4-2742](https://issues.umbraco.org/issue/U4-2742) +* `/config/applications.config` and `/config/trees.config` have some icon paths and names updated. You need to merge the new changes into your existing config files. +* `/config/tinyMceConfig.config` + * The `inlinepopups` is compatible and supported in Umbraco 7. You need to remove these elements: `plugin loadOnFrontend="true"`, `inlinepopups/plugin`; + * The plugins element that is shipped with Umbraco 7 looks like this: + + ```xml + + code + paste + umbracolink + anchor + charmap + table + lists + + ``` + + * You need to merge the changes from the new `tinyMceConfig` file into yours. The `command` elements that have changed are: `JustifyCenter`, `JustifyLeft`, `JustifyRight`, `JustifyFull`, `umbracomacro`, `umbracoembed`, `mceImage`, `subscript`, `superscript`, `styleselect` + * Remove the command: `mceSpellCheck` +* `/config/dashboard.config` + * You need to merge the changes from the new `dashboard.config` into yours. Some of the original dashboard entries that were shipped with Umbraco 6 have been replaced or removed. + +## Medium Trust + +Umbraco 7+ will no longer support medium trust environments. There are now some assemblies used in the core that do not support medium trust but are used extensively. Plugin scanning now also allows for scanning Umbraco's internal types which requires full trust. + +## Events + +### Tree events + +Content, Media, Members, and Data Type trees will no longer raise the legacy tree events (based on BaseTree). It is recommended to change all tree event handlers to use the new tree events that fire for every tree in Umbraco including legacy trees. The new tree events are static events and are found in the class `Umbraco.Web.Trees.TreeControllerBase`: + +* `MenuRendering` +* `RootNodeRendering` +* `TreeNodesRendering` + +### Legacy business logic events + +The Content, Media, Member, and Data Type editors have been re-created and are solely using the new Umbraco Services data layer. This means that operations performed in the backoffice will no longer raise the legacy business logic events (for example, events based on `umbraco.cms.businesslogic.web.Document`). It is recommended to change your event handlers to subscribe to the new Services data layer events. These are static events and are found in the services. For example: `Umbraco.Core.Services.ContentService.Saved`. + +## Property Editors + +Legacy property editors (pre-Umbraco 7) will not work with Umbraco 7. During the upgrade installation process, Umbraco will generate a report showing you which legacy property editors are installed. These will all be converted to a `readonly` Label property editor. No data loss will occur but you'll need to re-assign your existing data types to use a new compatible Umbraco 7 property editor. + +Most Umbraco core property editors shipped will be mapped to their equivalent Umbraco 7 editors. The Image cropper editor has not been completed for v7.0. + +### The Related Links property editor and XSLT + +Since the Related Links property is an advanced property editor, the data format has changed from XML to JSON. This should not have any effect when retrieving the data from razor. If you are outputting Related Links data with XSLT you will need to update your XSLT snippet. Making use of the new library method `umbraco.library:JsonToXml` and taking into account that the xml structure has also slightly changed. + +### GUID -> Alias mapping + +One of the database changes made in Umbraco 7 is the change of referencing a property editor from a GUID to a string alias. In order to map a legacy property editor to a new Umbraco 7 version you can add your custom "GUID -> Alias" map during application startup. To do this you would add your map using this method: `Umbraco.Core.PropertyEditors.LegacyPropertyEditorIdToAliasConverter.CreateMap` + +## Parameter Editors + +Legacy parameter editors (pre-Umbraco 7) will not work with Umbraco 7. If Umbraco detects legacy parameter editor aliases that do not map to a Umbraco 7 parameter editor it will render a textbox in its place. You will need to update your macros to use a compatible Umbraco 7 parameter editor as those that aren't supported. + +Previously, parameter editors were registered in an Umbraco database table: `cmsMacroPropertyType` which no longer exists. Parameter editors in Umbraco 7 are plugins like property editors. During the Umbraco 7 upgrade installation process it will update the new `cmsMacroProperty.editorAlias` column with the previous parameter editor alias. During this process it will look into the `Umbraco.Core.PropertyEditors.LegacyParameterEditorAliasConverter` for a map between a legacy alias to a new Umbraco 7 alias. + +Custom legacy parameters can be mapped to new Umbraco 7 parameter editor aliases during installation. This can be done by modifying the mapping during application startup using this method: `Umbraco.Core.PropertyEditors.LegacyParameterEditorAliasConverter.CreateMap`. + +## Database changes + +All database changes will be taken care of during the upgrade installation process. + +For database change details see (including all child tasks): + +* [Issue U4-2886](https://issues.umbraco.org/issue/U4-2886) +* [Issue U4-3015](https://issues.umbraco.org/issue/U4-3015) + +## Tags + +See above for the database updates made for better tag support. + +* Tags can now be assigned to a nodes property and not only a node +* Multiple tag controls can exist on one page with different data + * The legacy API does **not** support this, the legacy API will effectively, add/update/remove tags for the first property found for the document that is assigned a tag property editor. +* There is a new ITagService that can be used to query tags + * Querying for tags in a view (front-end) can be done via the new TagQuery class which is exposed from the UmbracoHelper. For example: `@Umbraco.TagQuery.GetTagsForProperty` + +## Packages + +You should check with the package creator for all installed packages to ensure they are compatible with Umbraco 7. + +## For package developers + +We see common errors that we cannot fix for you, but we do have recommendations you can follow to fix them: + +### TypeFinder + +```none +Could not load type umbraco.BusinessLogic.Utils.TypeFinder from assembly businesslogic, Version=1.0.5031.21336, Culture=neutral, PublicKeyToken=null. +``` + +The TypeFinder has been deprecated since 4.10 and is now found under `Umbraco.Core.TypeFinder`. + +### JavaScript in menu actions + +While you need to have JavaScript inside menu actions to trigger a response, it is highly recommended that you use the recommended `UmbClientMgr` methods. You should not try to override `parent.right.document` and similar tricks to get to the right-hand frame. + +### Use the recommended Umbraco uicontrols + +If you have a webforms page, it is recommended to use the built-in ASP.NET controls to render panels, properties and so on. If you use the raw HTML or try to style it to match the backoffice, you will get out of sync. Follow the guidelines set by Umbraco's internal editors and use the ASP.NET custom controls for UI. diff --git a/16/umbraco-cms/implementation/composing.md b/16/umbraco-cms/implementation/composing.md new file mode 100644 index 00000000000..7466218ccdb --- /dev/null +++ b/16/umbraco-cms/implementation/composing.md @@ -0,0 +1,514 @@ +--- +description: "This article covers the topic of composing in Umbraco." +--- + +# Composing + +Customising the behaviour of an Umbraco Application at 'start up'. for example adding, removing, or replacing the core functionality of Umbraco or registering custom code to subscribe to notifications. + +## Overview + +An Umbraco application is a `Composition` made of many different 'collections' and single items of specific functionality/implementation logic/components (eg. UrlProviders, ContentFinders - see below for a full list). These collections are populated when the Umbraco Application starts up. + +'Composing' is the term used to describe the process of curating which pieces of functionality should be included in a particular collection. The code that implements these choices at start up is called a `Composer`. + +A `Component` is a generic wrapper for writing custom code during composition, it has two methods: `Initialize()` and `Terminate()` and these are executed when the Umbraco Application starts up, and when it shuts down, respectively. The functionality of a `Component` is identical to having a class handling both the `UmbracoApplicationStartingNotification` and `UmbracoApplicationStoppingNotification`. + +How are the collections populated? - Either by scanning the codebase for c# classes that inherit from a particular base class or implement a particular interface (typed scanned) or by being explicitly registered via a `Composer`. + +Umbraco setup the default set of components and collections that deliver the core 'out of the box' Umbraco behaviour. These default collections can be removed, reordered, replaced, etc. by implementing `IComposer`'s and `IComponent`s to customise and extend Umbraco's behaviour. + +### Example - Creating a Composer to listen for ContentSavingNotification + +This example shows how to create a component and a notification handler for the `ContentSavingNotification`, (perhaps to check for explicit words, or some custom business logic that needs to run before the content item is saved in Umbraco). + +We create a new C# class that implements `IComposer` and use it register our notification handler. + +```csharp +using System.Linq; +using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Core.DependencyInjection; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Extensions; + +namespace My.Website; + +public class SubscribeToContentServiceSavingComposer : IComposer +{ + public void Compose(IUmbracoBuilder builder) + { + builder.AddNotificationHandler(); + } +} + +public class CustomContentSavingNotificationHandler : INotificationHandler +{ + public void Handle(ContentSavingNotification notification) + { + foreach (var content in notification.SavedEntities + // Check if the content item type has a specific alias + .Where(c => c.ContentType.Alias.InvariantEquals("MyContentType"))) + { + // Do something if the content is using the MyContentType doctype + } + } +} +``` + +{% hint style="warning" %} +Ordering of composers is important, the last one added can override a previously added composer! Make sure, when overriding, that your composer that is doing the overriding, is 'composing', after the composer has 'composed' the element you wish to override! +{% endhint %} + +### Example - Explicitly Registering a new custom OEmbedProvider + +This example shows a custom 'Spotify' OEmbed Provider which will allow Spotify URLs to be used via the 'embed' button in the Rich Text Editors. As the collection for OEmbedProviders is not 'typed scanned', we need to explicitly register the provider in the collection of OEmbedProviders. We create a C# class which implements `IComposer` and append our new Spotify OEmbedProvider to the `EmbedProvidersCollection`: + +```csharp +using Umbraco.Cms.Core.Media.EmbedProviders; +using Umbraco.Cms.Core.Serialization; + +namespace My.Website; + +public class SpotifyEmbedProvider : OEmbedProviderBase +{ + public SpotifyEmbedProvider(IJsonSerializer jsonSerializer) + : base(jsonSerializer) + { + } + + public override string ApiEndpoint => "https://embed.spotify.com/oembed/"; + + // Playlist + // https://open.spotify.com/user/spotify/playlist/37i9dQZF1E4sNI4jZloSZr?si=cueBooBfTnqCGriSa4N_Kg + // spotify:user:spotify:playlist:37i9dQZF1E4sNI4jZloSZr + // Artist + // https://open.spotify.com/artist/0iirUbtgwt9jEkc2Grin8C?si=TLeUR2cHR-KPRJJhW6YiVg + // spotify:artist:0iirUbtgwt9jEkc2Grin8C + // Album + // https://open.spotify.com/album/0lvtdqkqIln6uDBBUT7DHL?si=XTVJIEmnS_OVv9l6ktPFiw + // spotify:album:0lvtdqkqIln6uDBBUT7DHL + // Track + // https://open.spotify.com/track/7aCk4XfXIEJM2MecU6Gmf2?si=vESDzI0xTNeA9FQ_dvf1eQ + // spotify:track:7aCk4XfXIEJM2MecU6Gmf2 + public override string[] UrlSchemeRegex => new[] + { + @".*.spotify.com/.*", + @"spotify:.*" + }; + + public override Dictionary RequestParams => new(); + + public override string? GetMarkup(string url, int maxWidth = 0, int maxHeight = 0) + { + string requestUrl = base.GetEmbedProviderUrl(url, maxWidth, maxHeight); + OEmbedResponse? oembed = base.GetJsonResponse(requestUrl); + + return oembed?.GetHtml(); + } +} +``` + +```csharp +using Umbraco.Cms.Core.Composing; + +namespace My.Website; + +public class RegisterEmbedProvidersComposer : IComposer +{ + public void Compose(IUmbracoBuilder builder) + { + // Change the EmbedProvidersCollection + // by adding our new EmbedProvider for Spotify + builder.EmbedProviders().Append(); + } +} +``` + +See a list of collections below to determine which are 'type scanned' and which require explicit registration. + +### ComponentComposer + +It's an implementation of `IComposer`, that provides a quicker way to add a custom component to the Component's collection. Creating a C# class that inherits from `ComponentComposer` will automatically add `YourComponentType` to the collection of Components. In the example above, the `SubscribeToContentServiceSavingComposer` for the `SubscribeToContentServiceSavingComponent` could have been written more conveniently as: + +```csharp +public class SubscribeToContentServiceSavingComposer : ComponentComposer +{ } +``` + +## Collections + +> "Collections of elements", for example, the ContentFinders collection. - Collections are another concept that Umbraco uses to make things simpler, on top of DI. A collection builder builds a collection, allowing users to add and remove types before anything is registered into DI. + +Below is a list of collections with their corresponding 'collection type' and how items for this collection 'out of the box' are registered. + +| Collection | Type | Registration | +| ----------------------- | -------- | -------------------------------------------------------------- | +| Actions | Lazy | Type scanned for `IAction` | +| BackOfficeAssets | Ordered | Explicit Registration - Empty by default | +| CacheRefreshers | Lazy | Type scanned for `ICacheRefresher` | +| Components | Ordered | Explicit Registration | +| ContentApps | Ordered | Package.manifest & Explicit Registration | +| ContentFinders | Ordered | Explicit Registration | +| ContentIndexHandlers | Lazy | Type scanned for `IContentIndexHandler` | +| Dashboards | Weighted | Package.manifest & Explicit Registration | +| DataEditors | Lazy | Type scanned for `IDataEditor` | +| DataValueReferenceFactories | Ordered | Explicit Registration - Empty by default | +| EditorValidators | Lazy | Type scanned for `IEditorValidator` | +| EmbedProviders | Ordered | Explicit Registration | +| FilterHandlers | Lazy | Type scanned for `IFilterHandler` | +| HealthChecks | Lazy | Type scanned for `HealthCheck` | +| HealthCheckNotificationMethods | Lazy | Type scanned for `IHealthCheckNotificationMethod` | +| ManifestFilters | Ordered | Explicit Registration - Empty by default | +| ManifestValueValidators | Set | Explicit Registration | +| MapDefinitions | Set | Explicit Registration | +| Mappers | Set | Explicit Registration | +| MediaUrlGenerators | Set | Explicit Registration | +| MediaUrlProviders | Ordered | Explicit Registration | +| NPocoMappers | Set | Explicit Registration | +| PackageMigrationPlans | Lazy | Type scanned for `PackageMigrationPlan` | +| PartialViewSnippets | Lazy | Explicit Registration. Reads .cshtml files from `Umbraco.Cms.Core.EmbeddedResources.Snippets` assembly | +| PropertyValueConverters | Ordered | Type scanned for `IPropertyValueConverter` | +| RuntimeModeValidators | Set | Explicit Registration | +| SearchableTrees | Lazy | Type scanned for `ISearchableTree` | +| Sections | Ordered | Package.manifest & Explicit Registration | +| SelectorHandlers | Lazy | Type scanned for `ISelectorHandler` | +| SortHandlers | Lazy | Type scanned for `ISortHandler` | +| TourFilters | Base | Empty collection | +| Trees | Base | Type scanned. Must inherit `TreeControllerBase` & use `[Tree]` | +| UrlProviders | Ordered | Explicit Registration | +| UrlSegmentProviders | Ordered | Explicit Registration | +| Validators | Lazy | Explicit Registration | + +### Types of Collections + +| | Method | Notes | +| -------- | ------------------------------- | ----------------------------------------------------------------------------------------------------------------------- | +| Set | `SetCollectionBuilderBase` | The base class for collection builders that do not order their items explicitly. | +| Ordered | `OrderedCollectionBuilderBase` | The base class for collection builders that order their items explicitly. | +| Weighted | `WeightedCollectionBuilder` | The base class for collection builders that order their items by the `[Weight]` attribute. | +| Lazy | `LazyCollectionBuilderBase` | The base class for collection builders that resolve the types at the last moment, only when the collection is required. | + +### Example - Modifying Collections + +This example shows how to control which Healthchecks are available to run in the Umbraco backoffice. Create a C# class which implements IComposer, the Compose method gives access to the HealthChecks collection of the Umbraco Composition - first we clear all HealthChecks from the collection, then add back in the ones we want to keep: + +```csharp +using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Core.DependencyInjection; +using Umbraco.Cms.Core.HealthChecks.Checks.Permissions; +using Umbraco.Cms.Core.HealthChecks.Checks.Security; + +namespace My.Website; + +public class MyComposer: IComposer +{ + public void Compose(IUmbracoBuilder builder) + { + // Remove all HealthChecks + builder.HealthChecks().Clear(); + + // Explicitly add back the ones we want to use + builder.HealthChecks().Add(); + builder.HealthChecks().Add(); + } +} +``` + +## Attributes + +Umbraco has some useful C# attributes to decorate your composer classes or Types used in collections, to give you further control on how and when your Composers will 'compose'. + +### ComposeBefore and ComposeAfter + +A finer-grain mechanism can then be used to refine the order of composition. Each composer can specify that it should compose before or after another composer, using the ComposeBefore and ComposeAfter attributes. For instance: + +```csharp +[ComposeBefore(typeof(ThatOtherComposer))] +public class ThisComposer : IComposer +{ + public void Compose(IUmbracoBuilder builder) + { + } +} +``` + +`ThisComposer` will 'compose' before `ThatOtherComposer`. + +{% hint style="warning" %} +If you create a circular dependency then Umbraco will fail to boot and will report the conflicting/circular dependency. +{% endhint %} + +### Weight + +This attribute is used only for `WeightedCollectionBuilders` (see list above). It specifies an integer ordinal value for each item to be added to the weighted collection which controls their sort order. The weighting attribute is not applied to the Composers. + +```csharp +using System; +using Umbraco.Core; +using Umbraco.Core.Composing; +using Umbraco.Core.Dashboards; + +namespace Umbraco.Web.Dashboards; + +[Weight(10)] +public class FormsDashboard : IDashboard +{ + public string Alias => "formsInstall"; + public string[] Sections => new [] { Constants.Applications.Forms }; + public string View => "views/dashboard/forms/formsdashboardintro.html"; + public IAccessRule[] AccessRules => Array.Empty(); +} +``` + +### HideFromTypeFinder + +This is used to hide a type from being auto-scanned/added to a collection as in some cases certain items/types may need to be added to a collection manually. For example, a Search package may make it optional whether to replace the 'backoffice search' with an ISearchableTree implementation. Type scanning would make this change automatically at start up if the custom implementation was detected via type scanning. This attribute could hide the class from the scanner. + +### DisableComposer & Disable + +These attributes allow you to disable a particular implementation of a composer or class - Let's say Umbraco ships with two different ways of doing "something" (for instance, two front-end caches). Each way has its own composer, which registers all the relevant elements. Keep in mind that if both composers are detected, there will be some sort of collision. Ideally, we want to disable one of them. That can be achieved with the Disable attribute: + +```csharp +[Disable] +public class Way2Composer : IComposer +{ + //... +} +``` + +When used without arguments, these attributes apply to the composer they are marking. But, and this is where it becomes interesting, they can be used with an argument to act on another component. Therefore, should a user want to replace our "something" with theirs, they would write the following code: + +```csharp +[Disable(typeof(Way1Composer))] +public class MyComposer : IComposer +{ + public void Compose(IUmbracoBuilder builder) + { + // ... + } +} +``` + +But maybe they want to swap our two "something" implementations? In this case, assembly-level attributes can be used: + +```csharp +[assembly:DisableComposer(typeof(Way1Composer))] +[assembly:EnableComposer(typeof(Way2Composer))] +``` + +{% hint style="info" %} +Umbraco also has `[Enable]` & `[EnableComposer]` attributes but all composers are enabled by default. +{% endhint %} + +## Runtime Levels + +The `Umbraco.Cms.Core.RuntimeLevel` enum contains the following values: + +`BootFailed` + +The runtime has failed to boot and cannot run. + +`Unknown` + +The level is unknown. + +`Boot` + +The runtime is booting. + +`Install` + +The runtime has detected that Umbraco is not installed at all, ie. there is no database, and is currently installing Umbraco. + +`Upgrade` + +The runtime has detected an Umbraco install which needed to be upgraded, and is currently upgrading Umbraco. + +`Run` + +The runtime has detected an up-to-date Umbraco install and is running. + +| Level | Description | +| ------------ | ----------------------------------------------------------------------------------------------------------------------------- | +| `BootFailed` | The runtime has failed to boot and cannot run. | +| `Unknown` | The level is unknown. | +| `Boot` | The runtime is booting. | +| `Install` | The runtime has detected that Umbraco is not installed at all, ie. there is no database, and is currently installing Umbraco. | +| `Upgrade` | The runtime has detected an Umbraco install that needed to be upgraded and is currently upgrading Umbraco. | +| `Run` | The runtime has detected an up-to-date Umbraco install and is running. | + +## Example of using Ordered Collections and adding types explicitly + +You may wish to create an Umbraco package that allows package consumers to extend and add additional functionality. In this example, we show how you can use the `OrderedCollectionBuilderBase`. + +```csharp +using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Api.Management.Controllers; +using Umbraco.Cms.Api.Management.Routing; +using Umbraco.Cms.Core.Composing; + +namespace UmbracoDocs.Samples; + +public interface IDoThing +{ + string DoTheThing(string message); +} + +public class FirstThing : IDoThing +{ + public string DoTheThing(string message) + => $"First: {message}"; +} + +public class SecondThing : IDoThing +{ + public string DoTheThing(string message) + => $"Second: {message}"; +} + +public class ThirdThing : IDoThing +{ + public string DoTheThing(string message) + => $"Third: {message}"; +} + +// OrderedCollection - use when order of items is important (You may want to execute them in order) +public class DoThingsCollectionBuilder : OrderedCollectionBuilderBase +{ + protected override DoThingsCollectionBuilder This => this; +} + +public class DoThingsCollection : BuilderCollectionBase +{ + public DoThingsCollection(Func> items) + : base(items) + { + } +} + +public class DoThingsComposer : IComposer +{ + public void Compose(IUmbracoBuilder builder) + { + // Explicitly add to the collection a Type in a specific order + builder + .WithCollectionBuilder() + .Append() + .Append() + .Append(); + } +} + +// An Umbraco Management API Controller - used in a Dashboard or Property Editor, perhaps? +[ApiExplorerSettings(GroupName = "Do things")] +[VersionedApiBackOfficeRoute("do/things")] +public class DoThingsController : ManagementApiControllerBase +{ + private readonly DoThingsCollection _doThings; + + public DoThingsController(DoThingsCollection doThings) + => _doThings = doThings; + + [HttpGet] + [ProducesResponseType(StatusCodes.Status200OK)] + public IActionResult DoAllThings(string message) + { + var allThingsDone = _doThings + .Select(doThing => doThing.DoTheThing(message)) + .ToArray(); + + return Ok(allThingsDone); + } +} +``` + +## Example of using Lazy Collections with Type Scanning + +You may wish to create an Umbraco package that allows package consumers to extend and add additional functionality. In this example, we show how you can use the `LazyCollectionBuilderBase` to scan assemblies that implement your interface by using the `TypeLoader` + +{% hint style="warning" %} +Don't use type scanning if you can avoid it. Type scanning increases the Umbraco boot time. + +If your use case requires type scanning, ensure your interface implements `IDiscoverable`. This marker interface ensures that types are scanned once and then cached by Umbraco. This way, we save time by not having to re-scan for types repeatedly. +{% endhint %} + +```csharp +using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Api.Management.Controllers; +using Umbraco.Cms.Api.Management.Routing; +using Umbraco.Cms.Core.Composing; + +namespace UmbracoDocs.Samples; + +// Implement `IDiscoverable` to boost scanning performance +public interface IDoThing : IDiscoverable +{ + string DoTheThing(string message); +} + +public class FirstThing : IDoThing +{ + public string DoTheThing(string message) + => $"First: {message}"; +} + +public class SecondThing : IDoThing +{ + public string DoTheThing(string message) + => $"Second: {message}"; +} + +public class ThirdThing : IDoThing +{ + public string DoTheThing(string message) + => $"Third: {message}"; +} + +public class DoThingsCollectionBuilder : LazyCollectionBuilderBase +{ + protected override DoThingsCollectionBuilder This => this; +} + +public class DoThingsCollection : BuilderCollectionBase +{ + public DoThingsCollection(Func> items) + : base(items) + { + } +} + +public class DoThingsComposer : IComposer +{ + public void Compose(IUmbracoBuilder builder) + { + // Add types from assemblies. Be conscious of using type scanning, as this adds to the Umbraco boot time. + // If you need to use type scanning, ensure that your interface implements `IDiscoverable`. + builder + .WithCollectionBuilder() + .Add(() => builder.TypeLoader.GetTypes()); + } +} + +// An Umbraco Management API Controller - used in a Dashboard or Property Editor, perhaps? +[ApiExplorerSettings(GroupName = "Do things")] +[VersionedApiBackOfficeRoute("do/things")] +public class DoThingsController : ManagementApiControllerBase +{ + private readonly DoThingsCollection _doThings; + + public DoThingsController(DoThingsCollection doThings) + => _doThings = doThings; + + [HttpGet] + [ProducesResponseType(StatusCodes.Status200OK)] + public IActionResult DoAllThings(string message) + { + var allThingsDone = _doThings + .Select(doThing => doThing.DoTheThing(message)) + .ToArray(); + + return Ok(allThingsDone); + } +} +``` diff --git a/16/umbraco-cms/implementation/controllers.md b/16/umbraco-cms/implementation/controllers.md new file mode 100644 index 00000000000..ee1dd990a89 --- /dev/null +++ b/16/umbraco-cms/implementation/controllers.md @@ -0,0 +1,46 @@ +--- +description: >- + An Umbraco API Controller is an ASP.NET WebApi controller that is used for + creating REST services. +--- + +# Controllers + +Umbraco contains different types of controllers to perform different tasks: + +* [Render MVC Controllers](controllers.md#render-mvc-controllers) +* [Surface Controllers](controllers.md#surface-controllers) +* [Public API Controllers](controllers.md#umbraco-api-controllers) +* [Backoffice API Controllers](controllers.md#backoffice-api-controllers) + +## Render MVC Controllers + +When you make a page request to the MVC application, a controller is responsible for returning the response to that request. The controller can perform one or more actions. + +By default, all front-end requests to an Umbraco site are auto-routed via the _Index_ action of a core Controller: `Umbraco.Cms.Web.Common.Controllers.RenderController`. + +For details on using Render MVC Controllers, see the [Controller & Action Selection](default-routing/controller-selection.md) article. + +## Surface Controllers + +A SurfaceController is an MVC controller that interacts with the front-end rendering of an UmbracoPage. They can be used for rendering view components and for handling Form data submissions. SurfaceControllers are auto-routed which means you don't have to add/create your own routes for these controllers to work. + +All implementations of Surface Controllers inherit from the base class: `Umbraco.Cms.Web.Website.Controllers.SurfaceController`. + +For details on using Surface Controllers, see the [Surface Controllers](../reference/routing/surface-controllers/) article. + +## Public API Controllers + +A public API Controller is an ASP.NET Core API controller that is used for creating publicly available REST services. For details on implementing public API Controllers, see the [Umbraco API Controllers](../reference/routing/umbraco-api-controllers/) article. + +## Backoffice API Controllers + +Read the [Creating a Backoffice API article](../tutorials/creating-a-backoffice-api/) for a comprehensive guide to writing APIs for the Management API. + +{% hint style="info" %} +The Umbraco Backoffice API is also known as the Management API. Thus, a Backoffice API Controller is often referred to as a Management API Controller. +{% endhint %} + +*** + +{% include "../.gitbook/includes/umbraco-mvc-training-course.md" %} diff --git a/16/umbraco-cms/implementation/custom-routing/README.md b/16/umbraco-cms/implementation/custom-routing/README.md new file mode 100644 index 00000000000..49c3c0776a0 --- /dev/null +++ b/16/umbraco-cms/implementation/custom-routing/README.md @@ -0,0 +1,126 @@ +--- +description: Learn everything you need to know about custom routing in Umbraco CMS. +--- + +# Custom Routing + +_There are a couple of ways of controlling the routing behavior in Umbraco: customizing how the inbound request pipeline finds content & creating custom MVC routes that integrate within the Umbraco pipeline_. + +## Customizing the inbound pipeline + +Below lists the ways in which you can customize the inbound request pipeline, this is done by using native Umbraco plugin classes, notifications, or defining your own routes. + +### IContentFinder + +All Umbraco content is looked up based on the URL in the current request using an `IContentFinder`. IContentFinder's you can create and implement on your own which will allow you to map any URL to a Umbraco content item. + +See: [IContentFinder documentation](../../reference/routing/request-pipeline/icontentfinder.md) + +### Last Chance IContentFinder + +A `IContentLastChanceFinder` is a special implementation of an `IContentFinder` for use with handling 404's. You can implement one of these plugins to decide which Umbraco content page you would like to show when the URL hasn't matched a Umbraco content node. + +{% hint style="info" %} +When creating packages or using class libraries, the `SetContentLastChanceFinder` is a part of the `Umbraco.Cms.Web.Website` NuGet package. +{% endhint %} + +To set your own 404 finder create a `IContentLastChanceFinder` and set it as the `ContentLastChanceFinder`. A `ContentLastChanceFinder` will always return a 404 status code. Example: + +```csharp +using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Core.DependencyInjection; +using Umbraco.Extensions; + +namespace My.Website; + +public class UpdateContentFindersComposer : IComposer +{ + public void Compose(IUmbracoBuilder builder) + { + //set the last chance content finder + builder.SetContentLastChanceFinder(); + } +} +``` + +For more detailed information see: [IContentFinder documentation](../../reference/routing/request-pipeline/icontentfinder.md) + +## Custom MVC routes + +You can specify your own custom MVC routes to work within the Umbraco pipeline. It requires your controller to inherit from `UmbracoPageController` and either implement `IVirtualPageController` or use `.ForUmbracoPage` when registering your route, for more information and a complete example of both approaches see [Custom routing documentation](https://docs.umbraco.com/umbraco-cms/reference/routing/custom-routes#custom-routes-within-the-umbraco-pipeline) + +An example of registering a `UmbracoPageController` using `.ForUmbracoPage`: + +```csharp +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.Extensions.DependencyInjection; +using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Core.DependencyInjection; +using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.Web; +using Umbraco.Cms.Web.Common.ApplicationBuilder; +using Umbraco.Extensions; + +namespace CustomRoutes; + +public class MyCustomRouteComposer : IComposer +{ + public void Compose(IUmbracoBuilder builder) + { + builder.Services.Configure(options => + { + options.AddFilter(new UmbracoPipelineFilter(nameof(MyController)) + { + Endpoints = app => app.UseEndpoints(endpoints => + { + endpoints.MapControllerRoute( + "My custom controller", + "/custom/{action}", + new {Controller = "My", Action = "Index"}) + .ForUmbracoPage(FindContent); + }) + }); + }); + } + + private IPublishedContent FindContent(ActionExecutingContext actionExecutingContext) + { + var umbracoContextAccessor = actionExecutingContext.HttpContext.RequestServices + .GetRequiredService(); + var umbracoContext = umbracoContextAccessor.GetRequiredUmbracoContext(); + + return umbracoContext.Content.GetById(1064); + } +} +``` + +{% hint style="info" %} +This is an approach for mapping a custom route to a custom MVC controller. For creating routes for existing content pages you can use a custom MVC controller to handle the request by naming convention: see [Custom Controllers - Route Hijacking](../../reference/routing/custom-controllers.md). +{% endhint %} + +### RoutingRequestNotification + +You can subscribe to the `RoutingRequestNotification` which is published right after the point when the `PublishedRequestBuilder` is prepared - (but before it is ready to be processed). Here you can modify anything in the request before it is processed, eg. content, template, etc: + +```csharp +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Notifications; + +namespace CustomRoutes; + +public class PublishedRequestHandler : INotificationHandler +{ + public void Handle(RoutingRequestNotification notification) + { + var requestBuilder = notification.RequestBuilder; + // Do something with the IPublishedRequestBuilder here + } +} +``` + +For more information on how to register and use notification handlers see [Notifications documentation](../../reference/notifications/) + +### Related articles + +* [Find out how to add your own hub(s) with SignalR to the existing setup](signalR.md)) diff --git a/16/umbraco-cms/implementation/custom-routing/signalR.md b/16/umbraco-cms/implementation/custom-routing/signalR.md new file mode 100644 index 00000000000..af7788eeb8b --- /dev/null +++ b/16/umbraco-cms/implementation/custom-routing/signalR.md @@ -0,0 +1,175 @@ +--- +description: "Umbraco ships with signalR installed, find out how to add your own hub(s) to the existing setup" +--- + +# Adding a hub with SignalR and Umbraco + +Umbraco ships with signalR installed. This article shows how to add your own hub(s) to the existing setup. + +## Create a hub and its interface + +We are going to go for the most basic implementation possible, a status ping. So first create a new interface with the following code: + +```csharp +public interface ITestHubEvents +{ + // Define the events the clients can listen to + public Task Pong(); +} +``` + +And then the actual hub: + +```csharp +using Microsoft.AspNetCore.SignalR; + +public class TestHub : Hub +{ + // when a client sends us a ping + public async Task Ping() + { + // we trigger the pong event on all clients + await Clients.All.Pong(); + } +} +``` + +## Define a custom route + +Next up, is defining a custom route. Use `IAreaRoutes` and the base Umbraco backend path so there's no need to reserve another path in the settings. + +```csharp +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; +using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Core.DependencyInjection; +using Umbraco.Cms.Web.Common.ApplicationBuilder; +using Umbraco.Extensions; + +public class TestHubRoutes : IAreaRoutes +{ + private readonly IRuntimeState _runtimeState; + private readonly string _umbracoPathSegment; + + public TestHubRoutes( + IOptions globalSettings, + IHostingEnvironment hostingEnvironment, + IRuntimeState runtimeState) + { + _runtimeState = runtimeState; + _umbracoPathSegment = globalSettings.Value.GetUmbracoMvcArea(hostingEnvironment); + } + + public void CreateRoutes(IEndpointRouteBuilder endpoints) + { + switch (_runtimeState.Level) + { + case Umbraco.Cms.Core.RuntimeLevel.Run: + endpoints.MapHub(GetTestHubRoute()); + break; + } + + } + + public string GetTestHubRoute() + { + return $"/{_umbracoPathSegment}/{nameof(TestHub)}"; + } +} +``` + +### Add the routing to the Umbraco Composer + +Last step in the setup is registering our custom route: + +```csharp +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; +using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Core.DependencyInjection; +using Umbraco.Cms.Web.Common.ApplicationBuilder; +using Umbraco.Extensions; + +public class TestHubComposer : IComposer +{ + public void Compose(IUmbracoBuilder builder) + { + // first we are going to add signalR to the serviceCollection if no hubs have been added yet + // this is just in case Umbraco ever decides to use a different technology + if (!builder.Services.Any(x => x.ServiceType == typeof(IHubContext<>))) + { + builder.Services.AddSignalR(); + } + + // next is adding the routes we defined earlier + builder.Services.AddUnique(); + builder.Services.Configure(options => + { + options.AddFilter(new UmbracoPipelineFilter( + "test", + endpoints: applicationBuilder => + { + applicationBuilder.UseEndpoints(e => + { + var hubRoutes = applicationBuilder.ApplicationServices.GetRequiredService(); + hubRoutes.CreateRoutes(e); + }); + } + )); + }); + } +} +``` + +### Add the route in appsettings.json file + +When setting up SignalR routes, add the route to `ReservedPaths` in the `appsettings.json` file like: + +```cs +"Umbraco": { + "CMS": { + "Global": { + "ReservedPaths": "~/app_plugins/,~/install/,~/mini-profiler-resources/,~/umbraco/,~/umbraco/testhub/," + } + } +} +``` + +{% hint style="info" %} +You need to provide the default reserved paths, else you'll run into an issue as mentioned on [GitHub](https://github.com/umbraco/Umbraco-CMS/issues/12965). +{% endhint %} + +### Test the setup + +And lastly we can test the setup with some JavaScript in our view: + +```html + + + +``` + +When you insert this in a view, you should see a `signalR connection established` console message followed by `Pong`. diff --git a/16/umbraco-cms/implementation/data-persistence.md b/16/umbraco-cms/implementation/data-persistence.md new file mode 100644 index 00000000000..74a58e33456 --- /dev/null +++ b/16/umbraco-cms/implementation/data-persistence.md @@ -0,0 +1,33 @@ +# Data Persistence (CRUD) + +_The Umbraco Services layer is used to query and manipulate Umbraco stored in the database_. + +## Dependency injection + +All services are available using their interfaces in the dependency injection container. ASP.NET Core supports dependency injection in almost every scenario. + +### Constructors in classes + +```csharp + public class MyClass + { + private readonly IContentService _contentService; + + public MyClass(IContentService contentService) + { + _contentService = contentService; + } + } +``` + +### Constructors in views + +```cshtml + @inject IContentService ContentService +``` + +## Services + +There is a service for each type of data in Umbraco. + +[See here For a full list of services available (external)](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Services.html) diff --git a/16/umbraco-cms/implementation/default-routing/README.md b/16/umbraco-cms/implementation/default-routing/README.md new file mode 100644 index 00000000000..2d7436a77f0 --- /dev/null +++ b/16/umbraco-cms/implementation/default-routing/README.md @@ -0,0 +1,29 @@ +# Routing + +Get an overview of how the Umbraco pipeline is structured. See what happens from user request to content delivery. + +## Request Pipeline + +### [Inbound request pipeline](inbound-pipeline.md) + +Matching a URL to a content item & determining the rendering engine (MVC or Webforms). + +### [Controller selection](controller-selection.md) + +Match an MVC Controller and Action to handle the request. + +## [Execute request](execute-request.md) + +The MVC Action and View are executed. During this execution you can query for published data to be displayed/rendered. + +### [IPublishedContent](../../reference/querying/ipublishedcontent) + +IPublishedContent is a strongly typed underlying model used in all Umbraco views. + +### [UmbracoHelper](../../reference/querying/umbracohelper.md) + +Use UmbracoHelper to query published media and content. + +### [Members](../../reference/querying/imembermanager.md) + +This section covers the IMemberManager. diff --git a/16/umbraco-cms/implementation/default-routing/controller-selection.md b/16/umbraco-cms/implementation/default-routing/controller-selection.md new file mode 100644 index 00000000000..15d079e9c12 --- /dev/null +++ b/16/umbraco-cms/implementation/default-routing/controller-selection.md @@ -0,0 +1,82 @@ +# Controller & Action Selection + +When you make a page request to the MVC application, a controller is responsible for returning the response to that request. The controller can perform one or more actions. The controller action can return different types of action results based on the request. + +## Default Controller Action + +By default, Umbraco will execute every request via it's built-in default controller: `Umbraco.Cms.Web.Common.Controllers.RenderController`. Umbraco site automatically routes all the front-end requests via the `Index` action of the `RenderController`. + +```csharp +using Umbraco.Cms.Web.Common.Controllers; +using Microsoft.Extensions.Logging; +using Microsoft.AspNetCore.Mvc.ViewEngines; +using Umbraco.Cms.Core.Web; +using Microsoft.AspNetCore.Mvc; + +namespace UmbracoProject.Controller; + +public class HomePageController : RenderController +{ + + public HomePageController(ILogger logger, ICompositeViewEngine compositeViewEngine, IUmbracoContextAccessor umbracoContextAccessor) + : base(logger, compositeViewEngine, umbracoContextAccessor) + { + } + public override IActionResult Index() + { + return CurrentTemplate(CurrentPage); + } + + public IActionResult HomePage() + { + return CurrentTemplate(CurrentPage); + } +} +``` + +## Change the Default Controllers + +It is possible to implement a custom Controller to replace the default implementation to give complete control during the Umbraco request pipeline execution. You can configure Umbraco to use your implementation in a class. For example: + +{% code title="MyRenderController.cs" lineNumbers="true" %} + +```csharp +using Microsoft.AspNetCore.Mvc.ViewEngines; +using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Core.Web; +using Umbraco.Cms.Web.Common.Controllers; +using Umbraco.Cms.Web.Website.Controllers; + +namespace YourProjectNamespace; + +public class MyRenderController : RenderController +{ + public MyRenderController( + ILogger logger, + ICompositeViewEngine compositeViewEngine, + IUmbracoContextAccessor umbracoContextAccessor) + : base(logger, compositeViewEngine, umbracoContextAccessor) + { + } + + public override IActionResult Index() => Ok("MyRenderController Index method hit with CurrentPage.Name set to: " + CurrentPage?.Name); +} + +public class MyComposer : IComposer +{ + public void Compose(IUmbracoBuilder builder) + { + builder.Services.Configure(renderOptions + => renderOptions.DefaultControllerType = typeof(MyRenderController)); + } +} +``` + +{% endcode %} + +Ensure that the controller inherits from the base controller `Umbraco.Cms.Web.Common.Controllers.RenderController`. You can override the `Index` method to perform any customizations of your choice. + +## Custom Controller Selection + +You can create Custom controllers for different Document Types and Templates. This is termed 'Hijacking Umbraco Routes'. For details on how this process works, see the [Custom MVC Controllers (Umbraco Route Hijacking)](../../reference/routing/custom-controllers.md) article. diff --git a/16/umbraco-cms/implementation/default-routing/execute-request.md b/16/umbraco-cms/implementation/default-routing/execute-request.md new file mode 100644 index 00000000000..7effbc0d0a7 --- /dev/null +++ b/16/umbraco-cms/implementation/default-routing/execute-request.md @@ -0,0 +1,29 @@ +# Executing an Umbraco request + +_During the Umbraco request execution, an MVC Action is called which executes a Razor view to render content to the end-user_, + +## Using the Model + +Whenever a content item is rendered on the front-end, it is based on a model of type `IPublishedContent`. This model contains all of the information about the content item associated with the current request. + +If you are working in a custom MVC Controller's action, a model of type `ContentModel` will be provided in the Action's method parameters. This model contains an instance of `IPublishedContent` which you can use. + +When you are working in a View of type `UmbracoViewPage` (which is the default view type), the Model provided to that view will be `IPublishedContent`. For example, to render the current content model's name you could do: + +```csharp +@Model.Name +``` + +All Umbraco view page types inherit from `UmbracoViewPage`. A neat trick is that if you want your view Model to be `IPublishedContent` you can change your view type to `UmbracoViewPage` and the view will still render without issue even though the controller is passing it a model of type ContentModel. + +## [IPublishedContent](../../reference/querying/ipublishedcontent/) + +IPublishedContent is a strongly typed model used for all published content, media, and members. It is used to render content in your views for your website. + +## [UmbracoHelper](../../reference/querying/umbracohelper.md) + +UmbracoHelper is the unified way to work with published content/media on your website. Whether you are using MVC or WebForms you will be able to use UmbracoHelper to query/traverse Umbraco published data. + +## [IMemberManager](../../reference/querying/imembermanager.md) + +IMemberManager is an user manager interface for accessing member data in the form of IPublishedContent. IMemberManager has a variety of methods that are useful in views, controllers, and webforms classes. diff --git a/16/umbraco-cms/implementation/default-routing/inbound-pipeline.md b/16/umbraco-cms/implementation/default-routing/inbound-pipeline.md new file mode 100644 index 00000000000..68a8e6d6b25 --- /dev/null +++ b/16/umbraco-cms/implementation/default-routing/inbound-pipeline.md @@ -0,0 +1,32 @@ +# Umbraco's request pipeline + +Umbraco's request pipeline is the process of building up a URL, resolving the requests, and returning correct content. + +## Published Request Preparation + +The inbound process is triggered by `UmbracoRouteValueTransformer` and then handled with the Published router. The [Published Content Request Preparation](../../reference/routing/request-pipeline/published-content-request-preparation.md) process kicks in and creates a `PublishedRequestBuilder` which will be used to create a `PublishedContentRequest`. + +What it does: + +- It ensures Umbraco is ready, and the request is a document request. +- Ensures there's content in the published cache, if there isn't it routes to the `RenderNoContentController` which displays the no content page you see when running a fresh install. +- Creates a published request builder. +- Routes the request with the request builder using the `PublishedRouter.RouteRequestAsync(…)`. + - This will handle redirects, find domain, template, published content and so on. + - Build the final `IPublishedRequest`. +- Sets the routed request in the Umbraco context, so it will be available to the controller. +- Create the route values with the `UmbracoRouteValuesFactory`. + - This is what routes your request to the correct controller and action, and allows you to hijack routes. +- Set the route values to the http context. +- Handles posted form data. +- Returns the route values to netcore so it routes your request correctly. + +## Published Content Request Instance + +When finding published content the [PublishedRouter](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Routing.PublishedRouter.html) will first check if the [PublishedRequestBuilder](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Routing.PublishedRequestBuilder.html) already has content, if it doesn't the content finders will kick in. For more information, see the [Find published content](../../reference/routing/request-pipeline/published-content-request-preparation.md#find-published-content) section in the [Published Content Request Preparation](../../reference/routing/request-pipeline/published-content-request-preparation.md) article. + +This information is also used during the [Controller & Action selection](controller-selection.md) process. + +### Related Information + +- [Routing in Umbraco](../../reference/routing/request-pipeline/) diff --git a/16/umbraco-cms/implementation/integration-testing.md b/16/umbraco-cms/implementation/integration-testing.md new file mode 100644 index 00000000000..4cdba9c05d9 --- /dev/null +++ b/16/umbraco-cms/implementation/integration-testing.md @@ -0,0 +1,231 @@ +--- +description: A guide to getting started with integration testing in Umbraco +--- + +# Integration Testing + +These examples are for Umbraco 14. They use [NUnit](https://nunit.org/) as the testing framework. Leveraging [Umbraco.Cms.Tests.Integration](https://github.com/umbraco/Umbraco-CMS/tree/contrib/tests/Umbraco.Tests.Integration) providing base classes. + +{% hint style="info" %} +The Umbraco.Tests.Integration project uses version `3.14.0` of the NUnit NuGet package. It is essential to use this version to ensure compatibility. You can check the current package versions used by the Umbraco.Tests.Integration project [here](https://github.com/umbraco/Umbraco-CMS/blob/v14/dev/tests/Directory.Packages.props). +{% endhint %} + +## Getting started + +First you have to create a new UnitTest project based on NUnit and install the package into the project. + +```csharp +//Create project +dotnet new nunit +//Install Umbraco.Tests.Integration package +dotnet add package Umbraco.Cms.Tests.Integration +``` + +After the project is created and the package is added we have to create a JSON file, named `appsettings.Tests.Local.json` and a `GlobalSetup` class. + +The package already created an `appsettings.Tests.json` file. For both files make sure to go to "properties" and set "Copy to output directory" to "always" or "copy if newer". + +The GlobalSetup is necessary to call the `GlobalSetupTeardown` class present in the package. This class makes sure that configuration is read and everything is setup as needed. Here is a sample that can be used: + +```csharp +[SetUpFixture] +public class CustomGlobalSetupTeardown +{ + private static GlobalSetupTeardown _setupTearDown; + + [OneTimeSetUp] + public void SetUp() + { + _setupTearDown = new GlobalSetupTeardown(); + _setupTearDown.SetUp(); + } + + [OneTimeTearDown] + public void TearDown() + { + _setupTearDown.TearDown(); + } +} +``` + +{% hint style="info" %} +The class should not have a namespace. +{% endhint %} + +## Creating a test + +To create a test you have to create a new class in your project. This class has to be derived from `UmbracoIntegrationTest`. This gives you access to some helper methods that you can use. + +Second is the `[UmbracoTest]`- attribute that has to be set on the class. This attribute is responsible to set which type of database setup you want to use in your test class. + +The available options are: + +* None +* NewEmptyPerFixture +* NewEmptyPerTest +* NewSchemaPerFixture +* NewSchemaPerTest + +Basic sample: + +```csharp +using Umbraco.Cms.Tests.Integration; + +[TestFixture] +[UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)] +public class IntegrationTests : UmbracoIntegrationTest +{ + +} +``` + +## Testing a notification + +Start by making a NotificationHandler, this example will be of one canceling overwrites on content named "Root", so if you have some content named "Root" published, you cannot change it. + +```csharp +public class MyNotificationHandler : INotificationHandler +{ + public void Handle(ContentSavingNotification notification) + { + foreach (var content in notification.SavedEntities) + { + if (content.PublishName == "Root") + { + notification.CancelOperation(new EventMessage("Cancelled", "Please do not change root content", + EventMessageType.Error)); + } + } + } +} +``` + +Then we can make an integration test, we do have to register our notification in the test like you would do with a composer, we can do this by overriding the `CustomTestSetupMethod` and adding the notification. After this, we can build our ContentType and Content with their respective builders. When we are saving both the ContentType & Content, we need the services to do so, so we use the `GetRequiredService` method that can get the services we need. We can then use `Assert.Multiple()` to do multiple asserts. + +```csharp +[TestFixture] +[UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)] +public class UmbracoTests : UmbracoIntegrationTest +{ + protected override void CustomTestSetup(IUmbracoBuilder builder) + { + builder.AddNotificationHandler(); + } + + [Test] + [TestCase("Root", true, OperationResultType.FailedCancelledByEvent)] + [TestCase("Home Page", false, OperationResultType.Success)] + public void Notification_Cancels_ContentType_If_AllowAsRoot(string name, bool hasErrors, OperationResultType expectedResult) + { + // Make ContentType and save + var contentType = new ContentTypeBuilder() + .WithId(0) + .WithContentVariation(ContentVariation.Nothing) + .Build(); + var contentTypeService = GetRequiredService(); + contentTypeService.Save(contentType); + + // Make some Content and publish it + var content = new ContentBuilder() + .WithContentType(contentType) + .WithName(name) + .Build(); + + var contentService = GetRequiredService(); + contentService.Save(content); + contentService.Publish(content, ["*"]); + + // Try to save the content + var saveResult = contentService.Save(content); + + // Assert + var errors = saveResult.EventMessages + .GetAll() + .Where(x => x.MessageType == EventMessageType.Error); + + Assert.Multiple(() => + { + Assert.AreEqual(hasErrors, errors.Any()); + Assert.AreEqual(expectedResult, saveResult.Result); + }); + } +} +``` + +## Testing with a schema + +So one of the awesome things about integration tests, is that you can set up a site, download the package for it, and we can run this state for every test. This means that you do not have to go through and set up your tests with data like we do in the above example with the builder pattern. + +To start with we decorate our class with the `[UmbracoTest]` attribute with your preferred database setup and we again derive from `UmbracoIntegrationTest`. Then what you wanna do is set up your Umbraco site, go to the packages section and create your own package. Download the package and place the XML file next to your testing class. You want to have the build action of that XML file to be `EmbeddedResource` and you can set that again in the file's "properties". + +Now we're almost ready to start testing! The last thing we wanna do is have a SetUp method to install the package on your site. + +```csharp +[SetUp] +public void MySetup() +{ + var xml = PackageMigrationResource.GetEmbeddedPackageDataManifest(this.GetType()); + var packagingService = GetRequiredService(); + packagingService.InstallCompiledPackageData(xml); +} +``` + +Now you're all set to start testing with your own site! Let's try and see how that would look! Here's an example test, where we test that content is deleted, if you delete the Document Types, as you can see, this time we do not have to use builder patterns to set up our site! + +```csharp +[Test] +public void Ensure_No_Content_After_Doctype_Is_Deleted() +{ + var contentTypeService = GetRequiredService(); + var contentTypes = contentTypeService.GetAll(); + Assert.AreEqual(true, contentTypes.Any()); + + foreach (var contentType in contentTypes) + { + if (contentType.ParentId == Constants.System.Root) + { + contentTypeService.Delete(contentType); + } + } + var contentService = GetRequiredService(); + var contents = contentService.GetRootContent(); + + Assert.AreEqual(0, contents.Count()); + Assert.AreEqual(0, contentTypeService.GetAll().Count()); +} +``` + +## Testing from controller to database + +Sometimes we want to test from a controller action and down to the database. In this case, we use the built-in concept of a test server. All you need to do is to use the base class `UmbracoTestServerTestBase`. Let’s take an example: + +```csharp +[TestFixture] +public class AllCultureControllerTests : UmbracoTestServerTestBase +{ + [Test] + public async Task EnsureUnauthorizedStatusCode() + { + // Arrange + var url = GetManagementApiUrl(x => x.GetAll(CancellationToken.None, 0, 100)); + + // Act + var response = await Client.GetAsync(url); + + // Assert + Assert.AreEqual(HttpStatusCode.Unauthorized, response.StatusCode); + } +} +``` + +In this example you have to note three things: + +* You still need the `CustomGlobalSetupTeardown` class. +* Use the `GetManagementApiUrl` to get the URL of an Action and ensure all services use this URL information when requested. +* The `Client` is a standard `HttpClient`, but the base URL points to the test server that is set up for each test. + +{% hint style="info" %} +You can still use `GetRequiredService` to get the services required to seed data. +{% endhint %} + +Keep in mind that integration tests require a lot of setup before the test executes. So execution time will be many times longer compared to a unit test. diff --git a/16/umbraco-cms/implementation/learn-how-umbraco-works.md b/16/umbraco-cms/implementation/learn-how-umbraco-works.md new file mode 100644 index 00000000000..072d3aef0a2 --- /dev/null +++ b/16/umbraco-cms/implementation/learn-how-umbraco-works.md @@ -0,0 +1,21 @@ +--- +description: Get to know the Umbraco codebase. +--- + +# Learn how Umbraco works + +Developing an application requires knowledge about the tool you are working with. This section will give you an introduction to the underlying structure of Umbraco CMS. + +
RoutingThe process from front-end user requests to content delivery.default-routingrouting-image.png
Custom routingLearn how to work with custom URLs and custom MVC routes.custom-routingc-routing-image.png
ControllersEverything you need to know about the different types of controllers.controllers.mdcontrollers-image.png
Data persistenceLearn how to create, read, update, and delete data in the Umbraco database.data-persistence.mddatap-image.png
ComposingCustomize the behavior of an Umbraco application at 'start up'.composing.mdcomposing-image.png
Services and HelpersLearn how to use the core Services and Helpers when extending Umbraco.servicesservices-image.png
+ +## Test your application + +This section also includes documentation on different ways to run tests on your code and implementations. + +{% content-ref url="integration-testing.md" %} +[integration-testing.md](integration-testing.md) +{% endcontent-ref %} + +{% content-ref url="unit-testing.md" %} +[unit-testing.md](unit-testing.md) +{% endcontent-ref %} diff --git a/16/umbraco-cms/implementation/nullable-reference-types.md b/16/umbraco-cms/implementation/nullable-reference-types.md new file mode 100644 index 00000000000..7a4d96d0f2b --- /dev/null +++ b/16/umbraco-cms/implementation/nullable-reference-types.md @@ -0,0 +1,17 @@ +--- +description: "In this article we describe what Nullable reference types is." +--- + +# Nullable Reference Types + +From Umbraco version 10, Nullable Reference Types is enabled by default in Umbraco. + +Nullable reference types is a group of features introduced in C# 8.0. These features can be used to minimize the likelihood that your code causes the runtime to throw `System.NullReferenceException`. + +Nullable reference types includes three features that help you avoid these exceptions, including the ability to explicitly mark a reference type as nullable: + +- Improved static flow analysis that determines if a variable may be null before dereferencing it. +- Attributes that annotate APIs so that the flow analysis determines null-state. +- Variable annotations that developers use to explicitly declare the intended null-state for a variable. + +To learn more about Nullable Reference Types, refer to the [Microsoft Documentation](https://docs.microsoft.com/en-us/dotnet/csharp/nullable-references) diff --git a/16/umbraco-cms/implementation/services/README.md b/16/umbraco-cms/implementation/services/README.md new file mode 100644 index 00000000000..1b2497ae0d1 --- /dev/null +++ b/16/umbraco-cms/implementation/services/README.md @@ -0,0 +1,600 @@ +--- +description: >- + Umbraco has a range of 'Core' Services and Helpers that act as a 'gateway' to + Umbraco data and functionality to use when extending or implementing an + Umbraco site. +--- + +# Services and Helpers + +Umbraco has a range of 'Core' Services and Helpers that act as a 'gateway' to Umbraco data and functionality to use when extending or implementing an Umbraco site. + +The general rule of thumb is that management Services provide access to allow the modification of Umbraco data (and therefore aren't optimised for displaying data). Helpers on the other hand provide access to readonly data with performance of displaying data taken into consideration. + +{% hint style="warning" %} +Although there is a management Service named the `IContentService` - only use this to modify content - do not use the `IContentService` in a View/Template to pull back data to display, this will make requests to the database and be slow - here instead inject the `IPublishedContentQueryAccessor` interface and get the `IPublishedContentQuery` that operate against a cache of published content items, and are significantly quicker. +{% endhint %} + +The management Services and Helpers are all registered with Umbraco's underlying DI framework. This article aims to show examples of gaining access to utilise these resources in multiple different scenarios. There are subtle differences to be aware of depending on what part of Umbraco is being extended. + +This article will also suggest how to follow a similar pattern to encapsulate custom 'site specific' implementation logic, in similar services and helpers, registered with the underlying DI contain. This would be to avoid repetition and promote consistency and readability within an Umbraco site solution. + +## Accessing Management Services and Helpers in a Template/View + +Inside a view/template or partial view, access is also provided by the DI framework, by using the `@inject` keyword. + +```csharp +@inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage +@using ContentModels = Umbraco.Cms.Web.Common.PublishedModels; +@using Umbraco.Cms.Core.Services; +@using Umbraco.Cms.Web.Common; + +@* it is really 'unlikely' to need to use a management Service in a view: *@ +@inject IRelationService RelationService +@inject UmbracoHelper Umbraco + +@{ + Layout = null; + + // retrieve an item from Umbraco's published cache with id 123 + IPublishedContent publishedContentItem = Umbraco.Content(123); +} +``` + +## Accessing Core Services and Helpers in a Controller + +Inside a [custom Controller](../../reference/routing/custom-controllers.md) access is provided to Services via the `Services` property ([ServiceContext](../../reference/management/)) and the `UmbracoHelper` via the `Umbraco` property ([UmbracoHelper](../../reference/querying/umbracohelper.md)). + +```csharp +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.ViewEngines; +using Microsoft.Extensions.Logging; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Web; +using Umbraco.Cms.Web.Common.Controllers; + +namespace Umbraco9.Controllers; + +public class BlogPostController : RenderController +{ + private readonly ILogger _logger; + private readonly IPublishedContentQuery _publishedContentQuery; + private readonly IRelationService _relationService; + + public BlogPostController( + ILogger logger, + ICompositeViewEngine compositeViewEngine, + IUmbracoContextAccessor umbracoContextAccessor, + IPublishedContentQuery publishedContentQuery, + IRelationService relationService) + : base(logger, compositeViewEngine, umbracoContextAccessor) + { + _logger = logger; + _publishedContentQuery = publishedContentQuery; + _relationService = relationService; + } + + public override IActionResult Index() + { + // write helpful messages to the Umbraco logs to aid with debugging + _logger.LogInformation("Using core logger implementation"); + // retrieve an item from Umbraco's published cache with id 123 + IPublishedContent publishedContentItem = _publishedContentQuery.Content(123); + // it is unlikely to use a management service when rendering content from a custom controller + //(when using relationService like this you would want to provide a layer of caching) + var allRelatedUmbracoItems = _relationService.GetByParentId(CurrentPage.Id); + + return base.Index(); + } +} +``` + +## Accessing core Services and Helpers when there is no 'UmbracoContext' eg in a Component or C# Class + +Controllers and Views can access an `IUmbracoContext` by injecting the `IUmbracoContextAccessor`, however this is not always the case 'everywhere in Umbraco', for example common extension points: Components,ContentFinders or Custom C# Classes. + +{% hint style="warning" %} +IUmbracoContext, UmbracoHelper, IPublishedContentQuery - are all based on an HttpRequest - their lifetime is controlled by an HttpRequest. So if you are not operating within an actual request, you cannot inject these parameters and if you try to ... Umbraco will report an error on startup. +{% endhint %} + +### Injecting Services into a Component + +It's possible to inject management Services that do not rely on the `UmbracoContext` into the constructor of a component. This example shows injecting the `IMediaService` in a Notification Handler to create a corresponding Media Folder for every 'landing page' that is saved in the Content Section, by subscribing to the 'Content Saved' notification. + +```csharp +using System.Linq; +using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Core.DependencyInjection; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; + +namespace Umbraco9.Components; + +public class SubscribeToContentSavedEventComposer : IComposer +{ + public void Compose(IUmbracoBuilder builder) + { + builder.AddNotificationHandler(); + } +} +public class SubscribeToContentSavedNotification: INotificationHandler +{ + private readonly IMediaService _mediaService; + + public SubscribeToContentSavedNotification(IMediaService mediaService) + { + _mediaService = mediaService; + } + + public void Handle(ContentSavedNotification notification) + { + foreach (var contentItem in notification.SavedEntities) + { + // if this is a new landing page create a folder for associated media in the media section + if (contentItem.ContentType.Alias == "landingPage") + { + // we have injected in the mediaService in the constructor for the component see above. + bool hasExistingFolder = _mediaService.GetByLevel(1).Any(f => f.Name == contentItem.Name); + if (!hasExistingFolder) + { + // let's create one (-1 indicates the root of the media section) + IMedia newFolder = _mediaService.CreateMedia(contentItem.Name, -1, "Folder"); + _mediaService.Save(newFolder); + } + } + } + } +} +``` + +See documentation on [Composing](../composing.md) for further examples and information on Components and Composition. + +### Accessing Published Content outside of a Http Request + +Trying to inject types that are based on an Http Request such as `UmbracoHelper` or `IPublishedContentQuery` into classes that are not based on an Http Request will trigger an error. However, there is a technique that allows the querying of the Umbraco Published Content, using the `UmbracoContextFactory` and calling `EnsureUmbracoContext()`. + +In this example, when a page is unpublished, instead of a 404 occurring for the content when the url is requested in the future, we might want to serve a 410 'page gone' status code instead. We handle the Unpublishing notification of the ContentService, access the Published Content Cache, determine it's 'published url' and then store for later use in any 'serving the 410' mechanism. + +An [IContentFinder](../../reference/routing/request-pipeline/icontentfinder.md) could be placed in the ContentFinder ordered collection, right before a 404 is served. This could be done to lookup the incoming request against the stored location of 410 urls, and serve the 410 status request code if a match is found for the previously published item. + +```csharp +using System; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Core.DependencyInjection; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.PublishedCache; +using Umbraco.Cms.Core.Web; +using Umbraco.Extensions; + +namespace Umbraco9.Components; + +public class HandleUnPublishingEventComposer : IComposer +{ + public void Compose(IUmbracoBuilder builder) + { + builder.AddNotificationHandler(); + } +} + +public class HandleUnPublishingHandler : INotificationHandler +{ + private readonly IUmbracoContextFactory _umbracoContextFactory; + + public HandleUnPublishingHandler(IUmbracoContextFactory umbracoContextFactory) + { + _umbracoContextFactory = umbracoContextFactory; + } + + public void Handle(ContentUnpublishedNotification notification) + { + // for each unpublished item, we want to find the url that it was previously 'published under' and store in a database table or similar + using (UmbracoContextReference umbracoContextReference = _umbracoContextFactory.EnsureUmbracoContext()) + { + // the UmbracoContextReference provides access to the ContentCache + IPublishedContentCache contentCache = umbracoContextReference.UmbracoContext.Content; + + foreach (var item in notification.UnpublishedEntities) + { + if (item.ContentType.Alias == "blogpost") + { + // item being unpublished will still be in the cache, as unpublishing event fires before the cache is updated. + IPublishedContent soonToBeUnPublishedItem = contentCache.GetById(item.Id); + + if (soonToBeUnPublishedItem != null) + { + string previouslyPublishedUrl = soonToBeUnPublishedItem.Url(); + + if (!string.IsNullOrEmpty(previouslyPublishedUrl) && previouslyPublishedUrl != "#") + { + _customFourTenService.InsertFourTenUrl(previouslyPublishedUrl, DateTime.UtcNow); + } + } + } + } + } + } +} +``` + +#### Accessing the Published Content Cache from a Content Finder / UrlProvider + +Inside a ContentFinder access to the content cache is possible by injecting `IUmbracoContextAccessor` into the constructor and provided via the PublishedRequest object: + +```csharp +public Task TryFindContent(IPublishedRequestBuilder request) +{ + if (!UmbracoContextAccessor.TryGetUmbracoContext(out var umbracoContext)) + { + return false; + } + var someContent = umbracoContext.Content.GetById(1234); + + // ... +} +``` + +And inside an `IPublishedUrlProvider` injection of `IUmbracoContextAccessor` into the constructor is also possible. + +```csharp +private readonly IUmbracoContextAccessor _umbracoContextAccessor; + +public MyCustomUrlProvider(IUmbracoContextAccessor umbracoContextAccessor) +{ + _umbracoContextAccessor = umbracoContextAccessor ?? throw new ArgumentNullException(nameof(umbracoContextAccessor)); +} + +public override UrlInfo GetUrl(IPublishedContent content, UrlMode mode = UrlMode.Default, string culture = null, Uri current = null) +{ + + var umbracoContext = _umbracoContextAccessor.GetRequiredUmbracoContext(); + var someContent = umbracoContext.Content.GetById(1234); + + // ... +} +``` + +{% hint style="info" %} +It is still possible to inject services into IContentFinder's. IContentFinders are singletons, but the example is showing you do not 'need to' in order to access the Published Content Cache. +{% endhint %} + +## Customizing Services and Helpers + +When implementing an Umbraco site, it is likely to have to execute similar code that accesses or operates on Umbraco data, in multiple places, perhaps using the core management Services or Umbraco Helpers. + +For example; Getting a list of the latest News Articles, or building a link to the site's News Section or Contact Us page. Repeating this kind of logic in multiple places, Views, Partial Views / Controllers etc, is possible, but it's generally considered good practice to consolidate this logic into a single place. + +### Extension methods + +One option is to add 'Extension Methods' to the `UmbracoHelper` class or `IPublishedContentQuery` interface. + +```csharp +using System.Linq; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Extensions; + +namespace Umbraco9.Components; + +public static class PublishedContentQueryExtensions +{ + public static IPublishedContent GetNewsSection(this IPublishedContentQuery publishedContentQuery) + { + // assuming a single site with a single News Section at the top level + IPublishedContent siteRoot = publishedContentQuery.ContentAtRoot().FirstOrDefault(); + // make sure siteRoot isn't null, then locate first child content item with alias 'newsSection' + return siteRoot?.FirstChild(f => f.ContentType.Alias == "newsSection") ?? null; + } +} +``` + +Anywhere there is reference to the `UmbracoHelper` or `IPublishedContentQuery` and a reference is added to the namespace the extension belongs to, it is possible to call the method by writing `_publishedContentQuery.GetNewsSection()`. + +### Custom Services and Helpers + +Another option, is to make use of the underlying DI framework, and create custom Services and Helpers, that in turn can have the 'core' management Services and Umbraco Helpers injected into them. + +This approach enables the grouping together of similar methods within a suitably named service, and promotes the possibility of testing this custom logic outside of Controllers and Views. + +{% hint style="warning" %} +Depending on where the custom service will be utilised, we will dictate the best practice approach to accessing the 'Published Content Cache'. If it is 100% guaranteed that the service will only be called from a place with an UmbracoContext, eg a controller or view, then it is safe to inject `IPublishedContentQuery` etc for simplicity. However if the custom service is called in a location without UmbracoContext (eg an notification handler) it will fail. Therefore the approach of accessing the Published Content Cache via injecting IUmbracoContextFactory and calling `EnsureUmbracoContext()` will provide consistency across any custom services no matter where they are utilised. +{% endhint %} + +In this example, we create a custom service, that's responsible for finding key pages within a site, eg the News Section or the Contact Us page. These methods will commonly be called in different places throughout the site, and it's great to encapsulate the logic to retrieve them in a single place - we'll call this service `SiteService`. + +Create an interface to define the service: + +```csharp +using Umbraco.Cms.Core.Models.PublishedContent; + +namespace Umbraco9.Services; + +public interface ISiteService +{ + IPublishedContent GetNewsSection(); + IPublishedContent GetContactUsPage(); +} +``` + +Create the concrete service class that implements the interface: + +```csharp +using System; +using Umbraco.Cms.Core.Models.PublishedContent; + +namespace Umbraco9.Services; + +public class SiteService : ISiteService +{ + public IPublishedContent GetNewsSection() + { + // TODO: implement this! + throw new NotImplementedException(); + } + + public IPublishedContent GetContactUsPage() + { + // TODO: implement this! + throw new NotImplementedException(); + } +} +``` + +Register the custom service with Umbraco's underlying DI container using an `IComposer`: + +```csharp +using Microsoft.Extensions.DependencyInjection; +using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Core.DependencyInjection; + +namespace Umbraco9.Services; + +public class RegisterSiteServiceComposer : IComposer +{ + public void Compose(IUmbracoBuilder builder) + { + builder.Services.AddSingleton(); + } +} +``` + +#### Lifetimes + +**"Transient"** services can be injected into "Transient" and below ⤵. (i.e. "Transient" services can be injected anywhere) + +* "Transient" means that anytime this type is needed a brand new instance of this type will be created. + +**"Scope"** services can be injected into "Request"/"Scope" based lifetimes only + +* "Scope" means that a single instance of this type will be created for the duration of the current HttpRequest. The instance will be disposed of at the end of the current HttpRequest. + +**"Singleton"** services can be injected into "Singletons" and below ⤵. + +* "Singleton" means that only a single instance of this type will ever be created for the lifetime of the application. + +#### Implementing the service + +**1 - The service will ONLY be used during a request like in a Controller or View** + +You can avoid repeating common implementation logic in multiple controllers and views. This is done by consolidating these implementations into a custom service. If you are very familiar with IPublishedContentQuery injecting this into the custom service is straight forward, but the caveat is you can only use this service in a controller/view. + +For example, locating the 'special' pages in the site using the familiar syntax of the `IPublishedContentQuery`: + +```csharp +using System.Linq; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Extensions; + +namespace Umbraco9.Services; + +public class SiteService : ISiteService +{ + private readonly IPublishedContentQuery _contentQuery; + public SiteService(IPublishedContentQuery contentQuery) + { + _contentQuery = contentQuery; + } + public IPublishedContent GetNewsSection() + { + var siteRoot = _contentQuery.ContentAtRoot().FirstOrDefault(); + var newsSection = siteRoot?.FirstChild(f => f.ContentType.Alias == "newsSection") ?? null; + return newsSection; + } + public IPublishedContent GetContactUsPage() + { + var siteRoot = _contentQuery.ContentAtRoot().FirstOrDefault(); + var contactUs = siteRoot?.FirstChild(f => f.ContentType.Alias == "contactUs") ?? null; + return contactUs; + } +} +``` + +**2 - The service can be used within or outside of a web request** + +```csharp +using System.Linq; +using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.Web; +using Umbraco.Extensions; + +namespace Umbraco9.Services; + +public class SiteService : ISiteService +{ + private readonly IUmbracoContextFactory _umbracoContextFactory; + + public SiteService(IUmbracoContextFactory umbracoContextFactory) + { + _umbracoContextFactory = umbracoContextFactory; + } + public IPublishedContent GetNewsSection() + { + using var umbracoContextReference = _umbracoContextFactory.EnsureUmbracoContext(); + var contentQuery = umbracoContextReference.UmbracoContext.Content; + var siteRoot = contentQuery.GetAtRoot().FirstOrDefault(); + var newsSection = siteRoot?.FirstChild(f => f.ContentType.Alias == "newsSection") ?? null; + return newsSection; + } + public IPublishedContent GetContactUsPage() + { + using var umbracoContextReference = _umbracoContextFactory.EnsureUmbracoContext(); + var contentQuery = umbracoContextReference.UmbracoContext.Content; + var siteRoot = contentQuery.GetAtRoot().FirstOrDefault(); + var contactUs = siteRoot?.FirstChild(f => f.ContentType.Alias == "contactUs") ?? null; + return contactUs; + } +} +``` + +The second approach can seem 'different' or more complex at first glance, but it is the syntax and method names that are slightly different... it enables the registering of the service in Singleton Scope, and its use outside of controllers and views. + +{% hint style="info" %} +Occasionally, you may face a situation where Umbraco fails to boot, due to a circular dependency on `IUmbracoContextFactory`. This can happen if your service interacts with third party code that also depends on an `IUmbracoContextFactory` instance (e.g. an Umbraco package). + +See the [Circular Dependencies](circular-dependencies.md) article for an example on how to get around this. +{% endhint %} + +**Aside: What is the IUmbracoContextAccessor then?** + +The `IUmbracoContextFactory` will obtain an `UmbracoContext` by first checking to see if one exists on the current thread using the `IUmbracoContextAccessor`. This is a singleton that can be injected anywhere and whose function is to provide access to the current UmbracoContext. On a 'non request' thread the IUmbracoContextAccessor's TryGetUmbracoContext method will return false and the IUmbracoContextFactory will create a new instance of the UmbracoContext. + +If you need to know whether the UmbracoContext has been obtained from an existing thread, or whether it has been freshly created, you can 'inject' `IUmbracoContextAccessor` yourself. This will check if the UmbracoContext is null using the TryGetUmbracoContext method, indicating whether you are in a 'non request' thread or not. You will still need to inject and use an IUmbracoContextFactory if you subsequently want to obtain an UmbracoContext in a non-request thread. + +```csharp +using System.Linq; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Web; +using Umbraco.Web.PublishedCache; + +namespace Umbraco9.Services; + +public class SiteService : ISiteService +{ + private readonly IUmbracoContextAccessor _umbracoContextAccessor; + private readonly IUmbracoContextFactory _umbracoContextFactory; + + public SiteService(IUmbracoContextAccessor umbracoContextAccessor, IUmbracoContextFactory umbracoContextFactory) + { + _umbracoContextAccessor = umbracoContextAccessor; + _umbracoContextFactory = umbracoContextFactory; + bool hasUmbracoContext = _umbracoContextAccessor.TryGetUmbracoContext(out _); + } +} +``` + +NB: With the `IUmbracoContextAccessor` and `IUmbracoContextFactory` you should NEVER have to inject the UmbracoContext itself directly into any of your constructors. + +#### Using the custom SiteService inside a Controller + +Because we've registered the SiteService with Umbraco's underlying DI framework we can inject the service into our controller's constructor, in the same way as 'core' Services and Helpers. + +```csharp +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.ViewEngines; +using Microsoft.Extensions.Logging; +using Umbraco.Cms.Core.Web; +using Umbraco.Cms.Web.Common.Controllers; +using Umbraco9.Services; + +namespace Umbraco9.Controllers; + +public class BlogPostController : RenderController +{ + private readonly ISiteService _siteService; + + public BlogPostController( + ILogger logger, + ICompositeViewEngine compositeViewEngine, + IUmbracoContextAccessor umbracoContextAccessor, + ISiteService siteService) + : base(logger, compositeViewEngine, umbracoContextAccessor) + { + _siteService = siteService; + } + public override IActionResult Index() + { + var newsSection = _siteService.GetNewsSection(); + var blogPostViewModel = new BlogPostViewModel(CurrentPage); + blogPostViewModel.HasNewsSection = false; + if (newsSection != null) + { + blogPostViewModel.HasNewsSection = true; + blogPostViewModel.NewsSection = newsSection; + } + + // etc + // Do other stuff here!, then return the custom viewmodel to the template view. + return CurrentTemplate(blogPostViewModel); + } +} +``` + +You can generate this ctor in Visual Studio by using either ctrl + . or alt + enter when your cursor is on the base class: + +
+ +#### Using the SiteService inside a View + +If strictly following the paradigm of MVC, calling custom Services from Views might feel like an anti-pattern. However there isn't necessarily one single 'best practice' approach to working with Umbraco. A lot depends on circumstance, expertise and pragmatism. Allowing Umbraco to handle the flow of incoming requests to a particular page + template, and writing implementation logic in Views/Templates, is still a very common approach. There are circumstances, where the custom implementation logic shared is very 'View' specific. Custom logic for constructing 'Alternative Text' for images or different crop urls for img srcsets can be neatly handled in a custom Helper/Service without having to create a hijacked MVC route for the request and build a complex ViewModel. Custom Services called from Views, can help separate the concerns, even if the 'plumbing' isn't pure MVC. + +To access the service directly from the view you would need to use the Razor `@inject` keyword to get a reference to the concrete implementation of the service registered with DI: + +```csharp +@using Umbraco9.Services + +@inject ISiteService SiteService +@inherits UmbracoViewPage +@{ + + IPublishedContent newsSection = SiteService.GetNewsSection(); +} +
+
+
+``` + +### Handle routes as server-side requests + +Sometimes you might want to request, for example "/sitemap.xml" from your server. Since this has a file extension it will be treated as a client-side request and will not work. You can configure routes to be handled as server-side requests in your program.cs. + +**For a single route:** + +```csharp +... +WebApplication app = builder.Build(); +builder.Services.Configure(options => +{ + options.HandleAsServerSideRequest = httpRequest => + { + return httpRequest.Path.StartsWithSegments("/sitemap.xml"); + }; +}); +``` + +**For multiple routes:** + +```csharp +services.Configure(options => +{ + string[] allowList = new[] {"/sitemap.xml", "robots.txt", ...}; + options.HandleAsServerSideRequest = httpRequest => + { + foreach (string route in allowList) + { + if (httpRequest.Path.StartsWithSegments(route)) + { + return true; + } + } + + return false; + }; +}); +``` diff --git a/16/umbraco-cms/implementation/services/circular-dependencies.md b/16/umbraco-cms/implementation/services/circular-dependencies.md new file mode 100644 index 00000000000..4239771029a --- /dev/null +++ b/16/umbraco-cms/implementation/services/circular-dependencies.md @@ -0,0 +1,25 @@ +# Circular Dependencies + +In some cases you might experience that a circular dependency is preventing your Umbraco installing from starting up. + +An example of this, could be a circular dependency on `IUmbracoContextFactory`, which would happen if your service interacts with third party code that also depends on an `IUmbracoContextFactory` instance. + +In this situation, you can request a lazy version of the dependency so it won't evaluate during boot, and would only be evaluated when accessed: + +```csharp +public class SiteService : ISiteService +{ + private readonly Lazy _umbracoContextFactory; + public SiteService(Lazy umbracoContextFactory) + { + _umbracoContextFactory = umbracoContextFactory; + } + public IPublishedContent GetNewsSection() + { + using (UmbracoContextReference umbracoContextReference = _umbracoContextFactory.Value.EnsureUmbracoContext()) + { + // Do your thing + } + } +} +``` diff --git a/16/umbraco-cms/implementation/services/images/vs-di-constructor-generation-tip.gif b/16/umbraco-cms/implementation/services/images/vs-di-constructor-generation-tip.gif new file mode 100644 index 00000000000..edf5db7a78c Binary files /dev/null and b/16/umbraco-cms/implementation/services/images/vs-di-constructor-generation-tip.gif differ diff --git a/16/umbraco-cms/implementation/unit-testing.md b/16/umbraco-cms/implementation/unit-testing.md new file mode 100644 index 00000000000..314cc401792 --- /dev/null +++ b/16/umbraco-cms/implementation/unit-testing.md @@ -0,0 +1,264 @@ +--- +description: A guide to getting started with unit testing in Umbraco +--- + +# Unit Testing + +These examples inspire unit testing in Umbraco with Umbraco 9.x, 10.x, 11.x and 12.x, using [NUnit](https://nunit.org/), [Moq](https://github.com/moq/moq4), and [AutoFixture](https://github.com/AutoFixture/AutoFixture). There are many ways of testing Umbraco and there’s no right or wrong way. + +When testing components in Umbraco, such as controllers, helpers, services etc. these components often require that you provide a couple of dependencies in your classes using [dependency injection](../reference/using-ioc.md). This is because a lot of magic happens “under the hood” of Umbraco and these dependencies are needed for that magic to happen. + +{% hint style="info" %} +Writing Unit Tests increases awareness of underlying dependencies, enhancing your skills as an Umbraco developer. +{% endhint %} + +## Mocking + +These tests follows an approach thats based on isolating your tests from Umbraco and mock as much of Umbraco’s dependencies as possible. Think of it like you’re not testing Umbraco, you’re testing how your implementation code interacts with Umbraco’s behavior. + +Once you get familiar with these underlying dependencies you might want to start looking into replacing them with actual implementations. This leans more towards integration or E2E testing. Again these examples should be a source of inspiration and the quickest way to get started with Unit Testing. + +{% hint style="info" %} +If you are new to mocking you can read more on this topic [here](https://martinfowler.com/bliki/TestDouble.html) or use the [Moq Quickstart](https://github.com/Moq/moq4/wiki/Quickstart) guide. For more inspiration and other ways of how to write tests in Umbraco there's a blogpost from HQ member Bjarke Berg about [Automated Testing](https://umbraco.com/blog/automated-testing-in-umbraco/). +{% endhint %} + +### Testing a ContentModel + +See [Reference documentation on Executing an Umbraco request](default-routing/execute-request.md). + +```csharp +public class PageViewModel : ContentModel +{ + public PageViewModel(IPublishedContent content) : base(content) { } + + public string Heading => (string)this.Content.GetProperty(nameof(Heading)).GetValue(); +} + +public class PageViewModelTests +{ + [Test, AutoData] + public void Given_PublishedContent_When_GetHeading_Then_ReturnPageViewModelWithHeading(string value, Mock content) + { + SetupPropertyValue(content, nameof(PageViewModel.Heading), value); + + var viewModel = new PageViewModel(content.Object); + + Assert.AreEqual(value, viewModel.Heading); + } + + public void SetupPropertyValue(Mock content, string propertyAlias, string propertyValue, string culture = null) + { + var property = new Mock(); + property.Setup(x => x.Alias).Returns(nameof(PageViewModel.Heading)); + property.Setup(x => x.GetValue(culture, null)).Returns(propertyValue); + content.Setup(x => x.GetProperty(propertyAlias)).Returns(property.Object); + } +} +``` + +### Testing a RenderController + +See [Reference documentation for Custom controllers (Hijacking Umbraco Routes)](../reference/routing/custom-controllers.md#creating-a-custom-controller). + +```csharp +public class PageController : RenderController +{ + public PageController(ILogger logger, ICompositeViewEngine compositeViewEngine, IUmbracoContextAccessor umbracoContextAccessor) : base(logger, compositeViewEngine, umbracoContextAccessor) { } + + public IActionResult Page(ContentModel model) + { + return View(new PageViewModel(model.Content)); + } +} + +public class PageControllerTests +{ + private PageController controller; + + [SetUp] + public void SetUp() + { + this.controller = new PageController(Mock.Of>(), Mock.Of(), Mock.Of()); + } + + [Test, AutoData] + public void When_PageAction_ThenResultIsIsAssignableFromContentResult(Mock content) + { + var model = new ContentModel(content.Object); + + var result = this.controller.Page(model); + + Assert.IsAssignableFrom(result); + } + + [Test, AutoData] + public void Given_PublishedContentHasHeading_When_PageAction_Then_ReturnViewModelWithHeading_With_AutoFixture(string value, Mock content) + { + SetupPropertyValue(content, nameof(PageViewModel.Heading), value); + + var viewModel = (PageViewModel)((ViewResult)this.controller.Page(new ContentModel(content.Object))).ViewData.Model; + + Assert.AreEqual(value, viewModel.Heading); + } + + public void SetupPropertyValue(Mock content, string propertyAlias, string propertyValue, string culture = null) + { + var property = new Mock(); + property.Setup(x => x.Alias).Returns(nameof(PageViewModel.Heading)); + property.Setup(x => x.GetValue(culture, null)).Returns(propertyValue); + content.Setup(x => x.GetProperty(propertyAlias)).Returns(property.Object); + } +} +``` + +### Testing a SurfaceController + +See [Reference documentation on SurfaceControllers](../reference/routing/surface-controllers/). + +```csharp +public class PageSurfaceController : SurfaceController +{ + public PageSurfaceController(IUmbracoContextAccessor umbracoContextAccessor, IUmbracoDatabaseFactory databaseFactory, ServiceContext services, AppCaches appCaches, IProfilingLogger profilingLogger, IPublishedUrlProvider publishedUrlProvider) : base(umbracoContextAccessor, databaseFactory, services, appCaches, profilingLogger, publishedUrlProvider) { } + + [HttpPost] + public IActionResult Submit() + { + return Content("H5YR!"); + } +} + +public class PageSurfaceControllerTests +{ + private PageSurfaceController controller; + + [SetUp] + public void SetUp() + { + this.controller = new PageSurfaceController(Mock.Of(), Mock.Of(), ServiceContext.CreatePartial(), AppCaches.NoCache, Mock.Of(), Mock.Of()); + } + + [Test] + public void When_SubmitAction_ThenResultIsIsAssignableFromContentResult() + { + var result = this.controller.Submit(); + + Assert.IsAssignableFrom(result); + } + + [Test] + public void When_SubmitAction_Then_ExpectHelloWorld() + { + var result = (ContentResult)this.controller.Submit(); + + Assert.AreEqual("H5YR!", result.Content); + } +} +``` + +{% hint style="info" %} +`ServiceContext.CreatePartial()` has optional parameters, and by naming them you only need to mock the dependencies that you need, for example: `ServiceContext.CreatePartial(contentService: Mock.Of());` +{% endhint %} + +## Testing a Controller + +```csharp +[ApiController] +[Route("/umbraco/api/products")] +public class ProductsController : Controller +{ + public IEnumerable GetAllProducts() + { + return new[] { "Table", "Chair", "Desk", "Computer", "Beer fridge" }; + } + + [HttpGet("getallproductsjson")] + public JsonResult GetAllProductsJson() + { + return new JsonResult(this.GetAllProducts()); + } +} + +public class ProductsControllerTests +{ + private ProductsController controller; + + [SetUp] + public void SetUp() + { + this.controller = new ProductsController(); + } + + [Test] + public void WhenGetAllProducts_ThenReturnViewModelWithExpectedProducts() + { + var expected = new[] { "Table", "Chair", "Desk", "Computer", "Beer fridge" }; + + var result = this.controller.GetAllProducts(); + + Assert.That(expected == result); + } + + [Test] + public void WhenGetAllProductsJson_ThenReturnViewModelWithExpectedJson() + { + var json = JsonSerializer.Serialize(this.controller.GetAllProductsJson().Value); + + Assert.That("[\"Table\",\"Chair\",\"Desk\",\"Computer\",\"Beer fridge\"]" == json); + } +} +``` + +## Testing ICultureDictionary using the UmbracoHelper + +See [Core documentation on the interface ICultureDictionary](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Dictionary.ICultureDictionary.html). + +```csharp +public class HomeController : RenderController +{ + private readonly UmbracoHelper umbracoHelper; + + public HomeController(UmbracoHelper umbracoHelper, ILogger logger, ICompositeViewEngine compositeViewEngine, IUmbracoContextAccessor umbracoContextAccessor) : base(logger, compositeViewEngine, umbracoContextAccessor) + { + this.umbracoHelper = umbracoHelper; + } + + public IActionResult Home(ContentModel model) + { + var myCustomModel = new PageViewModel(model.Content) + { + MyDictionaryProperty = this.umbracoHelper.GetDictionaryValue("myDictionaryKey") + }; + + return View(myCustomModel); + } +} + +public class HomeControllerTests +{ + private Mock cultureDictionary; + private Mock cultureDictionaryFactory; + private UmbracoHelper umbracoHelper; + private HomeController controller; + + [SetUp] + public void SetUp() + { + this.cultureDictionary = new Mock(); + this.cultureDictionaryFactory = new Mock(); + this.cultureDictionaryFactory.Setup(x => x.CreateDictionary()).Returns(this.cultureDictionary.Object); + this.umbracoHelper = new UmbracoHelper(this.cultureDictionaryFactory.Object, Mock.Of(), Mock.Of()); + this.controller = new HomeController(this.umbracoHelper, Mock.Of>(), Mock.Of(), Mock.Of()); + } + + [Test, AutoData] + public void GivenMyDictionaryKey_WhenIndexAction_ThenReturnViewModelWithMyPropertyDictionaryValue(string expected) + { + var model = new ContentModel(new Mock().Object); + this.cultureDictionary.Setup(x => x["myDictionaryKey"]).Returns(expected); + + var result = (PageViewModel)((ViewResult)this.controller.Home(model)).Model; + + Assert.AreEqual(expected, result.MyDictionaryProperty); + } +} +``` diff --git a/16/umbraco-cms/legacy-documentation/README.md b/16/umbraco-cms/legacy-documentation/README.md new file mode 100644 index 00000000000..cfbf35ba351 --- /dev/null +++ b/16/umbraco-cms/legacy-documentation/README.md @@ -0,0 +1,21 @@ +--- +description: Resources and links for older versions of Umbrco CMS. +--- + +# Legacy Documentation + +This documentation platform covers only supported versions of the Umbraco CMS, excluding version 8. If you use an unsupported version, you need to go elsewhere. + +[Learn more about how long each major version of Umbraco is supported.](https://umbraco.com/products/knowledge-center/long-term-support-and-end-of-life/) + +## [Legacy versions on GitHub](https://github.com/umbraco/UmbracoDocs/tree/umbraco-eol-versions) + +When a major version of Umbraco CMS goes End of Life (EOL), the documentation for this version will be unpublished 3 months later. + +The documentation for all EOL versions will continue to be available on the [UmbracoDocs GitHub repository](https://github.com/umbraco/UmbracoDocs/tree/umbraco-eol-versions). + +## Our Umbraco + +The documentation for Umbraco 7 and 8 lives on [our.umbraco.com](https://our.umbraco.com/documentation/). + +
Umbraco 7 Documentationhttps://our.umbraco.com/documentation/
Umbraco 8 Documentationhttps://our.umbraco.com/documentation/
diff --git a/16/umbraco-cms/reference/api-documentation.md b/16/umbraco-cms/reference/api-documentation.md new file mode 100644 index 00000000000..410b7ebb2f8 --- /dev/null +++ b/16/umbraco-cms/reference/api-documentation.md @@ -0,0 +1,27 @@ +--- +description: Information on Umbraco API Documentation +--- + +# API Documentation + +A library of API Reference documentation is auto-generated from the comments within the Umbraco Source Code. + +## C# API Documentation + +C# API references for the Umbraco Core, Infrastructure, Extensions and Web libraries. + +### [Umbraco.Cms.Core](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.html) + +### [Umbraco.Cms.Infrastructure](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Infrastructure.html) + +### [Umbraco.Cms.Web](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Web.Common.html) + +### [Umbraco.Extensions](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Extensions.html) + +{% hint style="info" %} +Opens a documentation browser that is different from the documentation section you're viewing now. +{% endhint %} + +## Backoffice UI Documentation + +You can find all reference documentation for the Backoffice UI in the [UI Library](../customizing/ui-library.md) article. diff --git a/16/umbraco-cms/reference/api-versioning-and-openapi.md b/16/umbraco-cms/reference/api-versioning-and-openapi.md new file mode 100644 index 00000000000..32dc11e8f00 --- /dev/null +++ b/16/umbraco-cms/reference/api-versioning-and-openapi.md @@ -0,0 +1,360 @@ +--- +description: How to use API versioning and OpenAPI (Swagger) for your own APIs. +--- + +# API versioning and OpenAPI (Swagger) + +Umbraco ships with Swagger to document the Content Delivery API. Swagger and the Swagger UI is available at `{yourdomain}/umbraco/swagger`. For security reasons, both are disabled in production environments. + +Due to the way OpenAPI works within ASP.NET Core, we have to apply some configurations in a global scope. If your Umbraco site used Swagger previous to Umbraco 12, these global configurations may interfere with your setup. + +In this article we'll explore concrete solutions to overcome challenges with the global configurations. + +{% hint style="info" %} +Umbraco uses [Swashbuckle.AspNetCore](https://github.com/domaindrivendev/Swashbuckle.AspNetCore/) to handle Swagger and the Swagger UI. + +If you have been using [NSwag](https://github.com/RicoSuter/NSwag) previous to Umbraco 12, chances are your Swagger setup will continue to work in Umbraco 12+ without any changes. Swashbuckle.AspNetCore and NSwag can coexist within the same site, as long as there are no conflicting routes between the two. + +That being said, it would be sensible to consider migrating your API documentation to Swashbuckle.AspNetCore. This way you can avoid having multiple dependencies that perform the same tasks. +{% endhint %} + +## API versioning + +The Umbraco APIs rely on having the requested API version as part of the URL. If you prefer a different versioning for your own APIs, you can setup alternatives while still preserving the functionality of the Umbraco API. + +The following code sample illustrates how you can use a custom header to pass the requested API version to your own APIs. + +{% code title="MyConfigureApiVersioningOptions.cs" %} + +```csharp +using Asp.Versioning; +using Microsoft.Extensions.Options; + +namespace My.Custom.Swagger; + +public class MyConfigureApiVersioningOptions : IConfigureOptions +{ + public void Configure(ApiVersioningOptions options) + => options.ApiVersionReader = ApiVersionReader.Combine( + // the URL segment version reader is required for the Umbraco APIs + new UrlSegmentApiVersionReader(), + // here you can add additional version readers to suit your needs + new HeaderApiVersionReader("my-api-version")); +} + +public static class MyConfigureApiVersioningUmbracoBuilderExtensions +{ + // call this from Program.cs, i.e.: + // builder.CreateUmbracoBuilder() + // ... + // .ConfigureMyApiVersioning() + // .Build(); + public static IUmbracoBuilder ConfigureMyApiVersioning(this IUmbracoBuilder builder) + { + builder.Services.ConfigureOptions(); + return builder; + } +} +``` + +{% endcode %} + +## Swagger route and/or availability + +As mentioned in the beginning of this article, Umbraco exposes Swagger and the Swagger UI at `{yourdomain}/umbraco/swagger`. Both are disabled when the site is in production mode. + +The code sample below shows how to change the Swagger route and availability. + +{% code title="MySwaggerRouteTemplatePipelineFilter.cs" %} + +```csharp +using Umbraco.Cms.Api.Common.OpenApi; +using Umbraco.Cms.Web.Common.ApplicationBuilder; + +namespace My.Custom.Swagger; + +public class MySwaggerRouteTemplatePipelineFilter : SwaggerRouteTemplatePipelineFilter +{ + public MySwaggerRouteTemplatePipelineFilter(string name) : base(name) + { + } + + /// + /// This is how you change the route template for the Swagger docs. + /// + protected override string SwaggerRouteTemplate(IApplicationBuilder applicationBuilder) => "swagger/{documentName}/swagger.json"; + + /// + /// This is how you change the route for the Swagger UI. + /// + protected override string SwaggerUiRoutePrefix(IApplicationBuilder applicationBuilder) => "swagger"; + + /// + /// This is how you configure Swagger to be available always. + /// Please note that this is NOT recommended. + /// + protected override bool SwaggerIsEnabled(IApplicationBuilder applicationBuilder) => true; +} + +public static class MyConfigureSwaggerRouteUmbracoBuilderExtensions +{ + // call this from Program.cs, i.e.: + // CreateUmbracoBuilder() + // ... + // .ConfigureMySwaggerRoute() + // .Build(); + public static IUmbracoBuilder ConfigureMySwaggerRoute(this IUmbracoBuilder builder) + { + builder.Services.Configure(options => + { + // include this line if you do NOT want the Swagger docs at /umbraco/swagger + options.PipelineFilters.RemoveAll(filter => filter is SwaggerRouteTemplatePipelineFilter); + + // setup your own Swagger routes + options.AddFilter(new MySwaggerRouteTemplatePipelineFilter("MyApi")); + }); + return builder; + } +} +``` + +{% endcode %} + +## Adding custom operation IDs + +Custom operation IDs can be a great way to make your API easier to use. Especially for consumers that generate API contracts from your Swagger documents. + +The Umbraco APIs use custom operation IDs for that exact reason. In order to remain as un-intrusive as possible, these custom operation IDs are not applied to your APIs. + +If you want to apply custom operation IDs to your APIs, you must ensure that the Umbraco APIs retain their custom operation IDs. The following code sample illustrates how this can be done. + +{% code title="MyOperationIdSelector.cs" %} + +```csharp +using Asp.Versioning; +using Microsoft.AspNetCore.Mvc.ApiExplorer; +using Microsoft.AspNetCore.Mvc.Controllers; +using Umbraco.Cms.Api.Common.OpenApi; + +namespace My.Custom.Swagger; + +public class MyOperationIdSelector : OperationIdSelector +{ + public override string? OperationId(ApiDescription apiDescription, ApiVersioningOptions apiVersioningOptions) + { + // use this if you want to opt into the default Umbraco operation IDs: + // return UmbracoOperationId(apiDescription, apiVersioningOptions); + + // only handle your own APIs here - make sure to let the base class handle the Umbraco APIs + if (apiDescription.ActionDescriptor is not ControllerActionDescriptor controllerActionDescriptor + || controllerActionDescriptor.ControllerTypeInfo.Namespace?.StartsWith("My.Custom.Api") is false) + { + return base.OperationId(apiDescription, apiVersioningOptions); + } + + // build your own logic to generate operation IDs here + return apiDescription.RelativePath is null + ? null + : string.Join(string.Empty, apiDescription.RelativePath.Split(new[] { '/', '-' }).Select(segment => segment.ToFirstUpperInvariant())); + } +} + +public static class MyOperationIdUmbracoBuilderExtensions +{ + public static IUmbracoBuilder ConfigureMyOperationId(this IUmbracoBuilder builder) + { + // call this from Program.cs, i.e.: + // CreateUmbracoBuilder() + // ... + // .ConfigureMyOperationId() + // .Build(); + builder.Services.AddSingleton(); + return builder; + } +} +``` + +{% endcode %} + +## Adding custom schema IDs + +Custom schema IDs can also make it easier for your API consumers to understand and work with your APIs. To that same end, Umbraco applies custom schema IDs to the Umbraco APIs - but not to your APIs. + +If you want to create custom schema IDs for your APIs, you must ensure that the Umbraco APIs retain their custom schema IDs. The following code sample illustrates how that can be done. + +{% code title="MySchemaIdSelector.cs" %} + +```csharp +using Umbraco.Cms.Api.Common.OpenApi; + +namespace My.Custom.Swagger; + +public class MySchemaIdSelector : SchemaIdSelector +{ + public override string SchemaId(Type type) + { + // use this if you want to opt into the default Umbraco schema IDs: + // return UmbracoSchemaId(type); + + // only handle your own types here - make sure to let the base class handle the Umbraco types + if (type.Namespace?.StartsWith("My.Custom.Api") is false) + { + return base.SchemaId(type); + } + + // build your own logic to generate schema IDs here + return string.Join(string.Empty, type.FullName!.Replace("My.Custom.Api", string.Empty).Split('.').Reverse()); + } +} + +public static class MySchemaIdUmbracoBuilderExtensions +{ + public static IUmbracoBuilder ConfigureMySchemaId(this IUmbracoBuilder builder) + { + // call this from Program.cs, i.e.: + // builder.CreateUmbracoBuilder() + // ... + // .ConfigureMySchemaId() + // .Build(); + builder.Services.AddSingleton(); + return builder; + } +} +``` + +{% endcode %} + +## Adding your own Swagger documents + +Umbraco automatically adds a "default" Swagger document to contain all APIs that are not explicitly mapped to a named Swagger document. This means that your custom APIs will automatically appear in the "default" Swagger document. + +If you want to exercise more control over where your APIs show up in Swagger, you can do so by adding your own Swagger documents. + +{% hint style="info" %} +Umbraco imposes no limitations on adding Swagger documents, and the code below is a simplistic example. + +In the [Swashbuckle GitHib repository](https://github.com/domaindrivendev/Swashbuckle.AspNetCore/) you will find comprehensive documentation for Swagger documents. +{% endhint %} + +A common use case for this is when you maintain multiple versions of the same API. Often you want to have separate Swagger documents for each version. The following code sample creates two Swagger documents - "My API v1" and "My API v2". + +{% code title="MyConfigureSwaggerGenOptions.cs" %} + +```csharp +using Microsoft.Extensions.Options; +using Microsoft.OpenApi.Models; +using Swashbuckle.AspNetCore.SwaggerGen; + +namespace My.Custom.Swagger; + +public class MyConfigureSwaggerGenOptions : IConfigureOptions +{ + public void Configure(SwaggerGenOptions options) + { + options.SwaggerDoc( + "my-api-v1", + new OpenApiInfo + { + Title = "My API v1", + Version = "1.0", + }); + + options.SwaggerDoc( + "my-api-v2", + new OpenApiInfo + { + Title = "My API v2", + Version = "2.0", + }); + } +} + +public static class MyConfigureSwaggerGenUmbracoBuilderExtensions +{ + public static IUmbracoBuilder ConfigureMySwaggerGen(this IUmbracoBuilder builder) + { + // call this from Program.cs, i.e.: + // builder.CreateUmbracoBuilder() + // ... + // .ConfigureMySwaggerGen() + // .Build(); + builder.Services.ConfigureOptions(); + return builder; + } +} +``` + +{% endcode %} + +With these Swagger documents in place, you can now assign the different versions of your API controllers to their respective documents using the `MapToApi` annotation. + +{% code title="MyApiController.cs" %} + +```csharp +using Asp.Versioning; +using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Api.Common.Attributes; + +namespace My.Custom.Api.V1; + +[Route("api/v{version:apiVersion}/my")] +[ApiController] +[ApiVersion("1.0")] +[MapToApi("my-api-v1")] +public class MyApiController : Controller +{ + [HttpGet] + [Route("do-something")] + [ProducesResponseType(typeof(MyDoSomethingViewModel), StatusCodes.Status200OK)] + public IActionResult DoSomething(string value) + => Ok(new MyDoSomethingViewModel(value)); +} + +public class MyDoSomethingViewModel +{ + public MyDoSomethingViewModel(string value) + => Value = value; + + public string Value { get; } +} +``` + +{% endcode %} + +{% code title="MyApiController.cs" %} + +```csharp +using Asp.Versioning; +using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Api.Common.Attributes; + +namespace My.Custom.Api.V2; + +[Route("api/v{version:apiVersion}/my")] +[ApiController] +[ApiVersion("2.0")] +[MapToApi("my-api-v2")] +public class MyApiController : Controller +{ + [HttpGet] + [Route("do-something")] + [ProducesResponseType(typeof(MyDoSomethingViewModel), StatusCodes.Status200OK)] + public IActionResult DoSomething(string value, int otherValue) + => Ok(new MyDoSomethingViewModel(value, otherValue)); +} + +public class MyDoSomethingViewModel +{ + public MyDoSomethingViewModel(string value, int otherValue) + { + Value = value; + OtherValue = otherValue; + } + + public string Value { get; } + + public int OtherValue { get; } +} +``` + +{% endcode %} diff --git a/16/umbraco-cms/reference/cache/README.md b/16/umbraco-cms/reference/cache/README.md new file mode 100644 index 00000000000..48756a7ab22 --- /dev/null +++ b/16/umbraco-cms/reference/cache/README.md @@ -0,0 +1,89 @@ +# Cache & Distributed Cache + +_This section refers to how to implement caching features in the Umbraco application in a consistent way that will work in both single server environments and load balanced (multi-server) environments. The caching described in this section relates to application caching in the context of a web application only._ + +{% hint style="warning" %} +**Please read this if you are Caching** + +Although caching is a pretty standard concept it is very important to make sure that caching is done correctly and consistently. It is always best to ensure performance is at its best before applying any cache and also beware of _over caching_ as this can cause degraded performance in your application because of cache turnover. + +In normal environments caching seems to be a pretty standard concept. If you are a package developer or developer who is going to publish a codebase to a load balanced environment then you need to be aware of how to invalidate your cache properly, so that it works in load balanced environments. If it is not done correctly then your package and/or codebase will not work the way that you would expect in a load balanced scenario. + +**If you are caching business logic data that changes based on a user's action in the backoffice and you are not using an \_ICacheRefresher**\_\*\* then you will need to review your code and update it based on the below documentation.\*\* +{% endhint %} + +## Retrieving and Adding items in the cache + +You can [update and insert items in the cache](updating-cache.md). + +## Refreshing/Invalidating cache + +### [ICacheRefresher](./#icacherefresher) + +The standard way to invalidate cache in Umbraco is to implement an `ICacheRefresher`. + +The interface consists of the following methods: + +* `Guid RefresherUniqueId { get; }` + * Which you'd return your own unique GUID identifier +* `string Name { get; }` + * The name of the cache refresher (informational purposes) +* `void RefreshAll();` + * This would invalidate or refresh all caches of the caching type that this `ICacheRefresher` is created for. For example, if you were caching `Employee` objects, this method would invalidate all `Employee` caches. +* `void Refresh(int Id);` + * This would invalidate or refresh a single cache for an object with the provided `int` id. +* `void Refresh(Guid Id);` + * This would invalidate or refresh a single cache for an object with the provided GUID id. +* `void Remove(int Id);` + * This would invalidate a single cache for an object with the provided `int` id. In many cases Remove and Refresh perform the same operation but in some cases `Refresh` doesn't remove/invalidate a cache entry, it might update it. `Remove` is specifically used to remove/invalidate a cache entry. + +_Some of these methods may not be relevant to the needs of your own cache invalidation so not all of them may need to perform logic._ + +There are 2 other base types of `ICacheRefresher` which are: + +* `ICacheRefresher` - this inherits from `ICacheRefresher` and provides a set of strongly typed methods for cache invalidation. This is useful when executing the method to invoke the cache refresher, when you have the instance of the object already since this avoids the overhead of retrieving the object again. + * `void Refresh(T instance);` - this would invalidate/refresh a single cache for the specified object. + * `void Remove(T instance);` - this would invalidate a single cache for the specified object. +* `IJsonCacheRefresher` - this inherits from `ICacheRefresher` but provides more flexibility if you need to invalidate cache based on more complex scenarios (e.g. the [MemberGroupCacheRefresher](https://github.com/umbraco/Umbraco-CMS/blob/v9/contrib/src/Umbraco.Core/Cache/MemberGroupCacheRefresher.cs)). + * `void Refresh(string jsonPayload)` - Invalidates/refreshes any cache based on the information provided in the JSON. The JSON value is any value that is used when executing the method to invoke the cache refresher. + +There are several examples of `ICacheRefresher`'s in the core: https://github.com/umbraco/Umbraco-CMS/tree/v9/dev/src/Umbraco.Core/Cache + +### Executing an ICacheRefresher + +To execute your `ICacheRefresher` you call these methods on the `DistributedCache` instance (the `DistributedCache` object exists in the `Umbraco.Cms.Core.Cache` namespace): + +* `void Refresh(Guid cacheRefresherId, Func getNumericId, params T[] instances)` + * This executes an `ICacheRefresher.Refresh(T instance)` of the specified cache refresher Id +* `void Refresh(Guid cacheRefresherId, int id)` + * This executes an `ICacheRefresher.Refresh(int id)` of the specified cache refresher Id +* `void Refresh(Guid cacheRefresherId, Guid id)` + * This executes an `ICacheRefresher.Refresh(Guid id)` of the specified cache refresher Id +* `void Remove(Guid refresherGuid, int id)` + * This executes an `ICacheRefresher.Remove(int id)` of the specified cache refresher Id +* `void Remove(Guid refresherGuid, Func getNumericId, params T[] instances)` + * This executes an `ICacheRefresher.Remove(T instance)` of the specified cache refresher Id +* `void RefreshAll(Guid refresherGuid)` + * This executes an `ICacheRefresher.RefreshAll()` of the specified cache refresher Id + +**So when do you use these methods to invalidate your cache?** + +This really comes down to what you are caching and when it needs to be invalidated. + +### What happens when an ICacheRefresher is executed? + +When an `ICacheRefresher` is executed via the `DistributedCache` a notification is sent out to all servers that are hosting your web application to execute the specified cache refresher. When not load balancing, this means that the single server hosting your web application executes the `ICacheRefresher` directly. However, when load balancing, this means that Umbraco will ensure that each server hosting your web application executes the `ICacheRefresher` so that each server's cache stays in sync. + +## Events handling to refresh cache + +To use the extensions add a `using` to `Umbraco.Extensions`; You can then invoke them on the injected `DistributedCache` object. + +## IServerMessenger + +The server messenger broadcasts 'distributed cache notifications' to each server in the load-balanced environment. The server messenger ensures that the notification is processed in the local environment. + +## Getting and clearing cached content + +[See our example on how to cache tags](examples/tags.md). + +## [ApplicationCache](application-cache.md) diff --git a/16/umbraco-cms/reference/cache/application-cache.md b/16/umbraco-cms/reference/cache/application-cache.md new file mode 100644 index 00000000000..0a3dae1dff5 --- /dev/null +++ b/16/umbraco-cms/reference/cache/application-cache.md @@ -0,0 +1,44 @@ +# Accessing the cache + +You should always be doing this consistently with the best practices listed below. You shouldn't be using HttpRuntime.Cache or HttpContext.Current.Cache directly. Instead, you should always be accessing it via the AppCaches cache helper (`Umbraco.Cms.Core.Cache`). + +## Cache types + +The `AppCaches` which can be found in namespace `Umbraco.Cms.Core.Cache` contains types of cache: Runtime Cache, Request Cache and Isolated Caches. + +**Runtime Cache** is the most commonly used and is synonymous with HttpRuntime.Cache. +**Request cache** is cache that exists only for the current request. This is synonymous with HttpContext.Current.Items and **isolated caches**. These are used by for example repositories, to ensure that each cached entity type has its own cache. When they have their own cache, lookups are fast and the repository does not need to search through all keys on a global scale. + +## Getting the AppCaches + +If you wish to use the AppCaches in a class, you need to use Dependency Injection (DI) in your constructor: + +```csharp +public class MyClass +{ + private readonly IRelationService _relationService; + + private readonly IAppPolicyCache _runtimeCache; + private readonly IAppCache _requestCache; + private readonly IsolatedCaches _isolatedCaches; + + public MyClass(AppCaches appCaches, IRelationService relationService) + { + _relationService = relationService; + _runtimeCache = appCaches.RuntimeCache; + _requestCache = appCaches.RequestCache; + _isolatedCaches = appCaches.IsolatedCaches; + } + + // One example would be to get relations based on a node id. The RelationService hits the database each time and is not something you should call fx from a view that could get hit many times. + // To get around that limitation you can wrap it in the cache so it only has to retrieve the value from the db once every minute (or whatever you set the timespan to). + public void DocsService(int nodeId) + { + // Gets child relations from the cache if it exists, otherwise gets them and caches them for 1 min. + var relations = _runtimeCache.GetCacheItem( + $"ChildRelations_{nodeId}", + () => _relationService.GetByChildId(nodeId, "umbDocument"), + TimeSpan.FromMinutes(1)); + } +} +``` diff --git a/16/umbraco-cms/reference/cache/cache-seeding.md b/16/umbraco-cms/reference/cache/cache-seeding.md new file mode 100644 index 00000000000..e9b5d6af900 --- /dev/null +++ b/16/umbraco-cms/reference/cache/cache-seeding.md @@ -0,0 +1,42 @@ +--- +description: Information about cache seeding +--- + +# Cache Seeding + +Umbraco uses a lazy loaded cache, meaning content is loaded into the cache on an as-needed basis. Whenever a piece of content is shown on the website for the first time it first needs to be loaded into the cache. + +Loading the content into the cache causes a delay. This delay is dependent on the latency between your server and your database, but is generally minimal. +For certain pages, like the front page, you may not want this delay to be there. The role of cache seeding is meant to solve this issue. + +## How it works + +Cache seeding is based on the concept of an `ISeedKeyProvider`. The role of the seed key provider is to specify what keys need to be seeded. + +There are two types of seed key providers: an `IDocumentSeedKeyProvider` specifying which document should be seeded, and an `IMediaSeedKeyProvider` specifying which media should be seeded. + +During startup, all the `ISeedKeyProviders` are run, and the keys they return are seeded into their respective caches, `IPublishedContentCache` for documents, and `IPublishedMediaCache` for media. Additionally, whenever a document or media is changed, the cache will immediately be updated with the changed content. This ensures that the content is always present in the cache. + +Whenever a piece of content is changed, the seeded keys must be checked, to see if the updated content was seeded. Because of the need the check all seeded keys, Umbraco caches the keys themselves during startup. This means that if you have a dynamic seed key provider, any newly added content will not be considered seeded until the server restarts. For instance, when seeding by Document Type any new content using the specified Document Type will not be seeded until the server is restarted. + +## Seed key providers + +### Default implementations + +By default, Umbraco ships with two seed key providers for documents, and one for media. + +For documents, the `ContentTypeSeedKeyProvider` seeds all documents of the given Document Types specified in the `appSettings.json` file. + +For documents and media, the `BreadthFirstKeyProvider` does a breadth-first traversal of the content and media tree respectively. This will seed N number of content specified in the `appSettings.json` file. + +The default seed key provider configuration can be found in the [cache settings section.](../configuration/cache-settings.md). + +### Custom seed key providers + +It is also possible to implement custom seed key providers. These are run alongside the default seed key providers on startup. + +The returned keys of all the seed key providers are unioned into a single set. This means there will be no duplicates. + +As mentioned above the provided keys are cached. Only the keys returned at startup will be considered seeded until the server restarts and the provider is rerun. + +For a specific example of implementing a custom seed key provider, see [Creating a Custom Seed Key Provider](../../extending/creating-custom-seed-key-provider.md). diff --git a/16/umbraco-cms/reference/cache/examples/README.md b/16/umbraco-cms/reference/cache/examples/README.md new file mode 100644 index 00000000000..c77fc7b4433 --- /dev/null +++ b/16/umbraco-cms/reference/cache/examples/README.md @@ -0,0 +1,3 @@ +# Examples + +- [Setting up caching on the tags property](tags.md). diff --git a/16/umbraco-cms/reference/cache/examples/images/response-2.PNG b/16/umbraco-cms/reference/cache/examples/images/response-2.PNG new file mode 100644 index 00000000000..812fc3ad56c Binary files /dev/null and b/16/umbraco-cms/reference/cache/examples/images/response-2.PNG differ diff --git a/16/umbraco-cms/reference/cache/examples/images/response.PNG b/16/umbraco-cms/reference/cache/examples/images/response.PNG new file mode 100644 index 00000000000..147915c206e Binary files /dev/null and b/16/umbraco-cms/reference/cache/examples/images/response.PNG differ diff --git a/16/umbraco-cms/reference/cache/examples/tags.md b/16/umbraco-cms/reference/cache/examples/tags.md new file mode 100644 index 00000000000..f8b72d07275 --- /dev/null +++ b/16/umbraco-cms/reference/cache/examples/tags.md @@ -0,0 +1,207 @@ +--- +description: "Information on how to insert and delete from the runtime cache" +--- + +# Working with caching + +This article will show you how to insert and delete from the runtime cache. + +## Scenario + +For this example we're working with tags. On my site I have two tag properties: + +1) One on every page using the tag group `default` + +2) One on my blog posts using the tag group `blog` + +We're going to expose an endpoint that allows us to get the tags from each group. + +The tags from the `default` should be cached for a minute. The `blog` tags will be cached until site restart or if you publish a blog post node in the Backoffice. + +## Example + +Why work with tags? Because they're not cached by default.. which makes them ideal for demo purposes :) + +### TagService + +First we want to create our `CacheTagService`. In this example it's a basic class with one method (`GetAll`) that wraps Umbraco's `TagQuery.GetAllTags()`. + +```csharp +using System; +using System.Collections.Generic; +using Umbraco.Cms.Core.Cache; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.PublishedCache; +using Umbraco.Extensions; + +namespace Doccers.Core.Services.Implement; + +public class CacheTagService : ICacheTagService +{ + private readonly ITagQuery _tagQuery; + private readonly IAppPolicyCache _runtimeCache; + + public CacheTagService(ITagQuery tagQuery, AppCaches appCaches) + { + _tagQuery = tagQuery; + // Get the RuntimeCache from appCaches + // and assign to our private field. + _runtimeCache = appCaches.RuntimeCache; + } + + public IEnumerable GetAll( + string group, + string cacheKey, + TimeSpan? timeout = null) + { + // GetCacheItem will automatically insert the object + // into cache if it doesn't exist. + return _runtimeCache.GetCacheItem(cacheKey, () => + { + return _tagQuery.GetAllTags(group); + }, timeout); + } +} +``` + +As you can see we inherit from the `ICacheTagService` interface. All that has is: + +```csharp +using System; +using System.Collections.Generic; +using Umbraco.Cms.Core.Models; + +namespace Doccers.Core.Services; + +public interface ICacheTagService +{ + IEnumerable GetAll( + string group, + string cacheKey, + TimeSpan? timeout = null); +} +``` + +The interface was created to better register it so we can use dependency injection. You can register your own classes like so: + +```csharp +using Doccers.Core.Services; +using Doccers.Core.Services.Implement; +using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Core.DependencyInjection; +using Microsoft.Extensions.DependencyInjection; + +namespace Doccers.Core; + +public class Composer : IComposer +{ + public void Compose(IUmbracoBuilder builder) + { + builder.Services.AddScoped(); + } +} +``` + +Now you can inject `ICacheTagService` in any constructor in your project - wohooo! + +### API + +Now that we have our service it's time to create an endpoint where we can fetch the (cached) tags. + +```csharp +using System; +using System.Collections.Generic; +using Microsoft.AspNetCore.Mvc; +using Doccers.Core.Services; +using Umbraco.Cms.Core.Models; + +namespace Doccers.Core.Controllers.Api; + +[ApiController] +[Route("/umbraco/api/tags")] +public class TagsController : Controller +{ + private readonly ICacheTagService _cacheTagService; + + // Dependency injection rocks! + public TagsController(ICacheTagService cacheTagService) + { + _cacheTagService = cacheTagService; + } + + [HttpGet("getdefaulttags")] + public IEnumerable GetDefaultTags() + { + // As mentioned earlier we want tags from "default" + // group to be cached for a minute. + return _cacheTagService.GetAll("default", "defaultTags", + TimeSpan.FromMinutes(1)); + } + + [HttpGet("getblogtags")] + public IEnumerable GetBlogTags() + { + // If you don't specify a TimeSpan the object(s) + // will be cached until manually removed or + // if the site restarts. + return _cacheTagService.GetAll("blog", "blogTags"); + } +} +``` + +`/umbraco/api/tags/getblogtags` + +![Result](images/response.png) + +`/umbraco/api/tags/getdefaulttags` + +![Result](images/response-2.png) + +Everything should now work as expected when it comes to getting tags. However, if I go to my Backoffice and add a new tag to the `blog` group the changes aren't shown on the endpoint. Let's fix that. + +### Clearing cache on publish + +To clear the cache we need a notification handler in which we register to the `ContentPublishedNotification` event on the `ContentService`. This allows us to run a piece of code whenever you publish a node. + +```csharp +using System.Linq; +using Umbraco.Cms.Core.Cache; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Notifications; + +namespace Doccers.Core; + +public class Notification : INotificationHandler +{ + + private readonly IAppPolicyCache _runtimeCache; + + public Notification(AppCaches appCaches) + { + _runtimeCache = appCaches.RuntimeCache; + } + + public void Handle(ContentPublishedNotification notification) + { + + if (notification.PublishedEntities.Any(x => x.ContentType.Alias == "blogPost")) + { + _runtimeCache.ClearByKey("blogTags"); + } + } +} +``` + +Now that we have our notification we also need to register it. Add `builder.AddNotificationHandler();` to the `Compose` method in the `Composer` class so it becomes: + +```csharp +public void Compose(IUmbracoBuilder builder) +{ + builder.Services.AddScoped(); + + builder.AddNotificationHandler(); + +} +``` + +Awesome! Now we have set up caching on our tags - making the site a bit faster. diff --git a/16/umbraco-cms/reference/cache/icacherefresher.md b/16/umbraco-cms/reference/cache/icacherefresher.md new file mode 100644 index 00000000000..d961b855023 --- /dev/null +++ b/16/umbraco-cms/reference/cache/icacherefresher.md @@ -0,0 +1,11 @@ +# ICacheRefresher + +_This section describes what ICacheRefresher and ICacheRefresher<T> are and how to use them to invalidate your cache correctly including load balanced environments_ + +## What is an ICacheRefresher + +This interface has been in the Umbraco core for a significant period. However, it has really only been used to ensure that content cache is refreshed among all server nodes participating in a load balanced scenario. + +An `ICacheRefresher` is the primary method that invalidates *any* cache needing refreshment or removal. This applies regardless of a load balanced environment. + +There are now a few different types of `ICacheRefreshers` in the Umbraco core. It is important to understand the differences between them and how cache invalidation works across multiple server nodes. diff --git a/16/umbraco-cms/reference/cache/iservermessenger.md b/16/umbraco-cms/reference/cache/iservermessenger.md new file mode 100644 index 00000000000..8d53f467143 --- /dev/null +++ b/16/umbraco-cms/reference/cache/iservermessenger.md @@ -0,0 +1,12 @@ +# IServerMessenger + +Broadcasts distributed cache notifications to all servers of a load balanced environment. +Also ensures that the notification is processed on the local environment. + +For a specified [ICacheRefresher](icacherefresher.md), the implemented methods will: + +* Notify the distributed cache +* Invalidate specified items +* Notify all servers of specified items removal +* Notify all servers of invalidation of a object +* Notify all servers of a global invalidation (clear the complete cache) diff --git a/16/umbraco-cms/reference/cache/updating-cache.md b/16/umbraco-cms/reference/cache/updating-cache.md new file mode 100644 index 00000000000..62c2de3b6b1 --- /dev/null +++ b/16/umbraco-cms/reference/cache/updating-cache.md @@ -0,0 +1,41 @@ +# Updating Cache + +_This section describes how you should be getting/adding/updating/inserting items in the cache._ + +## Adding and retrieving items in the cache + +The recommended way to put data in and get data out is to use one of the many overloaded methods of: `GetCacheItem`. The `GetCacheItem` methods (all except one) are designed to "Get or Add" to the cache. The following retrieves an item from the cache and adds it if it doesn't already exist: + +```csharp +MyObject cachedItem = _appCaches.RuntimeCache.GetCacheItem("MyCacheKey", () => new MyObject()); +``` + +where `_appCaches` is injected as type `AppCaches`. + +Notice 2 things: + +* The `GetCacheItem` method is strongly typed and +* We are supplying a callback method which is used to populate the cache if it doesn't exist. + +The example above will retrieve a strongly typed object of `MyObject` from the cache with the key of "MyCacheKey". If the object doesn't exist in the cache, a new instance of MyObject `MyObject` be added to it with the same key. + +There are a couple of overloads of `GetCacheItem` allowing you to customize how your object is cached from cache dependencies to expiration times. + +To use this generic implementation, add the `Umbraco.Extensions` namespace to your code. + +### Retrieving an item from the cache without a callback + +One of the overloads of `GetCacheItem` doesn't specify a callback. This allows you to retrieve an item from the cache without populating it if it doesn't exist. + +An example of usage: + +```csharp +MyObject cachedItem = _appCaches.RuntimeCache.GetCacheItem("MyCacheKey"); +``` +where `_appCaches` is injected as type `AppCaches`. + +### Inserting an item into the cache without retrieval + +Sometimes you might want to put something in the cache without retrieving it. +In this case there is an `InsertCacheItem` method. +This method will add or update the cache item specified by the key. If the item already exists in the cache, it will be replaced. diff --git a/16/umbraco-cms/reference/common-pitfalls.md b/16/umbraco-cms/reference/common-pitfalls.md new file mode 100644 index 00000000000..cf2999a8b42 --- /dev/null +++ b/16/umbraco-cms/reference/common-pitfalls.md @@ -0,0 +1,381 @@ +--- +description: Information on common Pitfalls and Anti-Patterns in Umbraco +--- + +# Common Pitfalls & Anti-Patterns + +This section highlights common pitfalls that developers often encounter. Some of the anti-patterns discussed here can lead to memory leaks, instability, or poor performance on your site. Reading this section could save your site. + +## Usage of Singletons and Statics + +Generally speaking, if you are writing software these days you should be using Dependency Injection (DI) principles. If you do this, you probably are not using [Singletons](https://en.wikipedia.org/wiki/Singleton\_pattern) or [Statics](https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/static-classes-and-static-class-members), and for the most part you should not be. + +Since Umbraco comes with dependency injection out of the box, there really is no reason to use singletons or statics. It makes your code difficult to test and hard to manage. Furthermore, the APIs become leaky and you will end up with more problems than when you started. + +Dependency injection is available everywhere, and you can register your own services as well. Additionally, some resources are available through properties on certain base classes. For example, all Razor views that Umbraco creates expose an `UmbracoHelper` property you can access through `@Umbraco`. The other base classes expose some things you might need like `UmbracoContext`, and things like `SurfaceController`. Even here the services are initially obtained through DI, and you can inject further Umbraco and custom services that you might need. + +For more information about consuming and registering your own dependencies have a look at the [Dependency Injection](using-ioc.md) documentation. + +### Example of using base class properties gotten through DI + +```csharp +public class ContactFormSurfaceController : SurfaceController +{ + // The services are injected with DI and passed to the parent class + public ContactFormSurfaceController( + IUmbracoContextAccessor umbracoContextAccessor, + IUmbracoDatabaseFactory databaseFactory, + ServiceContext services, + AppCaches appCaches, + IProfilingLogger profilingLogger, + IPublishedUrlProvider publishedUrlProvider) + : base(umbracoContextAccessor, databaseFactory, services, appCaches, profilingLogger, publishedUrlProvider) + { + } + + [HttpPost] + public IActionResult SubmitForm(ContactFormModel model) + { + // All normal form processing logic is left out of this example for brevity. + // You can access all of these properties because they are properties of the base class. + // If you need something else you can inject it in the constructor. + + //Profiling logger + using (ProfilingLogger.TraceDuration("Start", "stop")) + { + // UmbracoContext + UmbracoContext.Content.GetById(1234); + } + + return Ok(); + } +} +``` + +## Static references to scoped instances such as `UmbracoHelper` + +### Example 1 + +```csharp +public class BadApiController : Controller +{ + // Note that this is static, that's bad + private static UmbracoHelper _umbracoHelper; + + public BadApiController(IUmbracoHelperAccessor umbracoHelperAccessor) + { + // Don't do this, this is bad + if (_umbracoHelper is null) + { + umbracoHelperAccessor.TryGetUmbracoHelper(out UmbracoHelper umbracoHelper); + _umbracoHelper = umbracoHelper; + } + } +} +``` + +This practice can cause memory leaks along with inconsistent data results when using this `_umbracoHelper` instance. + +It is important to understand the difference between an object with Request-based scope and Singleton/Application-based scope. + +* **Application scope**: If an object has a singleton/application scope, that single object instance will exist for the lifetime of the application. The single instance will be shared by every thread that accesses it. Static variables will always exist for the lifespan of the application. +* **Request scope**: The web world is made up of requests and each request has its own thread. When an object is in the scope of a Request it only survives as long as the web request survives. At the end of the web request, the object may either be disposed of or cleared from memory by the garbage collector. Request scoped object instances are not accessed by every other thread in the application unless you do something like the above. + +An example of a request-scoped instance is the `HttpContext`. This object exists for a single request and it cannot be shared between other threads, especially not other request threads. This is because the object's thread is where the security information for a given user is handled. The `UmbracoContext` is also a request-scoped object. In fact, it relies directly on an instance of `HttpContext`. The `UmbracoHelper` is request-scoped as well. + +In the example above, the `UmbracoHelper`, which has a request-scoped lifetime, will be statically assigned to a variable. This request-scoped object is now bound to an Application-scope lifetime and will exist after the request has ended. This could mean that under certain circumstances an entire Umbraco cache copy is stuck in memory. It could also mean that the `Security` property of the context will be accessed by multiple threads. These threads may now contain the security information for a user from another request. + +Additionally there is never really any reason to use static references. Instead, you should always inject your required resources, and let the DI container handle the lifetimes of the objects. + +## Querying with Descendants using DescendantsOrSelf + +When using queries like this, you need to understand the implications. Here is a particularly bad scenario: + +You have 10,000 content items in your tree and your tree structure is something like this: + +```none +- Root +-- Home +-- Blog (Collection with 9495 nodes) +-- Office Locations (Collection with 500 nodes) +-- About Us +-- Contact Us +``` + +You create a menu on your Home page like: + +```csharp + +``` + +The query above renders out: _Root, Home, Blog, Office Locations, About Us, Contact Us_ + +This is going to iterate over every single node in Umbraco, all 10,000 of them. This will have a negative effect on the site's general performance. + +Instead of using the snippet above, something similar to the snippet below can be used: + +```csharp + +``` + +In many cases, you might know that there is only ever going to be a small number of Descendants. If so, using Descendants or DescendantsOrSelf will not have a negative effect on the site's performance. It is important to always be aware of the implications of what you are writing. + +## Too much querying ("Over querying") + +Querying and traversing content is not free. Anytime you make a query or resolve a property value there is overhead involved. Think about every query you make as an SQL call; too many requests can have a negative effect on the site's performance. + +Here is a common pitfall in relation to this: + +Following the example above, the menu is going to be rendered using the current page's root node: + +```csharp + +``` + +The `@Model.Root()` syntax is shorthand for doing this: `Model.AncestorOrSelf(1)`. This will traverse up the tree until it reaches an ancestor node with a level of one. As mentioned above, traversing costs resources and in this example, there are 3x traversals being done for the same value. + +Consider writing something similar to the example below: + +```csharp +@{ + var root = Model.Root(); +} + +``` + +## Using the Services layer in your views + +The Services layer of Umbraco is for manipulating the business logic of Umbraco directly to/from the database. None of these methods should be used within your views and can have a negative impact on the performance and stability of your application. + +Your views should rely only on the read-only data services such as `UmbracoHelper`, `ITagQuery` and `IMemberManager` and the properties and methods they expose. This ensures that the data being queried comes from the cache and that you are not inadvertently making database changes. + +For example, when retrieving a content item in your views: + +```csharp +@using Umbraco.Cms.Core.Services +@inject IContentService _contentService + +@{ + // Services access in your views :( + var dontDoThis = _contentService.GetById(1234); + + // Content cache access in your views + var doThis = Umbraco.Content(1234); +} +``` + +If you are using services in your views, you should figure out why this is being done and, in most cases, remove this logic. + +## Using Umbraco content items for volatile data + +This is one of the anti-patterns that could have the most negative impact on your site's performance. + +Umbraco content should not be used for volatile data. The Umbraco APIs, and the way Umbraco data is persisted, was not designed for this. When you need to store, write or track data that changes a lot, use a custom database table or another service. Do not use Umbraco content nodes for this. + +Some examples of what not to do, and what to do instead: + +|What not to do|Alternative| +| - | - | +|Hit counters to track the number of times your page has been viewed.|Use something like Google Analytics or a custom database table instead.| +|Creating new nodes for form submissions.|This should be stored in a custom database table.| +|Importing lots of data into Umbraco content nodes.|Import the data into custom database tables instead.| + +## Processing during startup + +Umbraco allows you to run some initialization code during startup by using `UmbracoApplicationStartingNotification`. This code can have a negative impact on the application startup process. This is especially true for Package developers as your code could end up impacting many websites. + +In many cases, [initialization code can be done lazily instead of eagerly](https://msdn.microsoft.com/en-us/library/dd997286\(v=vs.110\).aspx). Instead of initialization everything you need as soon as the application starts, you could execute your initialization code only when it is required. This can be achieved in different ways, such as: + +* Using [`Lazy`](https://msdn.microsoft.com/en-us/library/dd642331\(v=vs.110\).aspx) and putting the initialization logic in its callback. +* Using [`LazyInitializer`](https://msdn.microsoft.com/en-us/library/system.threading.lazyinitializer\(v=vs.110\).aspx?f=255\&MSPPError=-2147217396). +* Putting logic in a property getter with a lock and setting a flag when it is processed. +* Putting logic in a method with a lock and setting a flag when it is processed. + +It is important to ensure that the initialization logic executes only once for the lifetime of the application, even when your app domain is restarted. If your initialization logic creates a database table that should only be executed one time, set a persistence flag. A persistence flag will indicate to your own logic that the initialization code has already been executed and does not need to be done again. + +## Rebuilding indexes + +Rebuilding examine indexes can have a negative effect of the sites performance and is not a recommended practice. It is recommeded to ensure you are running the latest Umbraco and Examine versions if you are having trouble with out-of-sync index data. + +The primary reasons your data will become out of sync are: + +* Old version of Umbraco. +* Rebuilding indexes and restarting your app domain at the same time. + +It is not recommended to rebuild your indexes unless you absolutely need to. If you need to do this often then it is advised to determine why and to try to resolve the underlying problem. + +## Performing lookups and logic in Examine events + +There are a couple of well-known Examine events: `TransformingIndexValues` and `DocumentWriting`. Both of these events allow the developer to modify the data that is going into the Lucene index. We often see developers performing service lookups in these methods. For example, using `IContentService.GetById(e.NodeId)` inside of these events could cause an `N + 1` problem. This is because these events are executed for every single document being indexed. If you are rebuilding an index, this will mean that this logic will fire for every single document and media item going into each index. That could mean a large number of lookups, which can negatively impact on the site's performance. + +Similarly, if you are executing inefficient logic in these events, anytime you save or publish content or media that logic will slow the process down. If you rebuild an index, any slow code running in these events will cause the indexing to go even slower. + +## RenderTemplateAsync + +The API method called `RenderTemplateAsync` allows you to render a particular content item's template and get a `IHtmlEncodedString` in response. This could be useful if you want to send an email based on a content item and its template. However, you must be careful not to use this for purposes it is not meant to be used for. + +Do not use this method for rendering content as this could cause severe performance problems. For you are rendering normal content of module type data from another content item, you should use Partial Views instead. + +## Do not put logic inside your constructors + +Constructors should generally not perform any logic. They should set parameter values, perform null checks and perhaps validate data. + +There are a few reasons why this can become a performance problem: + +* The consumer of an API does not expect that by creating an object they should be worried about performance. +* Creating an object can inadvertently happen many times, especially when using Language Integrated Query (LINQ). + +Here is an example of how this can go wrong. + +Your tree structure is something like this: + +```none +- Root +-- Home +--- Recipes (node id = 3251, Collection with 5000 nodes) +--- About Us +--- Contact Us +``` + +You have a custom model that looks like this: + +```csharp +public class RecipeModel : PublishedContentWrapped +{ + public RecipeModel(IPublishedContent content, IPublishedValueFallback publishedValueFallback) : base(content, publishedValueFallback) + { + RelatedRecipes = content + .Parent + .Children() + .Where(x => x.Value>("related") + .Contains(content.Id)); + + Votes = content.Value("votes"); + } + + public int Votes { get; private set; } + + public IEnumerable RelatedRecipes { get; private set; } +} +} +``` + +You run the following code to show the favorites: + +```csharp +@var recipeNode = Umbraco.TypedContent(3251); +@{ + var recipeNode = Umbraco.Content(1234); +} + +
    + @foreach (var recipe in recipeNode.Children + .Select(x => new RecipeModel(x, _publishedValueFallback)) + .OrderByDescending(x => x.Votes) + .Take(10)) + { +
  • @recipe.Name
  • + } +
+``` + +To show the top 10 voted recipes, this code will end up doing the following: + +* Iterate over all 5000 Recipes. +* Create and allocate 5000 instances of `RecipeModel`. +* For each `RecipeModel` created, it will traverse upwards, iterate all 5000 recipes then resolve property data for 2 properties. + +This means that there is now an additional 5,000 new objects created and allocated in memory. The number of traversals/visits to each of these objects is now `5000 x 5000 = 25,000,000`. + +The other problem is that the logic used to lookup related recipes is inefficient. Instead, each recipe should have a picker to choose its related recipes, and then each of those can be looked up by their ID. + +## Do not eager load data, lazy load it instead + +The above example could be rewritten like this: + +```csharp + public class RecipeModel : PublishedContentWrapped + { + public RecipeModel(IPublishedContent content, IPublishedValueFallback publishedValueFallback) : base(content, publishedValueFallback) + {} + + private int? _votes; + public int Votes + { + get + { + // Lazy load the property value and ensure it's not re-resolved once it's loaded + return _votes ??= this.Value("votes"); + } + } + + // Just return the Ids, they can be resolved to IPublishedContent instances in the view or elsewhere, + // doesn't need to be in the model - this would also be bad if the model was cached since all of the + // related entities would end up in the cache too. + private List _related; + + public IEnumerable RelatedRecipes => _related ??= this.Value>("related").ToList(); + } +``` + +The code will still iterate over all Recipes meaning that the number of traversals/visits to each of these objects will be 5000. + +There really is not much reason to create a `RecipeModel`. Instead, it could be written like: + +```csharp +@{ + var recipeNode = Umbraco.Content(1234); +} + +
    + @foreach (var recipe in recipeNode.Children + .OrderByDescending(x => x.Value("votes")) + .Take(10)) + { +
  • @recipe.Name
  • + } +
+``` + +## Not caching expensive lookups + +Based on the above two points, you can see that iterating content with the traversal APIs ends up being expensive in terms of performance. + +How to solve performance issues will always depend on the specific scenario. One thing to consider is to cache the IDs of the content you need in your critical code. Then you could retrieve the content from the cache by ID. + +When you need to render the same four pieces of content for your navigation, we recommend caching, or hardcoding, the IDs of those content items. You can retrieve the content from their IDs using `Umbraco.Content`. This will always be faster than trying to traverse your content tree and finding the content programmatically. It will do a direct lookup in the cache, meaning that your code does not have to do many traversals to get your content. + +## Be mindful about memory + +When memory is used, for instance creating 5,000 recipe models with a `Select` statement, [Garbage Collection](https://learn.microsoft.com/en-us/dotnet/standard/garbage-collection/) needs to occur. This turnover can cause performance problems. The more objects created, the more items allocated in memory, the harder the job is for the Garbage Collector, resulting in more performance problems. + +Even worse is when you allocate a lot of large items in memory. These items will remain in memory for a long time, ending up in "[Generation 3](https://learn.microsoft.com/en-us/dotnet/standard/garbage-collection/fundamentals#generations)" which the Garbage Collector ignores for as long as possible. It does so because it knows it is going to take a lot of resources to clean up. + +## Best practices when using Models Builder + +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. +You can read more about this in the [Understanding and Extending Models Builder documentation](templating/modelsbuilder/understand-and-extend.md) diff --git a/16/umbraco-cms/reference/configuration/README.md b/16/umbraco-cms/reference/configuration/README.md new file mode 100644 index 00000000000..1a2519913a2 --- /dev/null +++ b/16/umbraco-cms/reference/configuration/README.md @@ -0,0 +1,146 @@ +--- +description: Information on configuring Umbraco +--- + +# Configuration + +Umbraco uses the .NET built-in configuration pattern. This means that the configuration is handled in the `appsettings.json` file and primarily done using `IConfiguration` with diffent sources. + +For more in depth information on the configuration pattern see Microsofts [Configuration in ASP.NET Core](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/?view=aspnetcore-6.0) article. + +{% hint style="info" %} +**Are you looking for the RuntimeMinificationSettings?** + +Smidge, which held the RuntimeMinificationSettings configuration, was removed with the release of Umbraco 14. + +You can install the Smidge package separately if needed. Learn more and see how to get started in [the official Smidge documentation](https://github.com/Shazwazza/Smidge). +{% endhint %} + +## Managing Configuration + +You might not always want to have the configuration stored in the `appsettings.json` file, for instance, you might not want to have the admin password in the file if using the unattended feature. You might also want to use a specific set of configurations when developing your solution. To achieve this, the `IConfiguration` pattern can be used for this. + +With the configuration pattern the settings can be read from multiple different source, where some take precedence over other, you can configure you site with: + +1. The `appsettings.json` file +2. An `appsettings.{environment}.json` file +3. UserSecrets (Only when in development) +4. Environment variables +5. Command line arguments + +This list is in order of precedence, so the values from `appsettings.json` will only be used if they're not also defined in the environment variables. If they are, then the environment variable will be used instead. + +There is one caveat, to this precedence though, the `appsettings.{environment}.json` file will only be used if the current environment matches the name of the config file, for instance, the `appsettings.Development.json` file will only be used when the environment is set to development. + +### Using Environment Variables for Configuration + +It is not feasible to have an entire json file as an environment variable, and the `:` doesn't work with environment variables on all platforms, so instead a double underscore is used to create the hierachy. + +As an example, if you want to set your unattended username, you would normally write it in the `appsettings.json` like so: + +```json +"Umbraco": { + "CMS": { + "Unattended": { + "UnattendedUserName": "A.N. Other" + } + } +} +``` + +As an environment variable it becomes a variable with the name `Umbraco__CMS__Unattended__UnattendedUserName` and a value of `A.N. Other`. + +### Using Command Line Arguments Configuration + +Like with environment variables, it's not feasible to use an entire JSON file as a command line argument. However, with the command line the `:` will work without issues, so each section of the hierarchy is separated with a `:` character. If we use the same example as above, you can achieve the same result by using the following when starting the site via the command line: + +`dotnet run Umbraco:CMS:Unattended:UnattendedUserName="A.N Other"` + +### Using UserSecrets for Configuration + +In the development environment it is possible to use UserSecrets for configuration, which is ideal for connection strings and similar settings that shouldn't be committed to source control. To use UserSecrets you need to first enable them for the project - this is done with the following command, issued within the directory that contains the `.csproj` file: + +`dotnet user-secrets init` + +Now it's possible to store the connection string with this command: + +`dotnet user-secrets set "ConnectionStrings:umbracoDbDSN" "CONNECTION_STRING_IN_HERE"` + +The name of the key is created in the same way as in the [Command Line](./#using-command-line-arguments-configuration) example above, and thus corresponds to this JSON chunk: + +```json +"ConnectionStrings": { + "umbracoDbDSN": "CONNECTION_STRING_IN_HERE" +} +``` + +## IntelliSense + +A great thing about `appsettings.json` is that it allows for intellisense with a schema file. For most editors this should work out of the box, without having to configure anything, since the schema is specified in the top of the file like so: `"$schema": "https://json.schemastore.org/appsettings.json"`. + +## Reading Configuration in Code + +You might need to read the configuration from your code. + +When reading the configuration you need to inject an [`IOptions<>`](https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.options.ioptions-1?view=dotnet-plat-ext-6.0) or [`IOptionsMonitor<>`](https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.options.ioptionsmonitor-1?view=dotnet-plat-ext-6.0) object into the class that needs it. Here is an example of how you would read the `Host` value from the SMTP settings contained within the global settings: + +```csharp +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; + +namespace MySite; + +public class SomeClass +{ + private GlobalSettings _globalSettings; + + public SomeClass(IOptions globalSettings) + { + _globalSettings = globalSettings.Value; + + var smtpHost = _globalSettings.Smtp.Host; + } +} +``` + +First off `using Microsoft.Extensions.Options` is added, to gain access to the `IOptions` type, and `using Umbraco.Cms.Core.Configuration.Models;` is added to get access to the `GlobalSettings` type. + +`IOptions` is then injected into the constructor of the class, where we can use the `Value` property to gain access to the actual settings object. + +Now we have a typed object containing our settings, so we can get the Host value by calling `_globalSettings.Smtp.Host`. + +To see what setting types you can access see the complete list below, each document corresponds to a settings type. + +## Configuration Options + +A complete list of all the configuration sections included in Umbraco, by default, can be seen here along with any keys they contain: + +* [Basic authentication settings](basicauthsettings.md) +* [Cache settings](cache-settings.md) +* [Connection strings settings](connectionstringssettings.md) +* [Content settings](contentsettings.md) +* [Debug settings](debugsettings.md) +* [Examine settings](examinesettings.md) +* [Exception filter settings](exceptionfiltersettings.md) +* [Global settings](globalsettings.md) +* [Health checks settings](healthchecks.md) +* [Hosting settings](hostingsettings.md) +* [Imaging settings](imagingsettings.md) +* [Indexing settings](indexingsettings.md) +* [Install default data setting](installdefaultdatasettings.md) +* [Logging settings](loggingsettings.md) +* [Maximum upload size settings](maximumuploadsizesettings.md) +* [Models builder settings](modelsbuildersettings.md) +* [Package migration settings](packagemigrationsettings.md) +* [Plugins settings](pluginssettings.md) +* [Request handler settings](requesthandlersettings.md) +* [Runtime settings](runtimesettings.md) +* [Security settings](securitysettings.md) +* [Serilog settings](serilog.md) +* [Type finder settings](typefindersettings.md) +* [Unattended settings](unattendedsettings.md) +* [Web routing settings](webroutingsettings.md) + +## Configured by code + +* [FileSystemProviders](filesystemproviders.md) diff --git a/16/umbraco-cms/reference/configuration/basicauthsettings.md b/16/umbraco-cms/reference/configuration/basicauthsettings.md new file mode 100644 index 00000000000..63828b7536e --- /dev/null +++ b/16/umbraco-cms/reference/configuration/basicauthsettings.md @@ -0,0 +1,46 @@ +--- +description: "Information on the basic authentication section" +--- + +# Basic Authentication Settings + +Allows you to configure the basic authentication settings for Umbraco. A basic authentication section fully populated with default values can be seen here: + +```json +"Umbraco": { + "CMS": { + "BasicAuth": { + "AllowedIPs": [], + "Enabled": false, + "RedirectToLoginPage": false, + "SharedSecret": { + "HeaderName": "X-Authentication-Shared-Secret", + "Value": null + } + } + } +} +``` +## AllowedIPs + +This is a comma-separated list of IP addresses you want to limit where the requests can come from. + +## Enabled + +If the value is set to `true`, the basic authentication is enabled. By default, the value is set to false. + +## RedirectToLoginPage + +If the value is set to `true`, instead of showing the basic authentication popup in the browser, the user is redirected to the login page. This is required for external logins to work. By default, the value is set to false. + +## SharedSecret + +A shared secret can be sent using an HTTP header to bypass the basic authentication. This can be valuable for server-to-server communication. + +### HeaderName + +The header name used to compare the shared secret. By default, the value is set to `X-Authentication-Shared-Secret`. + +### Value + +The value of the shared secret. Must be a string longer than 0 characters to be enabled. The default value is `null`. diff --git a/16/umbraco-cms/reference/configuration/cache-settings.md b/16/umbraco-cms/reference/configuration/cache-settings.md new file mode 100644 index 00000000000..ac31431bb44 --- /dev/null +++ b/16/umbraco-cms/reference/configuration/cache-settings.md @@ -0,0 +1,214 @@ +--- +description: Information on the Cache settings section +--- + +# Cache Settings + +{% hint style="info" %} +Are you looking for the **NuCache Settings**? + +While most cache configurations are under the `Umbraco:CMS:Cache` settings node, a few remain under `Umbraco:CMS:NuCache`. [Learn more about this at the bottom of this article](#nucache-settings). +{% endhint %} + +## HybridCacheOptions + +Umbraco's cache is implemented using Microsofts `HybridCache`, which also has its own settings. For more information [see the HybridCache documentation](https://learn.microsoft.com/en-us/aspnet/core/performance/caching/hybrid?view=aspnetcore-9.0#options). + +### MaximumPayLoadBytes + +One `HybridCache` setting of particular interest is the `MaximumPayloadBytes` setting. This setting specifies the maximum size of a cache entry in bytes and replaces the `BTreeBlockSize` setting from NuCache. +The default from Microsoft is 1MB. However, this limit could quickly be reached, especially when using multiple languages or property editors like the block grid. +To avoid this Umbraco overrides the setting to 100MB by default. You can also configure this manually using a composer: + +```csharp +using Microsoft.Extensions.Caching.Hybrid; +using Umbraco.Cms.Core.Composing; + +namespace MySite.Caching; + +public class ConfigureCacheComposer : IComposer +{ + public void Compose(IUmbracoBuilder builder) + { + builder.Services.AddOptions().Configure(x => + { + x.MaximumPayloadBytes = 1024 * 1024 * 10; // 10MB + }); + } +} +``` + +## Seeding settings + +The Seeding settings allow you to specify which content should be seeded into your cache. For more information on cache seeding see the [Cache Seeding.](../cache/cache-seeding.md) article. + +### ContentTypeKeys + +The `ContentTypeKeys` setting specifies which Document Types should be seeded into the cache. The setting is a comma-separated list of Document Type keys. + +```json +"Umbraco": { + "CMS": { + "Cache": { + "ContentTypeKeys": ["e811405e-0190-4d3e-8387-7558521eec81", "419e89fb-8cff-4549-a074-9f8a30687828", "e0d71146-8205-4cf4-8236-f982b392259f"], + } + } +} +``` + +### DocumentBreadthFirstSeedCount + +The `DocumentBreadthFirstSeedCount` setting specifies how many documents should be seeded into the cache when doing a breadth-first traversal. The default value is 100. + +```json +"Umbraco": { + "CMS": { + "Cache": { + "DocumentBreadthFirstSeedCount": 500 + } + } +} +``` + +## MediaBreadthFirstSeedCount + +The `MediaBreadthFirstSeedCount` setting specifies how many media items should be seeded into the cache when doing a breadth-first traversal. The default value is 100. + +```json +"Umbraco": { + "CMS": { + "Cache": { + "MediaBreadthFirstSeedCount": 500 + } + } +} +``` + +## Cache Entry settings + +The Entry settings allow you to specify how long cache entries should be kept. The cache entry settings are identical for documents and media. + +## LocalCacheDuration + +Specifies the duration for which cache entries should be kept in the local memory cache. The default value is 24 hours. + +```json +"Umbraco": { + "CMS": { + "Cache": { + "Entry": { + "Document": { + "LocalCacheDuration": "2.00:00:00" + }, + "Media": { + "LocalCacheDuration": "50.00:00:00" + } + } + } + } +} +``` + +## RemoteCacheDuration + +Specifies the duration that cache entries should be kept in the remote cache, second level cache. This setting is only relevant if a second-level cache is configured. The default value is 1 year. + +```json +"Umbraco": { + "CMS": { + "Cache": { + "Entry": { + "Document": { + "RemoteCacheDuration": "100.00:00:00" + }, + "Media": { + "RemoteCacheDuration": "150.00:00:00" + } + } + } + } +} +``` + +## SeedCacheDuration + +Specifies the duration for which seeded cache entries should be kept in the cache. The default value is 1 year. + +```json +"Umbraco": { + "CMS": { + "Cache": { + "Entry": { + "Document": { + "SeedCacheDuration": "200.00:00:00" + }, + "Media": { + "SeedCacheDuration": "250.00:00:00" + } + } + } + } +} +``` + +## NuCache Settings + +For backward compatibility reasons, certain settings are under the `Umbraco:CMS:NuCache` settings node. + +### UsePagedSqlQuery + +When `UsePagedSqlQuery` is set to `False`, the `Fetch` method is used instead of the `QueryPaged` method for rebuilding the NuCache files. This will increase performance on larger Umbraco websites with a lot of content when rebuilding the NuCache. + +```json +"Umbraco": { + "CMS": { + "NuCache": { + "UsePagedSqlQuery": false + } + } + } + +``` + +### SqlPageSize + +Specifying the `SqlPageSize` will change the size of the paged SQL queries. The default value is 1000. + +```json +"Umbraco": { + "CMS": { + "NuCache": { + "SqlPageSize": 500 + } + } + } +``` + +## NuCacheSerializerType + +The `NuCacheSerializerType` setting allows developers to specify the serialization format for NuCache content. This setting is particularly relevant for projects migrating from older versions of Umbraco that relied on JSON formats. + +To use JSON serialization instead of the default MessagePack: + +### Using 'Program.cs' + +```csharp +builder.Services.Configure(options => +{ + options.NuCacheSerializerType = NuCacheSerializerType.JSON; +}); +``` + +### Using 'appsettings.json' + +```csharp +{ + "Umbraco": { + "CMS": { + "NuCache": { + "NuCacheSerializerType": "JSON" + } + } + } +} +``` diff --git a/16/umbraco-cms/reference/configuration/connectionstringssettings.md b/16/umbraco-cms/reference/configuration/connectionstringssettings.md new file mode 100644 index 00000000000..62070ee48f9 --- /dev/null +++ b/16/umbraco-cms/reference/configuration/connectionstringssettings.md @@ -0,0 +1,37 @@ +--- +description: "Information on the connection strings settings section" +--- + +# Connection strings settings + +The connection strings settings section contains the connection string to the database Umbraco will connect to. This section is similar to what is used by default in .NET Core. The important thing is that the key for the connection string Umbraco will use is `"umbracoDbDSN"`. It is also important to know that this section is outside the `Umbraco.CMS` section, and is therefore in the root of the config. + +The connection strings config can look like this: + +```json +{ + "ConnectionStrings": { + "umbracoDbDSN": "Data Source=|DataDirectory|/Umbraco.sqlite.db;Cache=Private;Foreign Keys=True;Pooling=True", + "umbracoDbDSN_ProviderName": "Microsoft.Data.Sqlite" + } +} +``` + +{% hint style="info" %} +We recommend using private cache for SQLite. You can read more on why shared cache is discouraged in [the official SQLite documentation](https://sqlite.org/sharedcache.html). +{% endhint %} + +The connection string used here is an SQLite connection string, that will connect to a data in the file `Umbraco.sqlite.db` located in `/umbraco/Data` . + +Umbraco currently supports using either a Microsoft SQL Server or a SQLite database. Both of these options will have different connection strings. For more information about the specific connection strings, see: + +* [SQL Server 2019 connection strings](https://www.connectionstrings.com/sql-server-2019/) +* [SQLite connection strings](https://www.connectionstrings.com/sqlite/) + +{% hint style="info" %} +If you're using Umbraco 9 [SQL Server Compact database](https://www.connectionstrings.com/sql-server-compact/) is supported instead of SQLite. +{% endhint %} + +## Provider name +Because Umbraco cannot determine the provider name from the connection string in all cases. Umbraco follows [Microsoft's convention](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/configuration/?view=aspnetcore-8.0#connection-string-prefixes-1) for provider names, which involves specifying it as a postfix in the connection string name. + diff --git a/16/umbraco-cms/reference/configuration/contentdashboard.md b/16/umbraco-cms/reference/configuration/contentdashboard.md new file mode 100644 index 00000000000..6eea919821f --- /dev/null +++ b/16/umbraco-cms/reference/configuration/contentdashboard.md @@ -0,0 +1,38 @@ +--- +description: "Information on the content dashboard settings section" +--- + +# Content Dashboard Settings + +Allows you to configure the Content Dashboard settings for Umbraco. + +```json +{ + "Umbraco": { + "CMS": { + "ContentDashboard": { + "AllowContentDashboardAccessToAllUsers": false, + "ContentDashboardPath": "cms", + "ContentDashboardUrlAllowlist": [] + } + } + } +} +``` + +## AllowContentDashboardAccessToAllUsers + +Gets a value indicating whether the Content Dashboard should be available to all users. + +When the value is `true` the dashboard is visible for all user groups. Otherwise, when the value is `false`, the default access rules for that dashboard will be in use. + +## ContentDashboardPath + +Gets the path to use when constructing the URL for retrieving data for the content dashboard. + +## ContentDashboardUrlAllowlist + +Gets the allowed addresses to retrieve data for the content dashboard. + +No addresses specified indicates that any URL is allowed. + diff --git a/16/umbraco-cms/reference/configuration/contentsettings.md b/16/umbraco-cms/reference/configuration/contentsettings.md new file mode 100644 index 00000000000..a1bfa375598 --- /dev/null +++ b/16/umbraco-cms/reference/configuration/contentsettings.md @@ -0,0 +1,276 @@ +--- +description: Information on the content settings section +--- + +# Content Settings + +Content settings contains a handful of settings related to the content in the CMS. It includes settings such as allowed upload files, image settings, and much more. All the values in the content settings has default values, so all configuration is optional. + +The following snippet will give an overview of the keys and values in the content section including the default values: + +```json +"Umbraco": { + "CMS": { + "Content": { + "ContentVersionCleanupPolicy": { + "EnableCleanup": false, + "KeepAllVersionsNewerThanDays": 7, + "KeepLatestVersionPerDayForDays": 90 + }, + "AllowEditInvariantFromNonDefault": true, + "AllowedMediaHosts": [], + "AllowedUploadedFileExtensions": [], + "DisableDeleteWhenReferenced": false, + "DisableUnpublishWhenReferenced": false, + "DisallowedUploadedFileExtensions": ["ashx", "aspx", "ascx", "config", "cshtml", "vbhtml", "asmx", "air", "axd", "xamlx"], + "Error404Collection": [], + "BackOfficeLogo": "../media/qyci4xti/logo.png", + "HideBackOfficeLogo": false, + "Imaging": { + "ImageFileTypes": ["jpeg", "jpg", "gif", "bmp", "png", "tiff", "tif"], + "AutoFillImageProperties": [ + { + "Alias": "umbracoFile", + "ExtensionFieldAlias": "umbracoExtension", + "HeightFieldAlias": "umbracoHeight", + "LengthFieldAlias": "umbracoBytes", + "WidthFieldAlias": "umbracoWidth" + } + ] + }, + "LoginBackgroundImage": "login/login.jpg", + "LoginLogoImage": "login/logo_light.svg", + "LoginLogoImageAlternative": "login/logo_dark.svg", + "Notifications": { + "DisableHtmlEmail": false, + "Email": null + }, + "PreviewBadge": "My HTML here]]>", + "ResolveUrlsFromTextString": false, + "ShowDeprecatedPropertyEditors": false, + "ShowDomainWarnings": true, + "ShowUnroutableContentWarnings": true + } + } +} +``` + +## Root level settings + +In the root level section, that is those without a separate sub section like Imaging, you can configure: + +### Allow edit invariant from non-default + +Invariant properties are properties on a multilingual site that are not varied by culture. This means that they share the same value across all languages added to the website. + +When the setting is set to `false` the invariant properties that are shared between all languages can only be edited from the default language. This means you need access to the default language, in order to edit the property. + +When set to `true` (default) the invariant properties will need to be unlocked before they can be edited. The lock exists in order to make it clear that this change will affect more languages. + +### Allowed upload file extensions + +If greater control is required than available from the `DisallowedUploadedFileExtensions` setting, this setting can be used to store a list of file extensions. If provided, only files with these extensions can be uploaded via the backoffice. + +### Allowed media hosts + +By default, only relative URLs are allowed when getting URLs for resized images or thumbnails using the ImagesController. If you need absolute URLs you will have to add the allowed hosts to this list. The value could be `["umbraco.com", "www.umbraco.com", "our.umbraco.com"]`. + +### Disable delete when referenced + +This setting allows you to specify whether a user can delete content or media items that depend on other items. This also includes any descendants that have dependencies. Setting this to **true** will remove or disable the _Delete_ button. + +### Disable unpublish when referenced + +This setting allows you to specify whether or not users can unpublish content items that depend on other items or have descendants that have dependencies. Setting this to **true** will disable the _Unpublish_ button. + +### Disallowed upload file extensions + +This setting consists of a list of file extensions that editors shouldn't be allowed to upload via the backoffice. + +### Error 404 collection + +In case of a 404 error (page not found) Umbraco can return a default page instead. This is set here. Notice you can also set a different error page, based on the current culture so a 404 page can be returned in the correct language. + +```json +"Error404Collection": [ + { + "ContentId": 1, + "Culture": "en-US" + } +] +``` + +The above example shows what you need to do if you only have a single site that needs to show a custom 404 page. You specify which node that should be shown when a request for a non-existing page is being made. You can specify the node in three ways: + +1. Enter the nodes **id** (`"ContentId": 1`) +2. Enter the node's **GUID** (`"ContentKey": "4f96ffdd-b969-46a8-949e-7935c41fabc0"`) +3. Use [IContentLastChanceFinder](../../tutorials/custom-error-page.md#set-a-custom-404-page-using-icontentlastchancefinder) to find the node. + +{% hint style="info" %} + +* Ids are usually local to the specific solution (so won't point to the same node in two different environments if you're using Umbraco Cloud). +* GUIDs are universal and will point to the same node on different environments, provided the content was created in one environment and deployed to the other(s). +{% endhint %} + +If you have multiple sites, with different cultures, setup in your tree then you will need to setup the errors section like below: + +```json +"Error404Collection": [ + { + "ContentId": 1, + "Culture": "default" + }, + { + "ContentId": 200, + "Culture": "en-US" + }] +``` + +If you have more than two sites and forget to add a 404 page and a culture, the default page will act as fallback. Same happens if you for some reason forget to define a hostname on a site. + +### Backoffice logo + +This setting can be used to set a custom image path to replace the Umbraco logo in the backoffice. + +### Hide backoffice logo + +This setting can be used to hide the Umbraco logo in backoffice. + +### Login background image + +You can specify your own background image for the login screen here. The image will automatically get an overlay to match backoffice colors. This path is relative to the `wwwroot/umbraco` path. The default location is: `wwwroot/umbraco/login/login.jpg`. + +### Login logo image + +You can specify your own image for the small logo in the top left corner of the login screen. This path is relative to the `wwwroot/umbraco` path. The default location is: `wwwroot/umbraco/login/logo_light.svg`. + +### Login logo image (alternative) + +You can specify your own alternative image for the small logo in the top left corner of the login screen. The alternative image is shown on light backgrounds (for example for mobile resolutions). This path is relative to the `wwwroot/umbraco` path. The default location is: `wwwroot/umbraco/login/logo_dark.svg`. + +### Preview badge + +This allows you to customize the preview badge being shown when you're previewing a node. + +```json +"PreviewBadge": "In Preview Mode - click to end]]>" +``` + +### Resolve urls from text string + +This setting is used when you're running Umbraco in virtual directories. Setting this to true can increase render time for pages with a large number of links. However, this is required if Umbraco is running in a virtual directory. + +### Show deprecated property editors + +This setting is used for controlling whether or not the Data Types marked as obsolete should be visible in the dropdown when creating new Data Types. + +By default this is set to `false`. To make the obsolete data types visible in the dropdown change the value to `true`. + +### Show domain warnings + +If you do not configure Domains for each language in a multilingual site then every time you publish your content you get this warning: + +`Content published: Domains are not configured for multilingual site, please contact an administrator, see log for more information.` + +If you have a use case for not setting the domains, you can set this setting **ShowDomainWarnings** to `false` to stop the warning from displaying. + +### Show unroutable content warnings + +If your routing setup leads to more than one document having the same URL, on publish a warning will be displayed: + +`Content published: The document does not have a URL, possibly due to a naming collision with another document. More details can be found under Info.` + +To suppress these warnings, set this option to `false`. + +## Content version cleanup policy + +The global settings for the scheduled job which cleans historic content versions. These settings can be overridden per Document Type. + +Current draft and published versions will never be removed, nor will individual content versions which have been marked as "preventCleanup". + +See [Content Version Cleanup](../../fundamentals/data/content-version-cleanup.md) for more details on overriding configuration and preventing cleanup of specific versions. + +```json +"ContentVersionCleanupPolicy": { + "EnableCleanup": false, + "KeepAllVersionsNewerThanDays": 7, + "KeepLatestVersionPerDayForDays": 90 +} +``` + +If you don't wish to retain any content versions except for the current draft and currently published you can set both of the "keep" settings values to 0. After doing this, the next time the scheduled job runs (hourly) all non-current versions (except those marked "prevent cleanup") will be removed. + +### Enable cleanup + +When `true` a scheduled job will delete historic content versions that are not kept according to the policy every hour. + +When `false`, the scheduled job will never delete any content versions regardless of overridden settings for a Document Type. + +This defaults to `false` when not set in the configuration which will be the case for those upgrading from v9.0.0. However, the dotnet new template will supply an `appsettings.json` with the value set to true for all sites starting from Umbraco 9.1.0. + +### Keep all versions newer than days + +All versions that fall in this period will be kept. + +### Keep latest version per day for days + +For content versions that fall in this period, the most recent version for each day is kept. All previous versions for that day are removed unless marked as preventCleanup. + +This variable is independent of `KeepAllVersionsNewerThanDays`, if both were set to the same value `KeepLatestVersionPerDayForDays` would never apply as `KeepAllVersionsNewerThanDays` is considered first. + +## Imaging + +This section is used for managing how Umbraco handles images, allowed attributes and, which properties of an image that should be automatically updated on upload. + +```json +"Imaging": { + "ImageFileTypes": ["jpeg", "jpg", "gif", "bmp", "png", "tiff", "tif"], + "AutoFillImageProperties": { + "Alias": "umbracoFile", + "WidthFieldAlias": "umbracoWidth", + "HeightFieldAlias": "umbracoHeight", + "LengthFieldAlias": "umbracoBytes", + "ExtensionFieldAlias": "umbracoExtension" + } +} +``` + +Let's break it down. + +### Image file types + +This is a separated list of accepted image formats + +### Auto fill image properties + +You can define what properties should be automatically updated when an image is being uploaded. This means that if you decide to rename the default **umbracoWidth** and **umbracoHeight** properties the values in **`"WidthFieldAlias"`** and **`"HeightFieldAlias"`** need to be updated. This needs to happen in order to automatically populate the values when the image is being uploaded. + +### Custom media document + +If you need to create a custom Media Type to handle images you need to add another object using the custom Media Type alias. Like below. Keep in mind that the width and height attributes have also been changed in this example. + +```json +"Imaging": { + "ImageFileTypes": ["jpeg", "jpg", "gif", "bmp", "png", "tiff", "tif"], + "AutoFillImageProperties": [ + { + "Alias": "umbracoFile", + "WidthFieldAlias": "umbracoWidth", + "HeightFieldAlias": "umbracoHeight", + "LengthFieldAlias": "umbracoBytes", + "ExtensionFieldAlias": "umbracoExtension" + }, + { + "Alias": "customImage", + "WidthFieldAlias": "width", + "HeightFieldAlias": "height", + "LengthFieldAlias": "umbracoBytes", + "ExtensionFieldAlias": "umbracoExtension" + } + ] +} +``` + +## Notifications + +Umbraco can send out email notifications, set the sender email address for the notifications emails here. To set the SMTP server used to send the emails, edit the standard Simple Mail Transfer Protocol (SMTP) section in the global section, see [global settings](globalsettings.md) for more information. diff --git a/16/umbraco-cms/reference/configuration/datatypes.md b/16/umbraco-cms/reference/configuration/datatypes.md new file mode 100644 index 00000000000..5d65f725f6a --- /dev/null +++ b/16/umbraco-cms/reference/configuration/datatypes.md @@ -0,0 +1,32 @@ +--- +description: "Information on the data types settings section" +--- + +# Data Types Settings + +Allows you to configure the behavior of data types. + +```json +{ + "Umbraco": { + "CMS": { + "DataTypes": { + "CanBeChanged": "True" + } + } + } +} +``` + +## CanBeChanged + +Gets or sets a value indicating if data types can be changed after they've been used. + +Valid values: + +- `"True"` + - Allows data types to be changed after creation. This can lead to data on content is not valid on the Data Type. +- `"False"` + - Disallow Data Type changes. (Recommeded value, unless you really know what you are doing) +- `"FalseWithHelpText"` + - Disallow Data Type changes, but show the users a help text so they understand why. diff --git a/16/umbraco-cms/reference/configuration/debugsettings.md b/16/umbraco-cms/reference/configuration/debugsettings.md new file mode 100644 index 00000000000..aadd4469609 --- /dev/null +++ b/16/umbraco-cms/reference/configuration/debugsettings.md @@ -0,0 +1,28 @@ +--- +description: "Information on debug settings section" +--- + +# Debug settings + +This section contains configurations regarding debugging, and should therefore only be used in development. + +The debug section has two settings you can configure, `"LogIncompletedScopes"` and `"DumpOnTimeoutThreadAbort"`, both of these are false by default: + +```json +"Umbraco": { + "CMS": { + "Debug": { + "DumpOnTimeoutThreadAbort": false, + "LogIncompletedScopes": false + } + } +} +``` + +## Log incompleted scopes + +If this value is set to true, any scope that gets disposed without first being completed will trigger a log entry containing the stacktrace. + +## DumpOnTimeoutThreadAbort + +If this value is set to true, a memory dump will be taken if a thread aborts due to a timeout. This dump will be saved to `/umbraco/Data/MiniDump`. \ No newline at end of file diff --git a/16/umbraco-cms/reference/configuration/examinesettings.md b/16/umbraco-cms/reference/configuration/examinesettings.md new file mode 100644 index 00000000000..7ff030ba0c3 --- /dev/null +++ b/16/umbraco-cms/reference/configuration/examinesettings.md @@ -0,0 +1,25 @@ +--- +description: "Information on the Examine settings section" +--- + +# Examine settings + +Since the majority of Examine configuration takes place in code, this section is small and contains only one setting to change: `LuceneDirectoryFactory`. This setting allows you to change the behavior of the `ExamineIndexes` directory. + +This section has a default value, and does not need to be configured, configuring Examine might look something like this: + +```json +"Umbraco": { + "CMS": { + "Examine": { + "LuceneDirectoryFactory": "Default" + } + } +} +``` + +This is how Examine is configured by default. There is three different types of Lucene directory factories: + +* `Default` - The index will operate from the default location: `umbraco/Data/TEMP/ExamineIndexes` +* `SyncedTempFileSystemDirectoryFactory` - The index will operate on a local index created in the processes %temp% location and will replicate back to main storage in `umbraco/Data/TEMP/ExamineIndexes` +* `TempFileSystemDirectoryFactory` - The index will operate only in the processes %temp% directory location diff --git a/16/umbraco-cms/reference/configuration/exceptionfiltersettings.md b/16/umbraco-cms/reference/configuration/exceptionfiltersettings.md new file mode 100644 index 00000000000..1fecfe066e1 --- /dev/null +++ b/16/umbraco-cms/reference/configuration/exceptionfiltersettings.md @@ -0,0 +1,19 @@ +--- +description: "Information on the exception filter settings section" +--- + +# Exception filter settings + +This section allows you to disable the `ModelBindingExceptionFilter`, this filter is only enable if the models builder mode is set to `InMemoryAuto`. This filter will return a redirect to the page being loaded after one second, if a `ModelsBindingException` or `InvalidCastException` occurs. The reason for this filter is that a page might be requested at the same time as the content type has been changed. If this occurs, the new model might not have been generated and loaded yet. This filter will take care of this. +By default this filter is enabled, but will be ignored if the mode is not `InMemoryAuto`. To manually disable the filter add the `"ExecptionFilter"` section to your config with the `"Disabled"` key set to `true` like so: + +```json +"Umbraco": { + "CMS": { + "ExceptionFilter": { + "Disabled": true + } + } +} +``` + diff --git a/16/umbraco-cms/reference/configuration/filesystemproviders.md b/16/umbraco-cms/reference/configuration/filesystemproviders.md new file mode 100644 index 00000000000..0bac9982caa --- /dev/null +++ b/16/umbraco-cms/reference/configuration/filesystemproviders.md @@ -0,0 +1,176 @@ +--- +description: Information on FileSystemProviders and how to configure them in Umbraco +--- + +# FileSystemProviders Configuration + +Filesystem providers are configured via code, you can either configure it in a composer, or in the `Program.cs` file. + +```csharp +using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Infrastructure.DependencyInjection; +using IHostingEnvironment = Umbraco.Cms.Core.Hosting.IHostingEnvironment; + +namespace FilesystemProviders; + +public class FilesystemComposer : IComposer +{ + public void Compose(IUmbracoBuilder builder) => + builder.SetMediaFileSystem(factory => + { + IHostingEnvironment hostingEnvironment = factory.GetRequiredService(); + IWebHostEnvironment webHostEnvironment = factory.GetRequiredService(); + var folderLocation = "~/CustomMediaFolder"; + var rootPath = webHostEnvironment.MapPathWebRoot(folderLocation); + var rootUrl = hostingEnvironment.ToAbsolute(folderLocation); + + return new PhysicalFileSystem( + factory.GetRequiredService(), + hostingEnvironment, + factory.GetRequiredService>(), + rootPath, + rootUrl); + }); +} +``` + +By default Umbraco will save Media in a folder called `/media` within the webroot on the Physical file system. The code snippet above will change the location to instead save the media in a folder called `/CustomMediaFolder` within the webroot. + +The media provider can be of many types, for example in case you want to store media on Azure, Amazon or even DB. But the provider that comes by default with the installation of Umbraco is the `PhysicalFileSystem` provider. + +## PhysicalFileSystem Configuration + +The physical file system provider manages the interaction of Umbraco with the local file system. It can be configured for two different scenarios: + +* Media files stored inside a virtual folder of the site +* Media files stored somewhere else outside of the site and accessed via a custom URL + +### Virtual Folder + +To configure the PhysicalFileSystem for a virtual folder, create a new filesystem with a root path and URL within the wwwroot folder. Refer to the example above and [Extending FileSystemProviders](../../extending/filesystemproviders/) for more information. + +### Physical path + +There are a few more steps involved if you want to store the media files in a separate folder outside the webroot. + +First you must register the folder as a static file provider in your `Program.cs` file like so: + +```csharp +... +WebApplication app = builder.Build(); +app.UseStaticFiles(new StaticFileOptions + { + FileProvider = new PhysicalFileProvider(Path.Combine("C:", "storage", "umbracoMedia")), + RequestPath = "/CustomPath" + }); +``` + +Now you can register the folder as the media filesystem + +```csharp +using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Infrastructure.DependencyInjection; +using IHostingEnvironment = Umbraco.Cms.Core.Hosting.IHostingEnvironment; + +namespace FilesystemProviders; + +public class FilesystemComposer : IComposer +{ + public void Compose(IUmbracoBuilder builder) + { + builder.SetMediaFileSystem((factory) => + { + IHostingEnvironment hostingEnvironment = factory.GetRequiredService(); + var rootPath = Path.Combine("C:", "storage", "umbracoMedia"); + var rootUrl = hostingEnvironment.ToAbsolute("/CustomPath"); + + return new PhysicalFileSystem( + factory.GetRequiredService(), + hostingEnvironment, + factory.GetRequiredService>(), + rootPath, + rootUrl); + }); + } +} +``` + +This is much the same as when you register it within the wwwroot with a virutal folder. The only differnce is that now you provide an absolute root path and root URL to the physical filesystem. + +* `rootPath` is the full filesystem path where you want media files to be stored. It has to be rooted, must use directory separators (`\`) and must not end with a separator. For example, `Z:` or `C:\path\to\folder` or `\\servername\path`. +* `rootUrl` is the URL where the files will be accessible from. It must use URL separators (`/`) and must not end with a separator. It can either be a folder, like `/UmbracoMedia`, in which case it will considered as subfolder of the main domain (`example.com/UmbracoMedia`) or can be a fully qualified URL, with also domain name and protocol (for ex `http://media.example.com/media`). + +For more information see [Extending FileSystemProviders](../../extending/filesystemproviders/). + +## Custom providers + +To store media files in different systems, the type of provider must be changed. You can learn [how to build a custom filesystem provider](../../extending/filesystemproviders/#custom-file-systems-ifilesystem) in the Extending Umbraco section. + +{% hint style="info" %} +At the moment when a file is saved, its full URL is stored as node property. This means that a configuration change will not apply to pre-existing media files but only to the ones saved after that. + +If you want all your media files in the same location, you have to copy all pre-existing files to the new path. Additionally, you need to update the path property of the media item to the new URL. This can be either directly inside the database or by using the `MediaService`. +{% endhint %} + +## Get the contents of a file as a stream + +The recommended approach to obtain a file's content as a stream is to utilize the `MediaFileManager`. It is advised to avoid reading the file directly from the server using methods like `Server.MapPath`. This will ensure that, regardless of the file system provider, the stream will be returned correctly. This example demonstrates using MediaFileManager to validate file existence and stream it back from a controller. + +```csharp +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.StaticFiles; +using Umbraco.Cms.Core.Cache; +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.Logging; +using Umbraco.Cms.Core.Routing; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Web; +using Umbraco.Cms.Infrastructure.Persistence; +using Umbraco.Cms.Web.Website.Controllers; + +namespace FilesystemProviders; + +public class MediaController : SurfaceController +{ + private readonly MediaFileManager _mediaFileManager; + private readonly IWebHostEnvironment _webHostEnvironment; + + public MediaController( + IUmbracoContextAccessor umbracoContextAccessor, + IUmbracoDatabaseFactory databaseFactory, + ServiceContext services, + AppCaches appCaches, + IProfilingLogger profilingLogger, + IPublishedUrlProvider publishedUrlProvider, + MediaFileManager mediaFileManager, + IWebHostEnvironment webHostEnvironment) + : base(umbracoContextAccessor, databaseFactory, services, appCaches, profilingLogger, publishedUrlProvider) + { + _mediaFileManager = mediaFileManager; + _webHostEnvironment = webHostEnvironment; + } + + public IActionResult Index(string id, string file) + { + var path = _webHostEnvironment.MapPathWebRoot($"/media/{id}/{file}"); + if (_mediaFileManager.FileSystem.FileExists(path) == false) + { + return new NotFoundResult(); + } + + var stream = _mediaFileManager.FileSystem.OpenFile(path); + stream.Seek(0, SeekOrigin.Begin); + + var provider = new FileExtensionContentTypeProvider(); + if (!provider.TryGetContentType(file, out var contentType)) + { + contentType = "application/octet-stream"; + } + + return new FileStreamResult(stream, contentType); + + } +} +``` diff --git a/16/umbraco-cms/reference/configuration/globalsettings.md b/16/umbraco-cms/reference/configuration/globalsettings.md new file mode 100644 index 00000000000..84f5f2d9306 --- /dev/null +++ b/16/umbraco-cms/reference/configuration/globalsettings.md @@ -0,0 +1,364 @@ +--- +description: "Information on the global settings section" +--- + +# Global Settings + +Global settings contains at set of global settings for the CMS such as default UI language, reserved urls, and much more. All of these, except for SMTP settings contains default values, meaning that all configuration is optional, unless you wish to send emails from your site. + +The following snippet contains all the available options, with default values, and some example values for the required keys `From`, `Host` and `Port` keys of the SMTP settings: + +```json +"Umbraco": { + "CMS": { + "Global": { + "ReservedUrls": "~/.well-known,", + "ReservedPaths": "~/app_plugins/,~/install/,~/mini-profiler-resources/,~/umbraco/,", + "TimeOut": "00:20:00", + "DefaultUILanguage": "en-US", + "HideTopLevelNodeFromPath": true, + "UseHttps": false, + "VersionCheckPeriod": 7, + "IconsPath": "~/umbraco/assets/icons", + "UmbracoCssPath": "~/css", + "UmbracoScriptsPath": "~/scripts", + "UmbracoMediaPath": "~/media", + "UmbracoMediaPhysicalRootPath": "X:/Shared/Media", + "InstallMissingDatabase": false, + "DisableElectionForSingleServer": false, + "DatabaseFactoryServerVersion": "SqlServer.V2019", + "MainDomLock": "FileSystemMainDomLock", + "MainDomKeyDiscriminator": "", + "Id": "184a8175-bc0b-43dd-8267-d99871eaec3d", + "NoNodesViewPath": "~/umbraco/UmbracoWebsite/NoNodes.cshtml", + "Smtp": { + "From": "person@umbraco.dk", + "Host": "localhost", + "Port": 25, + "SecureSocketOptions": "Auto", + "DeliveryMethod": "Network", + "PickupDirectoryLocation": "", + "Username": "person@umbraco.dk", + "Password": "SuperSecretPassword" + }, + "DatabaseServerRegistrar": { + "WaitTimeBetweenCalls": "00:01:00", + "StaleServerTimeout": "00:02:00" + }, + "DatabaseServerMessenger": { + "MaxProcessingInstructionCount": 1000, + "TimeToRetainInstructions": "2.00:00:00", + "TimeBetweenSyncOperations": "00:00:05", + "TimeBetweenPruneOperations": "00:01:00" + }, + "DistributedLockingMechanism": "", + "DistributedLockingReadLockDefaultTimeout": "00:01:00", + "DistributedLockingWriteLockDefaultTimeout": "00:00:05", + } + } +} +``` + +## Root level settings + +In the root level section, that is those without a seperate sub section like SMTP, you can configure + +### Reserved urls + +Key: `ReservedUrls` +Type: `string` (default: `~/.well-known,`) + +A comma-seperated list of files to be left alone by Umbraco, these files will be served, and the Umbraco request pipeline will not be triggered. + +### Reserved paths + +Key: `ReservedPaths` +Type: `string` (default: `~/app_plugins/,~/install/,~/mini-profiler-resources/,~/umbraco/,`) + +A comma-separated list of all the folders in your directory to be left alone by Umbraco. If you have folders with custom files, add them to this setting to make sure Umbraco leaves them alone. + +{% hint style="warning" %} Adding additional values to the Reserves URLs and Reserved Paths will overwrite default/current values. This causes performance issues as well. {% endhint %} + +### Timeout + +Key: `TimeOut` +Type: `string` (default: `00:20:00`) + +Configure the session timeout to determine how much time without a request being made can pass before the user is required to log in again. The session timeout format needs to be set as `HH:MM:SS`. Any activity within the backoffice will reset the timer. + +{% hint style="info" %} Long session timeouts raise data exposure and unauthorized access risks. Thus, it's vital to establish a reasonable timeout to mitigate security risks. {% endhint %} + +### Default UI language + +Key: `DefaultUILanguage` +Type: `string` (default: `en-US`) + +The default language to use in the backoffice if a user isn't explicitly assigned one. + +### Hide top level nodes from path + +Key: `HideTopLevelNodeFromPath` +Type: `bool` (default: `true`) + +If you are running multiple sites, you don't want the top level node in your URL and can disable it with this setting. + +### Use https + +Key: `UseHttps` +Type: `bool` (default: `false`) + +Makes sure that all of the requests in the backoffice are called over HTTPS instead of HTTP when set to true. + +### Version check period + +Key: `VersionCheckPeriod` +Type: `int` (default: `7`) + +When this value is set above 0, the backoffice will check for a new version of Umbraco every 'x' number of days where 'x' is the value defined for this setting. Set this value to 0 to never check for a new version. + +### Icons path + +Key: `IconsPath` +Type: `string` (default: `umbraco/assets/icons`) + +By adding this value you can specify a new/different folder for storing your icon resources. It's important to be aware of NetCore's limitations regarding serving static file content. By default, static content will only be served from the `wwwroot` folder. + +### Umbraco CSS path + +Key: `UmbracoCssPath` +Type: `string` (default: `~/css`) + +By adding this you can specify a new/different folder for storing your CSS files, and still be able to edit them within Umbraco. It's also important to be aware of NetCores limitations regarding serving static file content here as well, by default, static content will only be served from the wwwroot folder. For more info see [Extending filesystem](../../extending/filesystemproviders/) + +### Umbraco scripts path + +Key: `UmbracoScriptsPath` +Type: `string` (default: `~/scripts`) + +By adding this you can specify a new/different folder for storing your script/js files, and still be able to edit them within Umbraco. It's also important to be aware of NetCores limitations regarding serving static file content here as well, by default, static content will only be served from the wwwroot folder. For more info see [Extending filesystem](../../extending/filesystemproviders/) + +### Umbraco media path + +Key: `UmbracoMediaPath` +Type: `string` (default: `~/media`) + +By adding this you can specify a new/different folder for storing your media files, and still be able to edit them within Umbraco. It's also important to be aware of NetCores limitations regarding serving static file content here as well, by default, static content will only be served from the wwwroot folder. For more info see [Extending filesystem](../../extending/filesystemproviders/) + +### Umbraco media physical root path + +Key: `UmbracoMediaPhysicalRootPath` +Type: `string` (default: `~/media`) + +By adding this you can specify a new/different folder for storing your media files elsewhere on the server. Unlike `UmbracoMediaPath`, this does not change the relative path that media is served from (e.g. /media) but allows for files to be stored **outside** of the wwwroot folder. Both relative paths (../../Shared/Media) and absolute server paths (X:/Shared/Media) are supported. For more info see [Extending filesystem](../../extending/filesystemproviders/) + +### Install missing database + +Key: `InstallMissingDatabase` +Type: `bool` (default: `false`) + +This is not a setting that commonly needs to be configured. + +If enabled Umbraco will try to automatically install the database when it's missing. This is primarily used in conjuction with unattended installs. + +### Disable election for single server + +Key: `DisableElectionForSingleServer` +Type: `bool` (default: `false`) + +This is not a setting that commonly needs to be configured. + +This value is primarily used on Umbraco Cloud for a small startup performance optimization. When this is true, the website instance will automatically be configured to not support load balancing and the website instance will be configured to be the 'primary' server for scheduling so no [primary election](../../fundamentals/setup/server-setup/load-balancing/file-system-replication.md) occurs. This will save 1 database call during startup. + +### Database factory version + +Key: `DatabaseFactoryServerVersion` +Type: `bool` (default: `false`) + +This is not a setting that commonly needs to be configured. + +This setting is used to specify which sql server version that the database is running, this setting is only required if you use SqlServer 2008, if this is the case set the setting to `"SqlServer.V2008"` + +### Main dom lock + +Key: `MainDomLock` +Type: `string` + +Specifies the implementation of IMainDomLock to be used. + +`IMainDomLock` is used to synchronize access to resources like the Lucene indexes. + +Available options: + +* `"FileSystemMainDomLock"`- Available cross-platform, uses lock files written to LocalTempPath to control acquisition of MainDom status. +* `"MainDomSemaphoreLock"` - Windows only, uses a named system Semaphore with a `maximumCount` of 1 to control acquisition of MainDom status. +* `"SqlMainDomLock"` - Available cross-platform, uses the database to control acquisition of MainDom status. + +The default implementation unless configured otherwise is `FileSystemMainDomLock`. + +### Main dom key discriminator + +Key: `MainDomKeyDiscriminator` +Type: `string` + +For advanced use cases e.g. deployment slot swapping on Azure app services. + +When using SqlMainDomLock a MainDomKey is used to identify an instance of a running application. + +The MainDomKey is by default comprised of the server's machine name & the application id. + +This is generally all that is required to control MainDom status as starting a new process for the same application on the same server will result in a matching MainDomKey. This will then require that an existing instance yields MainDom status to the new process. + +Deployment slots for a given Azure App Service share the same machine name. Without additional configuration, they will share a MainDomKey and therefore compete for MainDom status. This can be undesirable if attempting to deploy to a deployment slot followed by a swap with the production slot as once traffic has switched to the new instance the old production instance reboots and can re-acquire MainDom status. See [What happens during a swap](https://docs.microsoft.com/en-us/azure/app-service/deploy-staging-slots#what-happens-during-a-swap). + +To prevent this from occurring you can specify a MainDomKeyDiscriminator which should be set as a slot-specific configuration to prevent the slots from competing for MainDom status. + +It's worth noting that during the swap operation there is a period where both instances will share the same configuration and at this point, the old instance will yield MainDom status to the new instance. + +### Main dom release signal polling interval + +Key: `MainDomReleaseSignalPollingInterval` +Type: `string` + +Gets or sets the duration (in milliseconds) for which the MainDomLock release signal polling task should sleep. The default value is 2000ms. + +### Id + +Key: `Id` +Type: `string` + +This setting doesn't need to be configured. + +This setting contains a unique ID used to identify your project, and is populated the first time your site runs, you shouldn't change this setting. + +### No nodes view path + +Key: `NoNodesViewPath` +Type: `string` (default: `~/umbraco/UmbracoWebsite/NoNodes.cshtml`) + +This setting specifies what view to render when there is no content on the site. + +## SMTP settings + +By adding this settings to the appsettings.json you will be able to send out emails from your Umbraco installation. This could be notifications emails if you are using content workflow, or you are using Umbraco Forms you also need to specify SMTP settings to be able use the email workflows. The forgot password function from the backoffice also needs a SMTP server to send the email with the reset link. + +### From + +Specifies the default address emails will be sent from, this setting may be overridden some place, such as when inviting a user, where the email of the user sending the invite will be used instead. The format of the address follows the RFC 822 standard so you can include a friendly name using the format `"Friendly Name "` + +### Host + +Address of the SMTP host used to send the email from. + +### Port + +The port of the SMTP host, port 25 is a common port for SMTP. + +### Username + +The username used to authenticate with the specified SMTP server, when sending an email. + +### Password + +The password used to authenticate with the specified SMTP server, when sending an email. + +### Secure socket options + +Allows you to specify what security should be used for the connection sending the email. + +The options are: + +* None - No SSL or TLS encryption should be used. +* Auto - Allow the IMailService to decide which SSL or TLS options to use (default). If the server does not support SSL or TLS, then the connection will continue without any encryption. +* SslOnConnect - The connection should use SSL or TLS encryption immediately. +* StartTls - Elevates the connection to use TLS encryption immediately after reading the greeting and capabilities of the server. If the server does not support the STARTTLS extension, then the connection will fail and a NotSupportedException will be thrown. +* StartTlsWhenAvailable - Elevates the connection to use TLS encryption immediately after reading the greeting and capabilities of the server, but only if the server supports the STARTTLS extension. + +### Delivery method + +Specifies what delivery method should be used for emails, most of the time you'd want to use the default `"Network"` option to send emails over the network. It might be useful during development to use `"SpecifiedPickupDirectory"` to place the email messages in a folder on disk, instead of trying to send them over the network. + +### Pickup directory location + +If you're using the `"SpecifiedPickupDirectory"` option on as the delivery method, this setting allows you to specify what folder the emails should be saved to. + +## Database server registrar settings + +It's unlikely that you will have to change these settings unless you're using a load balanced setup. + +### Wait time between calls + +Key: `DatabaseServerRegistrar.WaitTimeBetweenCalls` +Type: `string` (default: `00:01:00`) + +Sets a value for the amount of time to wait between calls to the database on the background thread. + +### Stale server timeout + +Key: `DatabaseServerRegistrar.StaleServerTimeout` +Type: `string` (default: `00:02:00`) + +Sets a value for the time span to wait before considering a server stale, after it has last been accessed. + +## Database server messenger + +It's unlikely that you will have change these settings, unless you're using a load balanced setup. These settings are all about how load balancing instructions from the database are processed and pruned. + +### Max processing instruction + +Key: `DatabaseServerMessenger.MaxProcessingInstructionCount` +Type: `string` (default: `1000`) + +Sets a value for the maximum number of instructions that can be processed at startup; otherwise the server cold-boots (rebuilds its caches). + +### Time to retain instructions + +Key: `DatabaseServerMessenger.TimeToRetainInstructions` +Type: `string` (default: `2.00:00:00`) + +Sets a value for the time to keep instructions in the database; records older than this number will be pruned. + +### Time between sync operations + +Key: `DatabaseServerMessenger.TimeBetweenSyncOperations` +Type: `string` (default: `00:00:05`) + +Sets a value for the time to wait between each sync operation. + +### Time between prune operations + +Key: `DatabaseServerMessenger.TimeBetweenPruneOperations` +Type: `string` (default: `00:01:00`) + +Sets a value for the time to wait between each prune operation. + +### Distributed Locking Mechanism + +Key: `DistributedLockingMechanism` +Type: `string` + +This is not a setting that commonly needs to be configured. + +Gets or sets a value representing the DistributedLockingMechanism to use. + +Valid values: + +* `"SqlServerDistributedLockingMechanism"` +* `"SqliteDistributedLockingMechanism"` + +### Distributed Read Lock DefaultTimeout + +Key: `DistributedLockingReadLockDefaultTimeout` +Type: `string` (default: `00:01:00`) + +Gets or sets a value representing the maximum time to wait whilst attempting to obtain a distributed read lock. + +The default value is 60 seconds. + +### Distributed Write Lock DefaultTimeout + +Key: `DistributedLockingWriteLockDefaultTimeout` +Type: `string` (default: `00:00:05`) + +Gets or sets a value representing the maximum time to wait whilst attempting to obtain a distributed write lock. + +The default value is 5 seconds. diff --git a/16/umbraco-cms/reference/configuration/healthchecks.md b/16/umbraco-cms/reference/configuration/healthchecks.md new file mode 100644 index 00000000000..d41e9c5991c --- /dev/null +++ b/16/umbraco-cms/reference/configuration/healthchecks.md @@ -0,0 +1,93 @@ +--- +description: "Information on the health check settings section" +--- + +# Health checks + +The health checks section allows you to disable certain health checks, and configure your own custom notification methods, that will automatically run the health checks every so often, and notify you if any health checks fails. + +An example of a HealthChecks settings can look something like this: + +```json +"Umbraco": { + "CMS": { + "HealthChecks": { + "DisabledChecks": [ + { + "Id": "D0F7599E-9B2A-4D9E-9883-81C7EDC5616F" + } + ], + "Notification": { + "Enabled": true, + "FirstRunTime": "* 4 * * *", + "Period": "1.00:00:00", + "NotificationMethods": { + "email": { + "Enabled": true, + "Verbosity": "Detailed", + "FailureOnly": true, + "Settings": { + "RecipientEmail": "alerts@mywebsite.tld" + } + } + } + } + } + } +} +``` + +This config will enable notifications to run the checks and notify via email if a check fails. The checks will run the first time five minutes after the site is booted, and then once every day. + +The email notification method is built in, if you want to read more about creating you own notification methods, or see a list of the ID of every built in health check, then see [Extending health checks](../../extending/health-check/) + +But let's go through the config one by one + +## Disabled checks + +A list of `DisabledHealthCheckSettings` objects, each of these objects represents a disabled health check. Only the Id key needs to be present and have a value, corosponding to the GUID of the health check to disable. + +There is also a `DisabledOn` key representing the date the health check was disabked and a `DisabledBy` key containing the ID of the user that disabled the health check, however these values are currently not used. + +## Notification + +Settings relating to running the health checks automatically and sending out notifications. + +### Enabled + +Allows you to disable or enable all notifications methods, if set to false, the health checks will not automatically run. + +### First run time + +This will configure when you run the health checks for the first time, if the value is not configured the health checks will run immediately after the site boots for the first time. This value is specified as a string in crontab format, so in this example, the health checks will first run at 4 a.m. + +### Period + +Specifies how often the health checks should run, as a DateTime string, in this example the checks will run every day (every 24 hours). + +### Notification methods + +A dictionary of all the notification methods that should be used. + +The key of the dictionary is the alias of the notification method, and the value is a `HealthChecksNotificationMethodSettings` configuration object, in this case it's the built in `email` notification method. + +Each object allows the following to be configured: + +#### Enabled + +Allows you to enable or disable specific checks. + +#### Verbosity + +Configures how verbose the reporting should be, the available options are: + +* Summary +* Detailed + +#### Failure only + +If set to true, the notification method will only run if a check has failed. + +#### Settings + +Allows you to set custom settings for a given implementation of a notification method, which settings are available depends on the specific implementation. diff --git a/16/umbraco-cms/reference/configuration/hostingsettings.md b/16/umbraco-cms/reference/configuration/hostingsettings.md new file mode 100644 index 00000000000..2c0c784cb77 --- /dev/null +++ b/16/umbraco-cms/reference/configuration/hostingsettings.md @@ -0,0 +1,45 @@ +--- +description: "Information on the hosting settings section" +--- + +# Hosting settings + +Hosting settings contains settings regarding the hosting of the site, such as application virtual path, local temporary storage location and debug. + +A full configuration with default values can be seen here: + +```json +"Umbraco": { + "CMS": { + "Hosting": { + "ApplicationVirtualPath": "/", + "LocalTempStorageLocation": "Default", + "Debug": false, + "SiteName" + } + } +} +``` + +## Setting overview + +### Application virtual path + +This setting specified the virtual path of the application, this path must start with a slash. + +### Local temp storage location + +This setting specifies the location of the local temp storage. + +Options: + +* Default +* EnvironmentTemp + +### Debug + +This setting allows you to run Umbraco in debug mode, by setting the value to true. + +### Site name + +Gets or sets a value specifying the name of the site. The [IWebHostEnvironment.ApplicationName](https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.hosting.ihostenvironment.applicationname?view=dotnet-plat-ext-6.0) is used if not specified diff --git a/16/umbraco-cms/reference/configuration/imagingsettings.md b/16/umbraco-cms/reference/configuration/imagingsettings.md new file mode 100644 index 00000000000..441608d8c7a --- /dev/null +++ b/16/umbraco-cms/reference/configuration/imagingsettings.md @@ -0,0 +1,84 @@ +--- +description: "Information on the imaging settings section" +--- + + +# Imaging settings + +The imaging settings section lets you configure the cache and resize settings for processed images on your project (using [ImageSharp.Web](https://docs.sixlabors.com/articles/imagesharp.web/) as default implementation). If you need to configure allowed image file types or auto fill image properties, you want to use [content settings](contentsettings.md) instead. + +All these settings contain default values, so nothing needs to be explicitly configured. A complete settings section for imaging can be seen here with all the default values: + +```json +"Umbraco": { + "CMS": { + "Imaging": { + "Cache": { + "BrowserMaxAge": "7.00:00:00", + "CacheMaxAge": "365.00:00:00", + "CacheFolderDepth": 8, + "CacheHashLength": 12, + "CacheFolder": "~/umbraco/Data/TEMP/MediaCache" + }, + "Resize": { + "MaxWidth": 5000, + "MaxHeight": 5000 + } + } + } +} +``` + +## Cache + +Contains configuration for browser and server caching. +When changing these cache headers, it is recommended to clear your media cache. This is due to the data being stored in the cache and not updated when the configuration is changed. + +### Browser max age + +Specifies how long a requested processed image may be stored in the browser cache by using this value in the `Cache-Control` response header. The default is 7 days (formatted as a timespan). + +### Cache max age + +Specifies how long a processed image may be used from the server cache before it needs to be re-processed again. The default is one year (365 days, formatted as a timespan). + +### Cache folder depth + +Gets or sets the depth of the nested cache folders structure to store the images. Defaults to 8. + +### Cache hash length +Gets or sets the length of the filename to use (minus the extension) when storing images in the image cache. Defaults to 12 characters. + +### Cache folder + +Allows you to specify the location of the cached images folder. By default, the cached images are stored in `~/umbraco/Data/TEMP/MediaCache`. The tilde (`~`) resolves to the content root of your project/application. + +## Resize + +Contains configuration for image resizing. + +### Max width/max height + +Specifies the maximum width and height an image can be resized to. If the requested width and height are _both_ above the configured maximums, no resizing will be performed. This adds basic security to prevent resizing to big dimensions and using a lot of server CPU/memory to do so. + +The maximum width and height settings are enforced by setting the `ImageSharpMiddlewareOptions.OnParseCommandsAsync` option of ImageSharp to an Umbraco-specific function. If you want to add your own logic without overwriting this behaviour, use the following code: + +```csharp +public class ConfigureImageSharpMiddlewareOptionsComposer : IComposer +{ + public void Compose(IUmbracoBuilder builder) + => builder.Services.Configure(options => + { + // Capture existing task to not overwrite it + var onParseCommandsAsync = options.OnParseCommandsAsync; + options.OnParseCommandsAsync = async context => + { + // Custom logic before + + await onParseCommandsAsync(context); + + // Custom logic after + }; + }); +} +``` diff --git a/16/umbraco-cms/reference/configuration/indexingsettings.md b/16/umbraco-cms/reference/configuration/indexingsettings.md new file mode 100644 index 00000000000..66044d8daf7 --- /dev/null +++ b/16/umbraco-cms/reference/configuration/indexingsettings.md @@ -0,0 +1,27 @@ +--- +description: "Information on the indexing section" +--- + +# Indexing Settings + +This section allows you to configure how content is indexed for Examine. + +```json +"Umbraco": { + "CMS": { + "Indexing": { + "ExplicitlyIndexEachNestedProperty": true + } + } +} +``` +## ExplicitlyIndexEachNestedProperty + +When indexing content, each property contained within certain complex editors are indexed as separate fields by default. These complex editors include: + +- Block List +- Block Grid + +The complex editors are also indexed to their own separate fields, which then contains "the sum" of all properties contained within. + +In some cases this yields a lot of fields in the index, which can lead to errors when performing searches. Changing this setting to `false` can mend that issue. It prevents each contained property from being written to the index in its own field. diff --git a/16/umbraco-cms/reference/configuration/installdefaultdatasettings.md b/16/umbraco-cms/reference/configuration/installdefaultdatasettings.md new file mode 100644 index 00000000000..5c4ab958eff --- /dev/null +++ b/16/umbraco-cms/reference/configuration/installdefaultdatasettings.md @@ -0,0 +1,148 @@ +--- +description: >- + Information on configuration allowing for the modification of default data + installed in new projects +--- + +# Install Default Data Settings + +When Umbraco is installed for the first time, it creates a set of default data. These include a language, some Data Types, and some Media and Member Types. + +In certain setups, you may want to take control over what is installed and opt-out of the creation of certain items. + +When working in a team and using Umbraco Deploy for schema updates, consider your colleague's local project setup. The default installed data may not always be useful. + +For example, if different languages are set up in Umbraco, it's better not to recreate them from the default language (en-US). In other situations, certain Umbraco default Data, Member and Media Types may not be required. + +The following example configuration shows how this default data installation can be customized: + +```json +"Umbraco": { + "CMS": { + "InstallDefaultData": { + "Languages": { + "InstallData": "Values", + "Values": [ + "en-US" + ] + }, + "DataTypes": { + "InstallData": "ExceptValues", + "Values": [ + "0225af17-b302-49cb-9176-b9f35cab9c17" + ] + }, + "MediaTypes": { + "InstallData": "All", + }, + "MemberTypes": { + "InstallData": "None" + } + } + } +} +``` + +Each `InstallData` setting can be one of the following values: + +* `All` - all default data for the type will be installed (this is the default behavior if the configuration is omitted). +* `Values` - only the default data specified will be installed. For languages, the values are the ISO codes for the language. For all other types, the Guid for the type should be listed. +* `ExceptValues` - all default data except those specified will be installed. +* `None` - no default data of the type will be installed. + +{% hint style="warning" %} +Be cautious when changing a Data Type configuration, as there are some dependencies between the different types. Make sure to check the reference information in the `info` tab to ensure they are not referenced somewhere else. +{% endhint %} + +For example, if you check the info tab of the `Label (bigint)` Data Type, you can see that it is referenced by the `Media Types`: + +

Data Type referenced by Media Type

+ +## Data Identifiers + +For `DataTypes`, `MediaTypes` and `MemberTypes` the Guid identifiers for the default data items need to be provided in the `Values` collection. + +For `Languages`, the `Values` collection expects the standard language ISO codes to be provided. Given this code is enough to fully specify a language, it's possible to use this collection to install additional default data. + +As an example, the following configuration would omit the default "English (United States)" language and instead install the "English (United Kingdom)" and "Italian" languages. As "English (United Kingdom)" is provided first, it would be created as Umbraco's default language for content creation. + +```json +"Umbraco": { + "CMS": { + "InstallDefaultData": { + "Languages": { + "InstallData": "Values", + "Values": [ + "en-GB", + "it" + ] + } + } + } +} +``` + +## Reference + +The Guid values representing the default Data, Media, and Member Types installed are as follows. + +Data types: + +``` +ApprovedColor = 0225af17-b302-49cb-9176-b9f35cab9c17 +Checkbox = 92897bc6-a5f3-4ffe-ae27-f2e7e33dda49 +CheckboxList = fbaf13a8-4036-41f2-93a3-974f678c312a +ContentPicker = FD1E0DA5-5606-4862-B679-5D0CF3A52A59 +DatePicker = 5046194e-4237-453c-a547-15db3a07c4e1 +DatePickerWithTime = e4d66c0f-b935-4200-81f0-025f7256b89a +Dropdown = 0b6a45e7-44ba-430d-9da5-4e46060b9e03 +DropdownMultiple = f38f0ac7-1d27-439c-9f3f-089cd8825a53 +ImageCropper = 1df9f033-e6d4-451f-b8d2-e0cbc50a836f +LabelBigInt = 930861bf-e262-4ead-a704-f99453565708 +LabelDateTime = 0e9794eb-f9b5-4f20-a788-93acd233a7e4 +LabelDecimal = 8f1ef1e1-9de4-40d3-a072-6673f631ca64 +LabelInt = 8e7f995c-bd81-4627-9932-c40e568ec788 +LabelString = f0bc4bfb-b499-40d6-ba86-058885a5178c +LabelTime = a97cec69-9b71-4c30-8b12-ec398860d7e8 +ListViewContent = C0808DD3-8133-4E4B-8CE8-E2BEA84A96A4 +ListViewMedia = 3A0156C4-3B8C-4803-BDC1-6871FAA83FFF +ListViewMembers = AA2C52A0-CE87-4E65-A47C-7DF09358585D +MediaPicker = 135D60E0-64D9-49ED-AB08-893C9BA44AE5 +MediaPicker3 = 4309A3EA-0D78-4329-A06C-C80B036AF19A +MediaPicker3Multiple = 1B661F40-2242-4B44-B9CB-3990EE2B13C0 +MediaPicker3MultipleImages = 0E63D883-B62B-4799-88C3-157F82E83ECC +MediaPicker3SingleImage = AD9F0CF2-BDA2-45D5-9EA1-A63CFC873FD3 +Member = d59be02f-1df9-4228-aa1e-01917d806cda +MemberPicker = 1EA2E01F-EBD8-4CE1-8D71-6B1149E63548 +MultipleMediaPicker = 9DBBCBBB-2327-434A-B355-AF1B84E5010A +Numeric = 2e6d3631-066e-44b8-aec4-96f09099b2b5 +Radiobox = bb5f57c9-ce2b-4bb9-b697-4caca783a805 +RelatedLinks = B4E3535A-1753-47E2-8568-602CF8CFEE6F +RichtextEditor = ca90c950-0aff-4e72-b976-a30b1ac57dad +Tags = b6b73142-b9c1-4bf8-a16d-e1c23320b549 +Textarea = c6bac0dd-4ab9-45b1-8e30-e4b619ee5da3 +Textstring = 0cc0eba1-9960-42c9-bf9b-60e150b429ae +Upload = 84c6b441-31df-4ffe-b67e-67d5bc3ae65a +UploadArticle = bc1e266c-dac4-4164-bf08-8a1ec6a7143d +UploadAudio = 8f430dd6-4e96-447e-9dc0-cb552c8cd1f3 +UploadVectorGraphics = 215cb418-2153-4429-9aef-8c0f0041191b +UploadVideo = 70575fe7-9812-4396-bbe1-c81a76db71b5 +``` + +Media types: + +``` +MediaTypes.Article - a43e3414-9599-4230-a7d3-943a21b20122 +MediaTypes.Audio - a5ddeee0-8fd8-4cee-a658-6f1fcdb00de3 +MediaTypes.File - 4c52d8ab-54e6-40cd-999c-7a5f24903e4d +MediaTypes.Folder - f38bd2d7-65d0-48e6-95dc-87ce06ec2d3d +MediaTypes.Image - cc07b313-0843-4aa8-bbda-871c8da728c8 +MediaTypes.Video - f6c515bb-653c-4bdc-821c-987729ebe327 +"Vector Graphics (SVG)" - c4b1efcf-a9d5-41c4-9621-e9d273b52a9c +``` + +Member types: + +``` +MemberTypes.DefaultAlias - d59be02f-1df9-4228-aa1e-01917d806cda +``` diff --git a/16/umbraco-cms/reference/configuration/loggingsettings.md b/16/umbraco-cms/reference/configuration/loggingsettings.md new file mode 100644 index 00000000000..5a4eb24d4cf --- /dev/null +++ b/16/umbraco-cms/reference/configuration/loggingsettings.md @@ -0,0 +1,32 @@ +--- +description: "Information on the logging settings section." +--- + +# Logging settings + +The majority of logging related configuration has been moved to the Serilog configuration see [Serilog settings](serilog.md) for more information. + +The following configuration is available in the Logging settings: + +```json +"Umbraco": { + "CMS": { + "Logging": { + "MaxLogAge": "2.00:00:00", + "Directory": "~/CustomLogFileLocation" + } + } +} +``` + +## MaxLogAge + +This setting allows you to configure the maximum log age for the internal audit log scrubbing. The default maximum age for the internal audit log is 24 hours. Change the duration with the `MaxLogAge` key in the Logging settings. + +To increase the maximum age of the entries in the audit log to 48 hours (2 days), set the value to `2.00:00:00`. + +## Directory + +By default, all log files are saved to the `umbraco/Logs` directory. You can define a custom directory for your log files by using the `Directory` key in the Logging settings. + +Set the value to `~/LogFiles` to add all log files to a `LogFiles` directory in the root of the file structure. diff --git a/16/umbraco-cms/reference/configuration/maximumuploadsizesettings.md b/16/umbraco-cms/reference/configuration/maximumuploadsizesettings.md new file mode 100644 index 00000000000..b622d0a8e0d --- /dev/null +++ b/16/umbraco-cms/reference/configuration/maximumuploadsizesettings.md @@ -0,0 +1,55 @@ +--- +description: "Information on how to change the default cap of upload size" +--- + +Umbraco does not touch the default maximum allowed content size of the different services, but you can configure this yourself. + +# Using IIS + +To configure the default 28.6MB upload limit using IIS, we have to create a web.config file at the root of the project. It should contain this: + +```xml + + + + + + + + + + + +``` + +`maxAllowedContentLength` is specified in bytes, so this configuration would limit requests, and therefore uploaded files, to 2 megabytes + +{% hint style="info" %} +**Are you hosting your site on Umbraco Cloud?** + +Umbraco Cloud uses IIS for hosting. This means you need to add the setting in a `web.config` file for this to work on your Umbraco Cloud hosted sites. +The upload size limit is 500mb on Umbraco Cloud. +{% endhint %} + +# Using Kestrel + +Runtime settings allow you to configure the `MaxRequestLength` and `MaxQueryStringLength` for kestrel. If you want to upload files larger than 28.6MB, then you have to configure these settings. If nothing is configured requests and query strings can only be the default size and smaller. + +An example of a configuration could look something like this: + +```json +"Umbraco": { + "CMS": { + "Runtime": { + "MaxQueryStringLength": 90, + "MaxRequestLength": 2000 + } + } +} +``` + +`MaxRequestLength` is specified in kilobytes. This configuration will limit requests, and therefore uploaded files, to 2 megabytes, and a maximum query string length of 90 characters. + +## [Using Nginx (external)](https://nginx.org/en/docs/http/ngx_http_core_module.html#client_max_body_size) + +## [Using apache (external)](https://httpd.apache.org/docs/2.2/mod/core.html#limitrequestbody) diff --git a/16/umbraco-cms/reference/configuration/modelsbuildersettings.md b/16/umbraco-cms/reference/configuration/modelsbuildersettings.md new file mode 100644 index 00000000000..ca6f20a61b2 --- /dev/null +++ b/16/umbraco-cms/reference/configuration/modelsbuildersettings.md @@ -0,0 +1,74 @@ +--- +description: "Information on the models builder settings section" +--- + +# Models builder settings + +This section allows you to configure the Umbraco models builder, a complete section with default values can be seen here: + +```json +"Umbraco": { + "CMS": { + "ModelsBuilder": { + "ModelsMode": "InMemoryAuto", + "ModelsNamespace": "Umbraco.Cms.Web.Common.PublishedModels", + "FlagOutOfDateModels": false, + "ModelsDirectory": "~/umbraco/models", + "AcceptUnsafeModelsDirectory": false, + "DebugLevel": 0, + "IncludeVersionNumberInGeneratedModels": true + } + } +} +``` + +Let's go through them one by one. + +## Models mode + +Specifies how the models builder will generate models and when to generate them. The options are: + +* `Nothing` - The modelsbuilder will not generate any models, this means that all views will use IPublishedContent, instead of strongly typed models. +* `InMemoryAuto` - Models will automatically be generated each time a content type change occurs, and will then be compiled, and loaded into memory dynamically. This means that the models are only available in views, however they will be available instantly. +* `SourceCodeManual` - Models will be generated as `.cs` files whenever a user clicks the "Generate models" button on the models builder dashboard - however, the models will not be compiled and loaded into memory dynamically. This means that models are available to edit within the project. The project needs to be recompiled and restarted for the new models, or model changes, to take effect. +* `SourceCodeAuto` - This mode behaves the same as `SourceCodeManual` with one difference, the generation of models happens automatically every time a content type change occurs. + +{% hint style="info" %} +When using Models Builder it is best practice to use the "Nothing" setting for all `appsettings.json` files. If needed, the models mode can then be set to "SourceCodeManual" or "SourceCodeAuto" In the `appsettings.json` file used on the local environment. +{% endhint %} + +## Models namespace + +This setting allows you to customize the namespace of the generated models, for instance you might want to change this to something that aligns better with your project structure, such as `MySite.ContentModels`. + +## Flag out of date models + +This setting allows you to specify if a model should be flagged as out of date if its content type, or a datatype the content type depends on, are changed. When a model is flagged as out of date you will be able to see that you need to regenerated models in modelsbuilder dashboard. + +This setting is only really relevant if you use the `SourceCodeManual` models mode, since otherwise the models will be automatically regenerated, and will therefore never be out of date. + +If you set this setting to true while using an `Auto` mode, it will automatically be interpreted as false. + +## Models directory + +Allows you to specify a custom directory for your generated models. By default this settings has to be a virtual directory, that is, it must start with `~/`, if needed `AccceptUnsafeModelsDirectory` can be set to true, to allow the path to be outside the website root, be aware though that this is a potential security risk. + +{% hint style="info" %} +If you want to generate models outside the web project you can change the ModelsDirectory path. Suppose you have a data project called My.Website.Data the ModelsDirectory path should be: + +`~/../My.Website.Data/Models/` +{% endhint %} + +## Accept unsafe models directory + +By setting this to true, you specify that the models directory is allowed to be outside the websites root. This is not allowed by default since it can be a potential security risk. + +## Debug level + +This setting specifies the logging level for the models builder. By default this is set to 0, which means minimal logging. Anything higher that 0 means increased logging. Be aware that this setting should only be set to something higher than 0 for development use, not on live sites. + +## Include version number in generated models + +When source code options are used, the Umbraco version number written to the generated code for each property of the model. This can be useful for debugging purposes but isn't essential. It causes the generated code to change every time Umbraco is upgraded and models are regenerated. In turn, this leads unnecessary code file changes that need to be checked into source control. + +If you prefer to exclude this version number from being written to the generated code, set this value to `false`. \ No newline at end of file diff --git a/16/umbraco-cms/reference/configuration/packagemigrationsettings.md b/16/umbraco-cms/reference/configuration/packagemigrationsettings.md new file mode 100644 index 00000000000..0fb145ea9c2 --- /dev/null +++ b/16/umbraco-cms/reference/configuration/packagemigrationsettings.md @@ -0,0 +1,43 @@ +--- +description: "Information on the package migration settings section" +--- + +# Package Migration + +This settings section provides control over how package migrations are executed in different environments (local, development, live etc.) + +Package migrations are defined by package developers allowing them to add functionality to their package that enhances the Umbraco CMS. There can be various types of migrations applied, including creating custom database tables and installing Umbraco schema and content. + +They run on start-up, thus ensuring that the functionality of the package has the necessary infrastructure, schema, and content in place when it is used. + +Migration steps that are explicitly created by the package developer to make database schema changes will always run in all environments. + +Depending on your workflow, for those steps that install Umbraco data - whether schema such as document types, content, or media - you may want them run in all environments, or you may prefer to only do this in certain ones. + +The default behavior for Umbraco CMS is for all package migrations to run in all environments. + +If using Umbraco Cloud, the default is to run package migrations fully _only in local environments_. By doing this, for schema, `.uda` files will be generated in the `/umbraco/Deploy/Revision` folder, which when pushed to a Cloud environment will be used to install the schema and content there. For content and media, the "queue for transfer" operation can be used. With this behavior, we avoid any issues caused by both a package migration and a deployment operation attempting to create schema and content. + +If different behavior is required, or if using Umbraco Deploy On-Premises, the following settings can be applied: + +```json +{ + "$schema": "https://json.schemastore.org/appsettings.json", + "Umbraco": { + "CMS": { + "PackageMigration": { + "RunSchemaAndContentMigrations": true, + "AllowComponentOverrideOfRunSchemaAndContentMigrations": true + } + } + } +} +``` + +## RunSchemaAndContentMigrations + +If set to `true`, the default behavior described above for Umbraco CMS on-premises and Umbraco Cloud will be applied. By setting to `false`, the installation of Umbraco schema and content from a package migration will be skipped. If missing the default value is `true`. + +## AllowComponentOverrideOfRunSchemaAndContentMigrations + +If this is set to the default value of `true`, Umbraco Cloud (or other deployment tools) can override the configured value of `RunSchemaAndContentMigrations` as is appropriate for their operation. By setting to `false` such tools should respect this setting, not make any overrides and use the configured value. diff --git a/16/umbraco-cms/reference/configuration/pluginssettings.md b/16/umbraco-cms/reference/configuration/pluginssettings.md new file mode 100644 index 00000000000..3a503f35385 --- /dev/null +++ b/16/umbraco-cms/reference/configuration/pluginssettings.md @@ -0,0 +1,26 @@ +--- +description: Information on the plugins settings section +--- + +# Plugins settings + +The Plugins settings allow you to configure how Umbraco handles plugins. Currently, you can only configure browsable file extensions, which allows you to customize what file types plugins are allowed to use for the front end. The default configuration looks like this: + +```json +"Umbraco": { + "CMS": { + "Plugins": { + "BrowsableFileExtensions": [ + ".html", + ".css", + ".js", + ".jpg", ".jpeg", ".gif", ".png", ".svg", + ".eot", ".ttf", ".woff", ".woff2", + ".xml", ".json", ".config", + ".lic"] + } + } +} +``` + +As you can see above, by default, markup, styles, scripts, images, fonts, configurations, and license type are included. If you were to, for example, remove the ".html" entry, then plugins would no longer be allowed to use HTML files. diff --git a/16/umbraco-cms/reference/configuration/requesthandlersettings.md b/16/umbraco-cms/reference/configuration/requesthandlersettings.md new file mode 100644 index 00000000000..c687f5f5ee5 --- /dev/null +++ b/16/umbraco-cms/reference/configuration/requesthandlersettings.md @@ -0,0 +1,161 @@ +--- +description: Information on the request handler settings section +--- + +# Request handler settings + +The options in the request handler let us do some useful things. It could be deciding whether or not to use trailing slashes and setting URL replacement for special characters. + +Let's have a further look at each option below. + +Here is a snippet containing all the default values of the `RequestHandler` section. + +```js +"Umbraco": { + "CMS": { + "RequestHandler": { + "AddTrailingSlash": true, + "ConvertUrlsToAscii": "try", + "ConvertFileNamesToAscii": "false", + "EnableDefaultCharReplacements": true, + "UserDefinedCharCollection": [ + { + "Char": " ", + "Replacement": "-" + }, + { + "Char": "\\", + "Replacement": "" + }, + { + "Char": "'", + "Replacement": "" + }, + { + "Char": "%", + "Replacement": "" + }, + { + "Char": ".", + "Replacement": "" + }, + { + "Char": ";", + "Replacement": "" + }, + { + "Char": "/", + "Replacement": "" + }, + { + "Char": "\\\\", + "Replacement": "" + }, + { + "Char": ":", + "Replacement": "" + }, + { + "Char": "#", + "Replacement": "" + }, + { + "Char": "+", + "Replacement": "plus" + }, + { + "Char": "*", + "Replacement": "star" + }, + { + "Char": "&", + "Replacement": "" + }, + { + "Char": "?", + "Replacement": "" + }, + { + "Char": "æ", + "Replacement": "ae" + }, + { + "Char": "ä", + "Replacement": "ae" + }, + { + "Char": "ø", + "Replacement": "oe" + }, + { + "Char": "ö", + "Replacement": "oe" + }, + { + "Char": "å", + "Replacement": "aa" + }, + { + "Char": "ü", + "Replacement": "ue" + }, + { + "Char": "ß", + "Replacement": "ss" + }, + { + "Char": "|", + "Replacement": "-" + }, + { + "Char": "<", + "Replacement": "" + }, + { + "Char": ">", + "Replacement": "" + } + ] + } + } +} +``` + +## Add trailing slash + +This will add a trailing slash to the URL when **``** is set to "true". If you don't want to have a trailing slash, set the value to **false**. + +### Convert URLs to ASCII + +This setting tells Umbraco to convert all URLs to ASCII: American Standard Code for Information Interchange, if set to false the URLs will remain `UTF-8`. + +This setting can be set to **try** This will make the engine try to convert the name to an ASCII implementation. If it fails, it will fallback to the name. Reason is that some languages don't have ASCII implementations, therefore the URLs would end up being empty. + +### Convert file names to ASCII + +This setting works the same as "Convert URLs to ASCII" above, but for Media item file names. + +### Enable default character replacements + +This setting tells Umbraco to use the default character replacements. If you don't want the default character replacements, set this to false. + +### Char collection + +This settings contains objects with a **"Char"** and **"Replacement** key. Each of these objects holds a character that should be replaced and what it should be replaced with. + +When something like the following is added to the list, the **ñ** will be shown as a **n** in the URL. + +```json +{ + "Char": "ñ", + "Replacement": "n" +} +``` + +{% hint style="info" %} +When managing entries from the collection, you need to do a couple of things for the change to be reflected: + +1. Refresh the database cache from the "Published Status" tab in the **Settings** section of the backoffice. +2. Re-save and publish any node where you want the change reflected. + * You can also use **Publish with descendants...** on the root node to update the URL for all descendants. +{% endhint %} diff --git a/16/umbraco-cms/reference/configuration/runtimesettings.md b/16/umbraco-cms/reference/configuration/runtimesettings.md new file mode 100644 index 00000000000..bae2d7afacd --- /dev/null +++ b/16/umbraco-cms/reference/configuration/runtimesettings.md @@ -0,0 +1,31 @@ +--- +description: "Information on the runtime settings section" +--- + +# Runtime settings + +In the Runtime settings you can configure: + +- Size limits for requests and query strings. Neither of these settings needs to be configured. If nothing is configured, requests and query strings can be any size. +- The runtime mode of Umbraco. +- The lifetime of temporary file uploads. This is primarily used when uploading images and other media in the backoffice. + +An example of a configuration could look something like: + +```json +"Umbraco": { + "CMS": { + "Runtime": { + "MaxQueryStringLength": 90, + "MaxRequestLength": 2048, + "Mode": "BackofficeDevelopment", + "TemporaryFileLifeTime": "1.00:00:00" + } + } +} +``` + +- `MaxRequestLength` is specified in kilobytes. Setting this limits the request size, including the size of uploaded files. +- `MaxQueryStringLength` is specified in number of characters. Setting this limits the maximum query string length. +- `Mode` can have three values: `BackofficeDevelopment` (default), `Development`, and `Production`. For more information, see the [Runtime modes](../../fundamentals/setup/server-setup/runtime-modes.md) article. +- `TemporaryFileLifeTime` is specified as a timespan. The default value is one day - `1.00:00:00`. diff --git a/16/umbraco-cms/reference/configuration/securitysettings.md b/16/umbraco-cms/reference/configuration/securitysettings.md new file mode 100644 index 00000000000..0af7a3d405c --- /dev/null +++ b/16/umbraco-cms/reference/configuration/securitysettings.md @@ -0,0 +1,150 @@ +--- +description: Information on the security settings section +--- + +# Security Settings + +The options in the security section allows you to configure all things security, whether to keep users logged in, password rules and more. + +A full configuration with all default values can be seen here: + +```json +"Umbraco": { + "CMS": { + "Security": { + "KeepUserLoggedIn": false, + "HideDisabledUsersInBackOffice": false, + "AllowPasswordReset": true, + "AuthCookieName": "UMB_UCONTEXT", + "AuthCookieDomain": "", + "UsernameIsEmail": true, + "MemberRequireUniqueEmail": true, + "AllowedUserNameCharacters": "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._@+\\", + "UserPassword": { + "RequiredLength": 10, + "RequireNonLetterOrDigit": false, + "RequireDigit": false, + "RequireLowercase": false, + "RequireUppercase": false, + "HashAlgorithmType": "PBKDF2.ASPNETCORE.V3", + "MaxFailedAccessAttemptsBeforeLockout": 5 + }, + "MemberPassword": { + "RequiredLength": 10, + "RequireNonLetterOrDigit": false, + "RequireDigit": false, + "RequireLowercase": false, + "RequireUppercase": false, + "HashAlgorithmType": "PBKDF2.ASPNETCORE.V3", + "MaxFailedAccessAttemptsBeforeLockout": 5 + }, + "UserDefaultLockoutTimeInMinutes": 43200, + "MemberDefaultLockoutTimeInMinutes": 43200, + "AllowConcurrentLogins": false, + "UserDefaultFailedLoginDurationInMilliseconds": 1000, + "UserMinimumFailedLoginDurationInMilliseconds": 250, + } + } +} +``` + +## Root level settings + +At the root level of security you can configure the following + +### Keep user logged in + +When set to false a user will be logged out after a specific amount of time has passed with no activity. You can specify this time span in the [global settings](globalsettings.md) with the `TimeOut` key. + +### Hide disabled users in backoffice + +When this is set to "true" it's not possible to see disabled users. This means it's not possible to re-enable their access to the backoffice again. It also means you can't create an identical username if the user was disabled by a mistake. + +### Allow password reset + +This feature allows users to reset their passwords if they have forgotten them. By default, this is enabled. It can be disabled at both the UI and API level by setting this value to "false". + +### Auth cookie name + +The authentication cookie which is set in the browser when a backoffice user logs in, and defaults to `UMB_UCONTEXT`. + +### Auth cookie domain + +The authentication cookie which is set in the browser when a backoffice user logs in is automatically set to the current domain. + +### Username is email + +This setting specifies whether the username and email address are separate fields in the backoffice editor. When set to "false", you can specify an email address and username, only the username can be used to log on. When set to "true" (the default value) the username is hidden and always the same as the email address. + +### Member require unique email + +By default Umbraco will not allow creation of more than one member account with the same email address. If you wish to allow this, set this value to `false`. + +### Allowed user name characters + +Defines the allowed characters for a username. + +### User default lockout time + +Use this setting to configure how long time a User is locked out of the Umbraco backoffice when a lockout occurs. The setting accepts an integer which defines the lockout in minutes. + +The default lockout time for users is 30 days (43200 minutes). + +### Member default lockout time + +Use this setting to configure how long time a Member is locked out of the Umbraco website when a lockout occurs. The setting accepts an integer which defines the lockout in minutes. + +The default lockout time for users is 30 days (43200 minutes). + +### Allow concurrent logins + +When set to `false`, any user account is prevented from having multiple simultaneous sessions. In this mode, only one session per user can be active at any given time. This enhances security and prevents concurrent logins with the same user credentials. + +### User login duration + +Umbraco provides protection from user enumeration attacks looking to identify valid backoffice login accounts. It does this by attempting to equalize the time taken for successful and failed logins. + +The `UserDefaultFailedLoginDurationInMilliseconds` can be used to provide a more realistic expected time for a successful login if the default isn't appropriate. This will be used before actual successful logins are detected. `UserMinimumFailedLoginDurationInMilliseconds` provides a minimum duration for a failed login. + +## User password settings + +This section lets you define the password rules for users. + +### Required length + +Specifies the minimum length a user password is allowed to be. + +### Require non letter or digit + +Requires a users password to contain at least one character which is not a letter or a digit if enabled. + +### Require digit + +Requires a users password to contain at least one digit if enabled. + +### Require lowercase + +Requires a users password to contain at least one lowercase letter if enabled. + +### Require uppercase + +Requires a users password to contain at least one uppercase letter if enabled. + +### Max failed access attempts before lockout + +Specifies the max amount of failed password attempts is allowed before the user is locked out of the site. + +### Hash algorithm type + +Allows you to specify what hashing algorithm should be used to store the users password. + +Options are: + +* `"PBKDF2.ASPNETCORE.V3"` +* `"PBKDF2.ASPNETCORE.V2"` +* `"HMACSHA256"` +* `"HMACSHA1"` + +## Member password settings + +This section allows you to define the password rules for members. This section is identical to the one for users. diff --git a/16/umbraco-cms/reference/configuration/serilog.md b/16/umbraco-cms/reference/configuration/serilog.md new file mode 100644 index 00000000000..6bdb8396aa7 --- /dev/null +++ b/16/umbraco-cms/reference/configuration/serilog.md @@ -0,0 +1,143 @@ +--- +description: "Information on the serilog settings section" +--- + +# Serilog settings + +Umbraco uses Serilog as its logging library, this means that the configuration of logging is offloaded to Serilog instead of the CMS. This means that logging specific configuration is not in the `Umbraco.Cms` section, but instead the Serilog section. + +We will go through some of the more common logging configurations here, but for more information see the [Serilog settings GitHub project](https://github.com/serilog/serilog-settings-configuration). + +## Default configuration + +When you create a new Umbraco project the following Serilog section will be included by default: + +```json +"Serilog": { + "MinimumLevel": { + "Default": "Information", + "Override": { + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information", + "System": "Warning" + } + } +} +``` + +## Specifying minimum log level + +As you can see above, the CMS comes with a default Serilog config that defines the minimum log level with the `"MinimumLevel"` key. + +You can specify the overall minimum log level with the `"Default"` key. This will apply to all namespaces, however it's also possible to override this log level for specific names spaces with the `"Override"` key. In the example above, any logging coming from the `Microsoft` and `System` namespaces will only log warnings and up, however the `Microsoft.Hosting.Lifetime` namespace will log information and up. + +The possible values, from most verbose to least, is: + +* Verbose +* Debug +* Information +* Warning +* Error +* Fatal + +For information on what each of these levels means see [the Serilog wiki](https://github.com/serilog/serilog/wiki/Configuration-Basics#minimum-level). + + +## Changing the log level for specific namespaces + +This can be done by updating the appsettings.json configuration file to specify namespaces in which you want to change the log level for. + +```json + "Serilog": { + "MinimumLevel": { + "Default": "Information", + "Override": { + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information", + "System": "Warning", + "Examine.Lucene.Providers.LuceneIndex": "Debug", + "Examine.BaseIndexProvider": "Debug", + "MyNamespace": "Verbose" + } + } + } +``` + +## Logging to a different output + +Serilog has the ability to log to a number of different mechanisms, from console to files, even to Slack or email. This is all configured using what Serilog calls sinks. + +An example of this can be seen in the default `appsettings.Development.json`, where Serilog is configured to log to the console using the Async wrapper sink: + +```json + "Serilog": { + "MinimumLevel": { + "Default": "Information" + }, + "WriteTo": [ + { + "Name": "Async", + "Args": { + "configure": [ + { + "Name": "Console" + } + ] + } + } + ] + } +``` + +Here you can see that we use the `"WriteTo"` key to specify a list of sinks the logger should write to. In this case we use the `"Async"` sink configured to write to the console, this means that we'll log to the console asynchronously. + +Now there's too many sinks to cover here, for a full list of all available sinks see [Provided sinks](https://github.com/serilog/serilog/wiki/Provided-Sinks#list-of-available-sinks). Each of these entries will have their own documentation on how to set up the logging with the particular sink. + +## Changing the Umbraco 'sink' +By default, Umbraco uses a special Serilog 'sink' that is optimized for performance. To change parameters for only this sink, but not the default. For E.g higher log level for this compared to other sinks you can do it in the following way: + +```json + "Serilog": { + "MinimumLevel": { + "Default": "Information" + }, + "WriteTo": [ + { + "Name": "UmbracoFile", + "Args": { + "RestrictedToMinimumLevel": "Warning", + "FileSizeLimitBytes": 1073741824, + "RollingInterval" : "Day", + "FlushToDiskInterval": null, + "RollOnFileSizeLimit": false, + "RetainedFileCountLimit": 31 + } + } + ] + } +``` + +## Adding a custom log property to all log items + +You may wish to add a log property to all log messages. A good example could be a log property for the `environment` to determine if the log message came from `development` or `production`. + +This is useful when you could be writing logs from all environments or multiple customer projects into a single logging source, such as Elasticsearch. This would allow you to search and filter for a specific project and its environment to see the log messages. You can also reference your hosting server's environment variables in the property values. + +In the `appsettings.json` configuration file you can add the following lines + +```json + "Serilog": { + "MinimumLevel": { + "Default": "Information", + "Override": { + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information", + "System": "Warning" + } + }, + "Properties": { + "Customer": "Super Customer", + "Environment": "Production" + } + } +``` diff --git a/16/umbraco-cms/reference/configuration/typefindersettings.md b/16/umbraco-cms/reference/configuration/typefindersettings.md new file mode 100644 index 00000000000..070d6b315c2 --- /dev/null +++ b/16/umbraco-cms/reference/configuration/typefindersettings.md @@ -0,0 +1,20 @@ +--- +description: "Information on the type finder settings section" +--- + +# Type finder settings + +The type finder settings allows you to specify assemblies that accept load exceptions when they are type scanned. For multiple assemblies separate them with a comma (`,`). +To accept load exceptions for all assemblies use an asterisk (`*`), like so: + +Furthermore it is possible to add additional assemblies to the exclusion filter. Thereby these assemblies will be ignored by Umbraco. This can be useful depending on nuget packages that are not Umbraco packages. +```json +"Umbraco": { + "CMS": { + "TypeFinder": { + "AssembliesAcceptingLoadExceptions": "*", + "AdditionalAssemblyExclusionEntries": [] + } + } +} +``` diff --git a/16/umbraco-cms/reference/configuration/unattendedsettings.md b/16/umbraco-cms/reference/configuration/unattendedsettings.md new file mode 100644 index 00000000000..277d4aaf748 --- /dev/null +++ b/16/umbraco-cms/reference/configuration/unattendedsettings.md @@ -0,0 +1,63 @@ +--- +description: "Information on the unattended settings section" +--- + +# Unattended + +This settings lets you configure the unattended install & upgrade settings. This is a feature that allows Umbraco to install without using the UI. If you don't intended to use this feature, you don't need to configure this. + +It's important to know that the install feature will only work if there is a connection string configured pointing to an empty database. A configuration for unattended install & upgrade can look something like: + +```json +{ + "$schema": "https://json.schemastore.org/appsettings.json", + + "ConnectionStrings": { + "umbracoDbDSN": "Server=.;Database=DocsSite;Integrated Security=true" + }, + "Umbraco": { + "CMS": { + "Unattended": { + "InstallUnattended": true, + "PackageMigrationsUnattended": true, + "UpgradeUnattended": true, + "UnattendedUserName": "A.N. Other", + "UnattendedUserEmail": "AN@Other.com", + "UnattendedUserPassword": "APasswordMeetingRequirements" + } + } + } +} +``` + +This will automatically install Umbraco to the `DocsSite` database on the local SQL server, and will also automatically upgrade whenever there is an upgrade to install. + +It is generally not recommended to keep user credentials in config files, therefore we recommend using environment variables to configure these settings. + +Let's go through the settings one by one + +## Install unattended + +Umbraco will only automatically install if this is set to true, and if there is a connection string pointing to an empty database. + +## Upgrade unattended + +If this is set to true, Umbraco will automatically run the upgrade migrations once the site has been upgraded. + +## Unattended user name + +This setting is used to specify the user name of the default admin user. + +## Unattended email + +This setting is used to specify the email address of the default admin user. + +## Unattended user pass + +This setting is used to specify the password of the default admin user. + +### Package migrations unattended + +Gets or sets a value indicating whether unattended package migrations are enabled. + +This is true by default. diff --git a/16/umbraco-cms/reference/configuration/webroutingsettings.md b/16/umbraco-cms/reference/configuration/webroutingsettings.md new file mode 100644 index 00000000000..77b01924975 --- /dev/null +++ b/16/umbraco-cms/reference/configuration/webroutingsettings.md @@ -0,0 +1,106 @@ +--- +description: Information on the web routing settings section +--- + +# Web routing + +This section allows you to configure routing for your solution, all of these settings have either default values, or do not need to be configured. However, you might want to tweak these settings in some scenarios, for instance, if you're running in a load-balanced setup. + +An example of a web routing config with default values, and a placeholder for the application URL can be seen here: + +```json +"Umbraco": { + "CMS": { + "WebRouting": { + "TryMatchingEndpointsForAllPages": false, + "TrySkipIisCustomErrors": false, + "InternalRedirectPreservesTemplate": false, + "DisableAlternativeTemplates": false, + "ValidateAlternativeTemplates": false, + "DisableFindContentByIdPath": false, + "DisableRedirectUrlTracking": false, + "UrlProviderMode": "Auto", + "UmbracoApplicationUrl": "http://www.mysite.com/" + } + } +} +``` + +{% hint style="warning" %} +**Are you on Umbraco Cloud?** + +It is not possible to change the `UmbracoApplicationUrl setting` because the value is overwritten to the default Umbraco URL: `https://[project-alias].[region].umbraco.io/` on Umbraco Cloud. +{% endhint %} + +## Using the webrouting options in code + +The following is an example of how you can use code to get the value for the `UmbracoApplicationUrl` configuration key: + +```csharp +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; + +namespace UmbracoDocs; + +public class TestService +{ + private readonly IOptionsMonitor _webRoutingSettings; + + public TestService(IOptionsMonitor webRoutingSettings) + { + _webRoutingSettings = webRoutingSettings; + } + + public void DocsExample() + { + var umbracoApplicationUrl = _webRoutingSettings.CurrentValue.UmbracoApplicationUrl; + } +} +``` + +## Try matching endpoints for all pages + +When set to `true` Umbraco will check if any routed endpoints match a front-end request. This happens before the Umbraco dynamic router tries to map the request to a Umbraco content item. This setting should not be necessary as long as the Umbraco catch-all route is registered last. + +## Try skip IIS custom errors + +Defines the value of Response.TrySkipIisCustomErrors when an error (404, 400, 500...) is encountered. In order to prevent IIS from displaying its own 404 or 500 pages, set this to `true` to have your own page displayed. + +## Internal redirect preserves template + +When true, an internal redirect does not reset the alternative template, if any. + +## Disable alternative templates + +When true, the entire alternative templates feature of Umbraco is disabled. + +**validateAlternativeTemplates** will not load the template from the database. If `false` the template might not exists in the database. Otherwise the template need to exist in the database. + +## Validate alternative templates + +If set to true alternative templates will be validated + +## Disable find content by ID path + +When true, content can't be found by their ID meaning that urls such as /1234 do _not_ find content with ID 1234. + +## Disable redirect URL tracking + +When you move and rename pages in Umbraco, 301 permanent redirects are automatically created, set this to true if you do not want this behavior. + +## URL provider mode + +Will set the URL provider mode, options are: + +* `Default`: Indicates that the URL provider should do what it has been configured to do. +* `Relative`: Indicates that the URL provider should produce relative URLs exclusively. +* `Absolute`: Indicates that the URL provider should produce absolute URLs exclusively. +* `Auto`: Indicates that the URL provider should determine automatically whether to return relative or absolute URLs. + +## Umbraco application URL + +Defines the Umbraco application URL that the server should reach itself. By default, Umbraco will guess that URL from the first request made to the server. Use this setting if the guess is not correct (because you are behind a load-balancer, for example). Format is: `http://www.mysite.com/`, ensure to contain the scheme (http/https) and complete hostname. + +{% hint style="info" %} +Previously before v9, it was required to specify **backofffice** path as this was customizable (`/umbraco` by default). However, from v9+ this is no longer possible, so it's sufficient to use the URL that contains the scheme (http/https) and complete hostname. +{% endhint %} diff --git a/16/umbraco-cms/reference/content-delivery-api/README.md b/16/umbraco-cms/reference/content-delivery-api/README.md new file mode 100644 index 00000000000..66809fb59a2 --- /dev/null +++ b/16/umbraco-cms/reference/content-delivery-api/README.md @@ -0,0 +1,623 @@ +--- +description: Get started with the Content Delivery API. +--- + +# Content Delivery API + +The Content Delivery API delivers headless capabilities built directly into Umbraco. It allows you to retrieve your content items in a JSON format and use your preferred technology to present them in different channels. + +The feature preserves a friendly editing experience in Umbraco while ensuring a performant delivery of content in a headless fashion. With the different extension points, you can tailor the API to fit a broad range of requirements. + +## Getting Started + +The Delivery API is an opt-in feature in Umbraco. It must be explicitly enabled through configuration before it can be utilized. + +### Enable the Content Delivery API + +When creating your project, you can enable the Delivery API using the `--use-delivery-api` or `-da` flag. This will automatically add the necessary configuration to your project. + +```bash +dotnet new umbraco -n MyProject -da +``` + +You can also enable the Delivery API at a later point by following these steps: + +1. Open your project's `appsettings.json`. +2. Insert the `DeliveryApi` configuration section under `Umbraco:CMS`. +3. Add the `Enabled` key and set its value to `true`. +4. Open `Program.Cs` +5. Add `.AddDeliveryApi()` to `builder.CreateUmbracoBuilder()` + +{% code title="appsettings.json" %} +```json +{ + "Umbraco": { + "CMS": { + "DeliveryApi": { + "Enabled": true + } + } + } +} +``` +{% endcode %} + +{% code title="Program.cs" %} +```csharp +builder.CreateUmbracoBuilder() + .AddBackOffice() + .AddWebsite() + .AddDeliveryApi() + .AddComposers() + .Build(); + +``` +{% endcode %} + +Once the Content Delivery API is enabled, the next step is to rebuild the Delivery API content index (_DeliveryApiContentIndex_). This can be done using the **Examine Management** dashboard in the **Settings** section of the Umbraco Backoffice. + +1. Access the Umbraco Backoffice. +2. Navigate to the **Settings** section. +3. Open the **Examine Management** dashboard. +4. Scroll down to find the **Tools**. + +

Use the "Rebuild index" button under Tools on the Examine Management dashboard in the Settings section.

+ +5. Use the **Rebuild index** button. + +Once the index is rebuilt, the API can serve the latest content from the multiple-items endpoint. + +### Additional configuration + +When the Delivery API is enabled in your project, all your published content will be made available to the public by default. + +A few additional configuration options will allow you to restrict access to the Delivery API endpoints and limit the content returned. + +{% code title="appsettings.json" %} +```json +{ + "Umbraco": { + "CMS": { + "DeliveryApi": { + "Enabled": true, + "PublicAccess": true, + "ApiKey": "my-api-key", + "DisallowedContentTypeAliases": ["alias1", "alias2", "alias3"], + "RichTextOutputAsJson": false + } + } + } +} +``` +{% endcode %} + +Find a description of each of the configuration keys in the table below. + +
Configuration keyDescription
PublicAccessDetermines whether the enabled Delivery API should be publicly accessible or if access should require an API key.
ApiKeySpecifies the API key needed to authorize access to the API when public access is disabled. This setting is also used to access draft content for preview.
DisallowedContentTypeAliases Contains the aliases of the content types that should never be exposed through the Delivery API, regardless of any other configurations.
RichTextOutputAsJsonEnable outputting rich text content as JSON rather than the default HTML output. JSON can be a preferred format in many scenarios, not least because it supports the routing of internal links better than HTML does.
+ +{% hint style="info" %} +**Are you using Umbraco Cloud?** + +When hosting your Umbraco website on Umbraco Cloud, security should always be prioritized for sensitive information like API keys. Rather than storing it as plain text in the `appsettings.json` file use the Umbraco Cloud's built-in secrets management. This feature lets you store and manage sensitive data, keeping your API key safe from potential exposure or unauthorized access. To learn more about implementing secrets management, read the [Secrets management documentation](https://docs.umbraco.com/umbraco-cloud/set-up/project-settings/secrets-management#accepted-prefixes). +{% endhint %} + +{% hint style="info" %} +To test the functionality of the API, you need to create some content first. +{% endhint %} + +## Concepts + +Before exploring the API endpoints detailed below, there are a few concepts to know. + +
+ +Content item JSON structure + +The Delivery API outputs the JSON structure outlined below to represent the retrieved content items, which consist of a range of properties: + +* Basic properties for any content item include `name`, `createDate`, `updateDate`, `id` and `contentType`. +* All editorial properties from the content type can be found in the `properties` collection. Depending on the configured property editor, the property output value can be one of the following: + * String: `"This is a string"` + * Number: `1234` + * Boolean expression: `true` + * Array: `["hello", "world"]` + * Object: `{myObject}` + * Empty: _`null`_ +* The `route` property provides the `path` to the content item and details about the root node value represented by the `startItem` object. We will discuss the concept of a [`startItem`](./#start-item) in more detail in the next section. +* If the content item varies by culture, the `cultures` property will contain information about all configured cultures for the content node. This includes the culture-variant `path` and `startItem` for each culture. + +```json +{ + "name": "string", + "createDate": "2023-06-23T11:31:07.281Z", + "updateDate": "2023-06-23T11:31:07.281Z", + "route": { + "path": "string", + "startItem": { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "path": "string" + } + }, + "id": "11fb598b-5c51-4d1a-8f2e-0c7594361d15", + "contentType": "string", + "properties": { + "property1Alias": "string", + "property2Alias": 0, + "property3Alias": true, + "property4Alias": [], + "property5Alias": {}, + "property6Alias": null + }, + "cultures": { + "cultureIsoCode1": { + "path": "string", + "startItem": { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "path": "string" + } + }, + "cultureIsoCode2": { + "path": "string", + "startItem": { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "path": "string" + } + } + } +} +``` + +
+ +
+ +Start item + +When working headlessly in a multi-site setup, it can be difficult to determine the site context of a particular content item. This is where the **start item** comes into play. + +The start item represents the root of a content item and is commonly returned from the API in conjunction with a content path. For instance: + +```json +{ + "route": { + "path": "/articles/2023/getting-started", + "startItem": { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "path": "docs-portal" + } + } +} +``` + +This means that the content item resides under the `docs-portal` root node and can be retrieved using the path `/articles/2023/getting-started`. + +The start item can also be helpful through the `Start-Item` request header when obtaining content from the Delivery API. Supplying a root node `id` or `path` as the header value, allows you to specify the starting point for the requested content operation: + +```http +GET /umbraco/delivery/api/v2/content/item/articles/2023/getting-started +Start-Item: docs-portal +``` + +
+ +
+ +Property expansion and limiting + +Property expansion and limiting allows you to: + +* Include properties from related content or media in the API output for a given content item. +* Limit the content properties in the API output. + +By default, a content property that allows picking a different content item (for example a content picker) outputs a "shallow" representation of the picked item. This means that the output only includes basic information about the picked item, excluding the item properties. + +When property expansion is applied to the content property, the properties of the picked item are included in the output. This functionality applies to media items and block editors, as well. + +Property expansion can also be applied to expanded properties, thus obtaining nested property expansion. As a result, the output grows in size, and this is where property limiting comes into the picture. + +All content properties (including expanded properties) are part of the output by default. Property limiting allows for limiting which content properties are included. This means we can tailor the output to concrete use cases without over-fetching. + +Property expansion and limiting can be used when querying for single and multiple content or media items. You can expand properties by adding an `expand` parameter to the query and limit them using the `fields` query parameter. + +Refer to the [Property Expansion and Limiting](property-expansion-and-limiting.md) article for an in-depth explanation of this feature. + +
+ +
+ +Preview + +Similar to the preview concept in Umbraco, the Delivery API allows for requesting unpublished content through its endpoints. + +This is done by setting a `Preview` header to `true` in the API request. + +Accessing unpublished versions of your content nodes requires authorization via an API key configured in `appsettings.json` file using the `Umbraco:CMS:DeliveryApi:ApiKey` configuration key. To obtain preview data, you must add the `Api-Key` request header containing the configured API key to the appropriate endpoints. + +```http +GET /umbraco/delivery/api/v2/content/item/11fb598b-5c51-4d1a-8f2e-0c7594361d15 +Preview: true +Api-Key: my-api-key +``` + +Is the API key not applied using the `Api-Key` request header, the unpublished content will not be included in the JSON response. + +
+ +
+ +Localization + +When your content is available in multiple languages, the Delivery API can resolve localized content. When querying content by `id`, the `Accept-Language` header can be used to request variant content. + +```http +GET /umbraco/delivery/api/v2/content/item/11fb598b-5c51-4d1a-8f2e-0c7594361d15 +Accept-Language: en-US +``` + +When querying content by path, the culture is already known and included in the path, making the `Accept-Language` header unnecessary. However, if this header is present in the request, the value of it will take precedence over any other configuration settings. Localization is also supported through the CMS's culture and hostname configuration. + +
+ +## Endpoints + +The Delivery API output can represent a specific content item or a paged list of multiple items. + +{% hint style="info" %} +When referring to a specific content item in your API requests, the `id` parameter always refers to the item’s key (GUID). +{% endhint %} + +### Gets a content item by id + +`GET` `/umbraco/delivery/api/v2/content/item/{id}` + +Returns a single item. + +#### Path Parameters + +| Name | Type | Description | +| ------------------------------------ | ------ | ------------------------- | +| id\* | String | GUID of the content item. | + +#### Query Parameters + +| Name | Type | Description | +| ------ | ------ | --------------------------------------------------------------------------------------------------------- | +| expand | String | Which properties to expand and therefore include in the output if they refer to another piece of content. | +| fields | String | Which properties to include in the response. All properties are included by default. | + +#### Headers + +| Name | Type | Description | +| --------------- | ------- | --------------------------------------------- | +| Accept-Language | String | Requested culture. | +| Api-Key | String | Access token. | +| Preview | Boolean | Whether draft content is requested. | +| Start-Item | String | URL segment or GUID of the root content item. | + +| Response Status | Description | +| --------------------- | ----------------------------------------------- | +| **200**: OK | Content item. | +| **401**: Unauthorized | Missing permissions after protection is set up. | +| **404**: Not Found | Content item not found. | + +## Gets a content item by route + +`GET` `/umbraco/delivery/api/v2/content/item/{path}` + +Returns a single item. + +#### Path Parameters + +| Name | Type | Description | +| -------------------------------------- | ------ | ------------------------- | +| path\* | String | Path of the content item. | + +#### Query Parameters + +| Name | Type | Description | +| ------ | ------ | --------------------------------------------------------------------------------------------------------- | +| expand | String | Which properties to expand and therefore include in the output if they refer to another piece of content. | +| fields | String | Which properties to include in the response. All properties are included by default. | + +#### Headers + +| Name | Type | Description | +| --------------- | ------- | --------------------------------------------- | +| Accept-Language | String | Requested culture. | +| Api-Key | String | Access token. | +| Preview | Boolean | Whether draft content is requested. | +| Start-Item | String | URL segment or GUID of the root content item. | + +| Response Status | Description | +| --------------------- | ----------------------------------------------- | +| **200**: OK | Content item. | +| **401**: Unauthorized | Missing permissions after protection is set up. | +| **404**: Not Found | Content item not found. | + +## Gets content item(s) by id + +`GET` `/umbraco/delivery/api/v2/content/items` + +Returns single or multiple items by id. + +#### Query Parameters + +| Name | Type | Description | +| ------------------------------------ | ------------ | ------------------------------------------------------------------------------------ | +| id\* | String Array | GUIDs of the content items. | +| expand | String | Which properties to expand in the response. | +| fields | String | Which properties to include in the response. All properties are included by default. | + +#### Headers + +| Name | Type | Description | +| --------------- | ------- | --------------------------------------------- | +| Accept-Language | String | Requested culture. | +| Api-Key | String | Access token. | +| Preview | Boolean | Whether draft content is requested. | +| Start-Item | String | URL segment or GUID of the root content item. | + +| Response Status | Description | +| --------------------- | ----------------------------------------------- | +| **200**: OK | Content item. | +| **401**: Unauthorized | Missing permissions after protection is set up. | + +## Gets content item(s) from a query + +`GET` `/umbraco/delivery/api/v2/content` + +Returns single or multiple items. + +#### Query Parameters + +| Name | Type | Description | +| ------ | ------------ | --------------------------------------------------------------------------------------------------------- | +| fetch | String | Structural query string option (e.g. `ancestors`, `children`, `descendants`). | +| filter | String Array | Filtering query string options (e.g. `contentType`, `name`, `createDate`, `updateDate`). | +| sort | String Array | Sorting query string options (e.g. `createDate`, `level`, `name`, `sortOrder`, `updateDate`). | +| skip | Integer | Amount of items to skip. | +| take | Integer | Amount of items to take. | +| expand | String | Which properties to expand and therefore include in the output if they refer to another piece of content. | +| fields | String | Which properties to include in the response. All properties are included by default. | + +#### Headers + +| Name | Type | Description | +| --------------- | ------- | --------------------------------------------- | +| Accept-Language | String | Requested culture. | +| Api-Key | String | Access token. | +| Preview | Boolean | Whether draft content is requested. | +| Start-Item | String | URL segment or GUID of the root content item. | + +| Response Status | Description | +| --------------------- | ----------------------------------------------- | +| **200**: OK | Content item. | +| **401**: Unauthorized | Missing permissions after protection is set up. | +| **404**: Not Found | Content item not found. | + +All endpoints are documented in a Swagger document at `{yourdomain}/umbraco/swagger`. This document is not available in production mode by default. For more information read the [API versioning and OpenAPI](https://docs.umbraco.com/umbraco-cms/reference/api-versioning-and-openapi) article. + +### Query parameters + +The Content Delivery API provides query parameters that allow you to customize the content returned by the API. The relevant query parameters for each endpoint are already specified in the corresponding documentation above. + +In addition to standard parameters like `skip` and `take`, the API provides different possibilities for the value of the `expand`, `fields`, `fetch`, `filter` and `sort` parameters. Below are the options that are supported out of the box. + +{% hint style="info" %} +You can extend the built-in selector, filter, and sorting capabilities of the Delivery API by creating custom query handlers. +{% endhint %} + +{% tabs %} +{% tab title="expand" %} +{% hint style="info" %} +Refer to the [Property expansion and limiting](./#property-expansion-and-limiting) concept for more information about this parameter. +{% endhint %} + +**`?expand=properties[$all]`**\ +All expandable properties on the retrieved content item will be expanded. + +**`?expand=properties[alias1]`**\ +A specific expandable property with the property alias _`alias1`_ will be expanded. + +**`?expand=properties[alias1,alias2,alias3]`**\ +Multiple expandable properties with the specified property aliases will be expanded. + +**`?expand=properties[alias1[properties[nestedAlias1,nestedAlias2]]]`**\ +The property with the property alias _`alias1`_ will be expanded, and likewise the properties _`nestedAlias1`_ and _`nestedAlias2`_ of the expanded _`alias1`_ property. +{% endtab %} + +{% tab title="fields" %} +{% hint style="info" %} +Refer to the [Property expansion and limiting](./#property-expansion-and-limiting) concept for more information about this parameter. +{% endhint %} + +**`?fields=properties[$all]`**\ +Includes all properties of the retrieved content item in the output. + +**`?fields=properties[alias1]`**\ +Includes only the property with the property alias _`alias1`_ in the output. + +**`?fields=properties[alias1,alias2,alias3]`**\ +Includes only the properties with the specified property aliases in the output. + +**`?fields=properties[alias1[properties[nestedAlias1,nestedAlias2]]]`**\ +Includes only the property with the property alias _`alias1`_ in the output. If this property is expanded, only the properties _`nestedAlias1`_ and _`nestedAlias2`_ of the expanded _`alias1`_ property are included. +{% endtab %} + +{% tab title="fetch" %} +You can apply a selector option to the `/umbraco/delivery/api/v2/content` endpoint to query content items based on their structure. The selector allows you to fetch different subsets of items based on a GUID or path of a specific content item. The Delivery API will search all available content items if no `fetch` parameter is provided. The following built-in selectors can be used out of the box: + +**`?fetch=ancestors:id/path`**\ +All ancestors of a content item specified by either its _`id`_ or _`path`_ will be retrieved. + +**`?fetch=children:id/path`**\ +All immediate children of a content item specified by either its _`id`_ or _`path`_ will be retrieved. + +**`?fetch=descendants:id/path`**\ +All descendants of a content item specified by either its _`id`_ or _`path`_ will be retrieved. + +{% hint style="info" %} +Only one selector option can be applied to a query at a time. You cannot combine multiple fetch parameters in a single query. +{% endhint %} + +For example, the following API call will attempt to retrieve all the content items that are directly below an item with the id `dc1f43da-49c6-4d87-b104-a5864eca8152`: + +**Request** + +```http +GET /umbraco/delivery/api/v2/content?fetch=children:dc1f43da-49c6-4d87-b104-a5864eca8152 +``` +{% endtab %} + +{% tab title="filter" %} +The filter query parameter allows you to specify one or more filters. These filters must match for a content item to be included in the response. The API provides a few built-in filters that you can use right away with the `/umbraco/delivery/api/v2/content` endpoint: + +**`?filter=contentType:alias`**\ +This filter restricts the results to content items that belong to the specified content type. Replace _`alias`_ with the alias of the content type you want to filter by. + +**`?filter=name:nodeName`**\ +Only content items whose name matches the specified value will be returned when this filter is applied. Replace _`nodeName`_ with the name of the item that you want to filter by. + +{% hint style="info" %} +The `contentType` and `name` filters support negation. You can exclude content items from the result set that match the filter criteria using an exclamation mark before the filter value. + +For example, you can fetch all content items that are _not_ of type `article` like this: `?filter=contentType:!article`. +{% endhint %} + +**`?filter=createDate>date`**\ +Only content items created later than the specified value will be returned when this filter is applied. Replace _`date`_ with the date that you want to filter by. + +**`?filter=updateDate>date`**\ +Only content items updated later than the specified value will be returned when this filter is applied. Replace _`date`_ with the date that you want to filter by. + +{% hint style="info" %} +The `createDate` and `updateDate` filters support both "greater than", "greater than or equal", "less than" and "less than or equal": + +* Use > for "greater than" filtering. +* Use >: for "greater than or equal" filtering. +* Use < for "less than" filtering. +* Use <: for "less than or equal" filtering. +{% endhint %} + +Multiple filters can be applied to the same request in addition to other query parameters: + +**Request** + +```http +GET /umbraco/delivery/api/v2/content?filter=contentType:article&filter=name:guide&skip=0&take=10 +``` + +This technique can also be used to perform range filtering. For example, fetch articles created in 2023: + +**Request** + +```http +GET /umbraco/delivery/api/v2/content?filter=contentType:article&filter=createDate>:2023-01-01&filter=createDate<2024-01-01&skip=0&take=10 +``` +{% endtab %} + +{% tab title="sort" %} +Specifying how the results should be ordered, can be achieved using the `sort` query option. You can use this parameter to sort the content items by fields, including `create date`, `level`, `name`, `sort order`, and `update date`. For each field, you can specify whether the items should be sorted in ascending (_asc_) or descending (_desc_) order. Without a `sort` query parameter, the order of the results will be determined by the relevance score of the _DeliveryApiContentIndex_ for the search term. + +**`?sort=createDate:asc/desc`**\ +An option to sort the results based on the creation date of the content item in either _`asc`_ or _`desc`_ order. + +**`?sort=level:asc/desc`**\ +An option to sort the results based on the level of the content item in the content tree in either _`asc`_ or _`desc`_ order. + +**`?sort=name:asc/desc`**\ +An option to sort the results based on the name of the content item in either _`asc`_ or _`desc`_ order. + +**`?sort=sortOrder:asc/desc`**\ +An option to sort the results based on the sort order of the content item in either _`asc`_ or _`desc`_ order. + +**`?sort=updateDate:asc/desc`**\ +An option to sort the results based on the last update date of the content item in either _`asc`_ or _`desc`_ order. + +Different sorting options can be combined for the `/umbraco/delivery/api/v2/content` endpoint, allowing for more advanced sorting functionality. Here is an example: + +**Request** + +```http +GET /umbraco/delivery/api/v2/content?sort=name:asc&sort=createDate:asc +``` +{% endtab %} +{% endtabs %} + +## Extension points + +The Delivery API has been designed for extensibility, offering multiple extension points that provide greater flexibility and customization options. These extension points allow you to tailor the API's behavior and expand its capabilities to meet your specific requirements. + +You can find detailed information about the specific areas of extension in the articles below: + +* [Tailor the API's response for custom property editors](custom-property-editors-support.md) +* [Extend the API with custom selecting, filtering, and sorting options](extension-api-for-querying.md) + +## Handling deeply nested JSON output + +.NET imposes a limit on the depth of object nesting within rendered JSON. This is done to detect cyclic references. Learn more about it in [the official .NET API docs](https://learn.microsoft.com/en-us/dotnet/api/system.text.json.jsonserializeroptions.maxdepth). + +If the limit is exceeded, .NET will throw a `JsonException`. + +The content models might be so deeply nested that the Delivery API produces JSON that exceeds this limit. If this happens, the `JsonException` will be logged and shown in the [Umbraco log viewer](https://docs.umbraco.com/umbraco-cms/fundamentals/backoffice/logviewer/). + +To handle this we have to change the limit. Since the Delivery API has its own JSON configuration, we can do so without affecting the rest of our site. + +1. Add the following `using` statements to `Program.cs`: + +{% code title="Program.cs" %} +```csharp +using Umbraco.Cms.Api.Common.DependencyInjection; +using Umbraco.Cms.Core; +``` +{% endcode %} + +2. Add the following code snippet to the `Program.cs` file: + +{% code title="Program.cs" %} +```csharp +builder.Services.AddControllers().AddJsonOptions( + Constants.JsonOptionsNames.DeliveryApi, + options => + { + // set the maximum allowed depth of + options.JsonSerializerOptions.MaxDepth = {desired max depth} + }); +``` +{% endcode %} + +## Current Limitations + +The Content Delivery API provides a powerful and flexible way to retrieve content from the Umbraco CMS. There are, however, certain limitations to be aware of. + +In this section, we will discuss some of the known limitations of the API, and how to work around them if necessary. + +### Protected content + +The Delivery API supports protected content and member authentication. This is an opt-in feature. You can read more about it in the [Protected Content in the Delivery API](protected-content-in-the-delivery-api/README.md) article. + +If member authentication is _not_ explicitly enabled, protected content is ignored and never exposed by the Delivery API. + +As a result, lifting protection from a content item requires an additional step to ensure it becomes accessible through the Delivery API. The recommended way is to publish the content item again. Alternatively, you can manually rebuild the `DeliveryApiContentIndex` to reflect the changes. + +### Property editors + +Certain limitations are associated with some of the built-in property editors in Umbraco. These limitations are listed below. + +#### Rich Text Editor + +Internal links may be insufficient in a multi-site setup when outputting the Rich Text Editor content as HTML (_the default format_). There is a possibility that this limitation may be addressed in future updates. However, consider the alternative approach to rendering the Rich Text Editor content as JSON. + +#### Member Picker + +The Member Picker property editor is not supported in the Delivery API to avoid the risk of leaking member data. + +#### Content Picker + +The Content Picker property editor is not supported in the Delivery API when configured for Members. This is due to the concern of potentially leaking member data. + +### Making changes to `DisallowedContentTypeAliases` + +When changing the content type aliases in the `Umbraco:CMS:DeliveryApi:DisallowedContentTypeAliases` configuration setting, `DeliveryApiContentIndex` should be rebuilt. This ensures that disallowed content types are not exposed through the Delivery API. + +Alternatively, the relevant content items can be republished. This will ensure that changes are reflected, eliminating the need to rebuild the index. diff --git a/16/umbraco-cms/reference/content-delivery-api/additional-preview-environments-support.md b/16/umbraco-cms/reference/content-delivery-api/additional-preview-environments-support.md new file mode 100644 index 00000000000..72363a78aa3 --- /dev/null +++ b/16/umbraco-cms/reference/content-delivery-api/additional-preview-environments-support.md @@ -0,0 +1,115 @@ +--- +description: >- + Configure custom preview URLs to provide editors with seamless access to external preview environments for the Content Delivery API data. +--- + +# Additional preview environments support + +{% hint style="warning" %} +The contents of this article have not yet been verified or updated for Umbraco 15. +{% endhint %} + +With Umbraco, you can save and preview draft content before going live. The preview feature allows you to visualize how a page will look once it is published, directly from within the backoffice. This is also possible for the Content Delivery API data. You can extend the preview functionality in the backoffice by configuring external preview URLs for client libraries consuming the Content Delivery API. + +{% hint style="info" %} +To get familiar with the preview functionality in the Delivery API, please refer to the [Preview concept](https://docs.umbraco.com/umbraco-cms/reference/content-delivery-api#preview) section. +{% endhint %} + +{% hint style="info" %} +The support for configuring additional preview environments in the Delivery API was introduced in version 12.3. +{% endhint %} + +## Configuring custom preview URLs + +If your client libraries feature preview functionality, you can enable editors in Umbraco to navigate directly to their preferred preview environments. To achieve this, start by generating the necessary URLs for each environment you wish to allow for preview. These URLs need to trigger preview mode within your application, which will fetch and present draft content from the Delivery API. + +Once you have these preview URLs, you will need to register them through code in Umbraco. + +Additionally, there are plans to simplify this process further. In an upcoming major version of Umbraco, a UI will be introduced, allowing you to configure these custom preview URLs directly from the backoffice. + +{% include "../../.gitbook/includes/obsolete-warning-ipublishedsnapshotaccessor.md" %} + +Here is an example of how to register such preview URLs for both variant and invariant content using a notification handler: + +{% code title="AdditionalPreviewUrlsNotificationHandler.cs" %} + +```csharp +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models.ContentEditing; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.PublishedCache; + +namespace Umbraco.Docs.Samples; + +public class AdditionalPreviewUrlsNotificationHandler : INotificationHandler +{ + private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor; + + public AdditionalPreviewUrlsNotificationHandler(IPublishedSnapshotAccessor publishedSnapshotAccessor) + => _publishedSnapshotAccessor = publishedSnapshotAccessor; + + public void Handle(SendingContentNotification notification) + { + foreach (ContentVariantDisplay variantDisplay in notification.Content.Variants.Where(variant => variant.PublishDate.HasValue)) + { + // Retrieve the route of each content item + IPublishedSnapshot publishedSnapshot = _publishedSnapshotAccessor.GetRequiredPublishedSnapshot(); + var route = publishedSnapshot.Content?.GetRouteById(true, notification.Content.Id, variantDisplay.Language?.IsoCode); + if (route == null) + { + continue; + } + + route = route.TrimStart('/'); + + variantDisplay.AdditionalPreviewUrls = new[] + { + new NamedUrl + { + // Dynamically generate Preview URL + Name = $"Development{(variantDisplay.Language != null ? $" ({variantDisplay.Language.Name})" : null)}", + Url = $"https://dev.environment.org/{route}?culture={variantDisplay.Language?.IsoCode}&preview=true" + }, + new NamedUrl + { + // Dynamically generate Preview URL + Name = $"Staging{(variantDisplay.Language != null ? $" ({variantDisplay.Language.Name})" : null)}", + Url = $"https://staging.environment.org/{route}?culture={variantDisplay.Language?.IsoCode}&preview=true" + } + }; + } + } +} +``` + +{% endcode %} + +The purpose of this notification handler is to dynamically generate additional preview URLs for published content items only (_for the sake of simplicity_). It constructs two custom preview URLs, one for a development environment and another for a staging environment. These URLs include the content's route, culture variant, and a `preview` query parameter to enable preview mode in the client application. + +You can then register your notification handler in a composer like this: + +{% code title="AdditionalPreviewUrlsNotificationHandlerComposer.cs" %} +```csharp +using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Core.Notifications; + +namespace Umbraco.Docs.Samples; + +public class AdditionalPreviewUrlsNotificationHandlerComposer : IComposer +{ + public void Compose(IUmbracoBuilder builder) + => builder.AddNotificationHandler(); +} +``` +{% endcode %} + +## Accessing preview environments + +Now that we have set up additional preview URLs for the Delivery API data, you can access them from the Content section. When you open a content node, you will see new preview options for the external environments you have configured. Next to the regular "Save and preview" button, there is an arrow for the multiple URLs that have been added. Click it to see all the available preview URLs, as shown below: + +![Preview invariant content with Delivery API](images/preview-invariant-content.png) + +Below is an example with variants, showcasing both the English and Danish versions of a content node. + +![Preview English variant with Delivery API](images/preview-variant-content-en.png) +![Preview Danish variant with Delivery API](images/preview-variant-content-da.png) \ No newline at end of file diff --git a/16/umbraco-cms/reference/content-delivery-api/custom-property-editors-support.md b/16/umbraco-cms/reference/content-delivery-api/custom-property-editors-support.md new file mode 100644 index 00000000000..789bfaf844f --- /dev/null +++ b/16/umbraco-cms/reference/content-delivery-api/custom-property-editors-support.md @@ -0,0 +1,252 @@ +--- +description: >- + Discover how to customize the Content Delivery API's response for your custom + property editors. +--- + +# Custom property editors support + +Out of the box, the Delivery API supports custom property editors, ensuring they are rendered alongside the built-in ones in Umbraco. However, if the output generated by your property editor isn't optimal for a headless context, you have the ability to customize the API response. This customization won't impact the Razor rendering, allowing you to tailor the Content Delivery API response according to your specific requirements. + +In this article, we'll look into how you can work with the `IDeliveryApiPropertyValueConverter` interface and implement custom [property expansion](./property-expansion-and-limiting.md) for your custom property editors. + +## Prerequisite + +The examples in this article revolve around the fictional `My.Custom.Picker` property editor. This property editor stores the key of a single content item and is backed by a property value converter. + +We will not dive into the details of creating a custom property editor for Umbraco in this article. If you need guidance on that, please refer to the [Creating a Property Editor](../../tutorials/creating-a-property-editor/README.md) and [Property Value Converters](../../customizing/property-editors/property-value-converters.md) articles. + +## Implementation + +To customize the output of a property value editor in the Delivery API, we need to opt-in by implementing the `IDeliveryApiPropertyValueConverter` interface. + +The code example below showcases the implementation of this interface in the property value converter for `My.Custom.Picker`. Our focus will be on the methods provided by the `IDeliveryApiPropertyValueConverter`, as they are responsible for customizing the Delivery API response. + +Towards the end of the example, you will find the response models that we will be using. + +The `IsConverter()` and `GetPropertyValueType()` methods are inherited from the `PropertyValueConverterBase` class, which is covered in the [Property Value Converters](../../customizing/property-editors/property-value-converters.md) article. + +{% include "../../.gitbook/includes/obsolete-warning-snapshot.md" %} + +{% code title="MyCustomPickerValueConverter.cs" lineNumbers="true" %} + +```csharp +using Umbraco.Cms.Core.DeliveryApi; +using Umbraco.Cms.Core.Models.DeliveryApi; +using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Cms.Core.PropertyEditors.DeliveryApi; +using Umbraco.Cms.Core.PublishedCache; + +namespace Umbraco.Docs.Samples; + +public class MyCustomPickerValueConverter : PropertyValueConverterBase, IDeliveryApiPropertyValueConverter +{ + private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor; + private readonly IApiContentRouteBuilder _apiContentRouteBuilder; + + public MyCustomPickerValueConverter( + IPublishedSnapshotAccessor publishedSnapshotAccessor, + IApiContentRouteBuilder apiContentRouteBuilder) + { + _publishedSnapshotAccessor = publishedSnapshotAccessor; + _apiContentRouteBuilder = apiContentRouteBuilder; + } + + public override bool IsConverter(IPublishedPropertyType propertyType) + => propertyType.EditorAlias.Equals("My.Custom.Picker"); + + public override Type GetPropertyValueType(IPublishedPropertyType propertyType) + => typeof(Guid?); + + public PropertyCacheLevel GetDeliveryApiPropertyCacheLevel(IPublishedPropertyType propertyType) + => PropertyCacheLevel.Elements; + + public PropertyCacheLevel GetDeliveryApiPropertyCacheLevelForExpansion(IPublishedPropertyType propertyType) + => PropertyCacheLevel.Snapshot; + + public Type GetDeliveryApiPropertyValueType(IPublishedPropertyType propertyType) + => typeof(DeliveryApiCustomPicker); + + public object? ConvertIntermediateToDeliveryApiObject( + IPublishedElement owner, + IPublishedPropertyType propertyType, + PropertyCacheLevel referenceCacheLevel, + object? inter, + bool preview, + bool expanding) + { + if (inter is null) + { + return null; + } + + return BuildDeliveryApiCustomPicker(inter, expanding); + } + + private DeliveryApiCustomPicker? BuildDeliveryApiCustomPicker(object inter, bool expanding) + { + if (!_publishedSnapshotAccessor.TryGetPublishedSnapshot(out IPublishedSnapshot? publishedSnapshot) || + publishedSnapshot?.Content is null) + { + return null; + } + + if (!Guid.TryParse(inter as string, out Guid id)) + { + return null; + } + + return new DeliveryApiCustomPicker { Id = id }; + } +} + +public class DeliveryApiCustomPicker +{ + public Guid Id { get; set; } + + public DeliveryApiItemDetails? ItemDetails { get; set; } +} + +public class DeliveryApiItemDetails +{ + public string? Name { get; set; } + + public IApiContentRoute? Route { get; set; } +} +``` + +{% endcode %} + +The Implementation of the `IDeliveryApiPropertyValueConverter` interface can be found in the following methods: + +* `GetDeliveryApiPropertyCacheLevel()`: This method specifies the cache level used for our property representation in the Delivery API response. +* `GetDeliveryApiPropertyCacheLevelForExpansion()`: This method specifies the cache level used for our property representation in the Delivery API response when [property expansion](./property-expansion-and-limiting.md) is applied. +* `GetDeliveryApiPropertyValueType()`: This method defines the value type of the custom property output for the Delivery API response. +* `ConvertIntermediateToDeliveryApiObject()`: This method converts the value from the property editor to the desired custom object in a headless context. + +In the given example, the content key (`Guid` value) is used when rendering with Razor. This is sufficient because Razor provides full access to the published content within the rendering context. In a headless context, we do not have the same access. To prevent subsequent round-trips to the server, we create a richer output model specifically for the Delivery API. + +The following example request shows how our custom implementation is reflected in the resulting API response. In this case, our custom property editor is configured under the alias `"pickedItem"`. + +**Request** + +```http +GET /umbraco/delivery/api/v2/content/item/blog +``` + +**Response** + +```json +{ + "name": "Blog", + "createDate": "2023-05-25T11:26:52.591927", + "updateDate": "2023-06-07T12:53:41.339963", + "route": { + "path": "/", + "startItem": { + "id": "88694917-ac4a-4cfa-aa82-d89a81940126", + "path": "blog" + } + }, + "id": "88694917-ac4a-4cfa-aa82-d89a81940126", + "contentType": "blog", + "properties": { + "pickedItem": { + "id": "61df413c-02b2-480d-8cb5-8e3c35132838", + "itemDetails": null + } + }, + "cultures": {} +} +``` + +## Property expansion support + +Property expansion allows us to conditionally add another level of detail to the Delivery API output. Usually, these additional details are "expensive" to retrieve (for example, requiring database access to populate). By applying property expansion, we provide the option for the caller of the API to opt-in explicitly to this "expensive" operation. From the caller's perspective, the alternative might be an even more expensive additional round-trip to the server. + +In our example, property expansion is implemented within `ConvertIntermediateToDeliveryApiObject()`. By considering the value of the `expanding` parameter, we can modify the `BuildDeliveryApiCustomPicker()` method as follows: + +{% include "../../.gitbook/includes/obsolete-warning-ipublishedsnapshotaccessor.md" %} + +```csharp +private DeliveryApiCustomPicker? BuildDeliveryApiCustomPicker(object inter, bool expanding) +{ + if (!_publishedSnapshotAccessor.TryGetPublishedSnapshot(out IPublishedSnapshot? publishedSnapshot) || + publishedSnapshot?.Content is null) + { + return null; + } + + if (!Guid.TryParse(inter as string, out Guid id)) + { + return null; + } + + // Property expansion support + if (expanding == false) + { + return new DeliveryApiCustomPicker { Id = id }; + } + + IPublishedContent? content = publishedSnapshot.Content.GetById(id); + if (content is null) + { + return new DeliveryApiCustomPicker { Id = id }; + } + + IApiContentRoute? route = _apiContentRouteBuilder.Build(content); + var itemDetails = new DeliveryApiItemDetails { Name = content.Name, Route = route }; + + return new DeliveryApiCustomPicker { Id = id, ItemDetails = itemDetails }; +} +``` + +If the `expanding` parameter is `false`, the method returns the same shallow representation of the referenced content item as before. Otherwise, we retrieve the corresponding `IPublishedContent` and construct our response object accordingly. + +To see the expanded output in the API response, we need to add the `expand` query parameter to our request. We can use either `?expand=properties[$all]` to expand all properties or `?expand=properties[pickedItem]` to expand the specific `'pickedItem'` property. + +**Request** + +```http +GET /umbraco/delivery/api/v2/content/item/blog?expand=properties[pickedItem] +``` + +**Response** + +```json +{ + "name": "Blog", + "createDate": "2023-05-25T11:26:52.591927", + "updateDate": "2023-06-07T12:53:41.339963", + "route": { + "path": "/", + "startItem": { + "id": "88694917-ac4a-4cfa-aa82-d89a81940126", + "path": "blog" + } + }, + "id": "88694917-ac4a-4cfa-aa82-d89a81940126", + "contentType": "blog", + "properties": { + "pickedItem": { + "id": "61df413c-02b2-480d-8cb5-8e3c35132838", + "itemDetails": { + "name": "Codegarden", + "createDate": "2023-05-21T00:34:11.513868", + "updateDate": "2023-06-06T17:46:12.833186", + "route": { + "path": "/codegarden/", + "startItem": { + "id": "88694917-ac4a-4cfa-aa82-d89a81940126", + "path": "blog" + } + } + } + } + }, + "cultures": {} +} +``` + +The `itemDetails` property of the `pickedItem` in the JSON response contains the additional details of the selected content item. diff --git a/16/umbraco-cms/reference/content-delivery-api/extension-api-for-querying.md b/16/umbraco-cms/reference/content-delivery-api/extension-api-for-querying.md new file mode 100644 index 00000000000..5c3634ca917 --- /dev/null +++ b/16/umbraco-cms/reference/content-delivery-api/extension-api-for-querying.md @@ -0,0 +1,352 @@ +--- +description: >- + Learn how to extend the Content Delivery API with custom selecting, filtering, + and sorting options for the multi-item-based endpoint. +--- + +# Extension API for querying + +The Delivery API allows you to retrieve multiple items by utilizing the `/umbraco/delivery/api/v2/content` endpoint. With the built-in query parameters, you have the flexibility to get any number of content nodes based on your needs. For a comprehensive list of supported query options, please refer to the [Endpoints](./#endpoints) section. + +For the query endpoint, we have created a new Examine index (_DeliveryApiContentIndex_) that facilitates fast retrieval of the desired content. This index ensures quick indexing and searching of data, with the possibility for future extensions. + +In this article, we'll explore creating custom selecting, filtering, and sorting options to enhance the querying capabilities of the Delivery API. + +## Query options + +Let's take a look at an example of using the query endpoint with query parameters for `fetch`, `filter`, and `sort`. A request might look like this: + +```http +GET /umbraco/delivery/api/v2/content?fetch=xxx&filter=yyy&filter=zzz&sort=aaa&sort=bbb +``` + +The placeholders in the example (`xxx`, `yyy`, etc.) represent the values that each query option evaluates in order to determine the suitable query handler. + +{% hint style="info" %} +You can include only one `fetch` parameter, while multiple `filter` and `sort` parameters are allowed. Additionally, the order of the `sort` parameters influences the sorting behaviour. Refer to the [Query parameters](./#query-parameters) section for the currently supported options. +{% endhint %} + +The implementation of each querying option consists of a class for indexing the data into the _DeliveryApiContentIndex_ and another one for handling the query. By implementing the `IContentIndexHandler` interface, you can control how your relevant data is indexed and made available for querying through our index. And you can customize the querying behaviour to suit your needs by implementing the `ISelectorHandler`, `IFilterHandler`, and `ISortHandler` interfaces. + +In the following sections, we will explore the implementation details of creating custom querying functionality for the Delivery API. + +## Custom selector + +Selectors handle the `fetch` part of a query. + +To showcase how to build a custom selector, consider a site structure with a few blog posts. A post is linked to an author, which is another content item. + +Authors can be marked as _'Featured'_ using a toggle, granting them additional visibility and recognition. We will use this marker as part of the indexing implementation for our selector option. + +The following example demonstrates the implementation of an `AuthorSelector`, which allows you to customize the querying behaviour specifically for finding all featured authors. This class contains both indexing and querying responsibilities. However, keep in mind that it is generally recommended to separate these responsibilities into dedicated classes. + +{% code title="AuthorSelector.cs" lineNumbers="true" %} +```csharp +using Umbraco.Cms.Core.DeliveryApi; +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Docs.Samples; + +public class AuthorSelector : ISelectorHandler, IContentIndexHandler +{ + private const string FeaturedAuthorsSpecifier = "featuredAuthors"; + private const string FieldName = "featured"; + + // Querying + public bool CanHandle(string query) + => query.StartsWith(FeaturedAuthorsSpecifier, StringComparison.OrdinalIgnoreCase); + + public SelectorOption BuildSelectorOption(string selector) => + new SelectorOption + { + FieldName = FieldName, + Values = new[] { "y" } + }; + + // Indexing + public IEnumerable GetFieldValues(IContent content, string? culture) + { + if (content.ContentType.Alias.InvariantEquals("author") == false) + { + return Enumerable.Empty(); + } + + var isFeatured = content.GetValue("featured"); + + return new[] + { + new IndexFieldValue + { + FieldName = FieldName, + Values = new object[] { isFeatured ? "y" : "n" } + } + }; + } + + public IEnumerable GetFields() + => new[] + { + new IndexField + { + FieldName = FieldName, + FieldType = FieldType.StringRaw, + VariesByCulture = false + } + }; +} +``` +{% endcode %} + +The `AuthorSelector` class implements the `ISelectorHandler` and `IContentIndexHandler` interfaces. + +`ISelectorHandler` allows for handling the `fetch` value in API queries through the `CanHandle()` and `BuildSelectorOption()` methods. + +* `CanHandle()` determines if the given `fetch` query corresponds to the `"featuredAuthors"` value. +* `BuildSelectorOption()` constructs the selector option to search for authors with a positive value (for example, `"y"`) in a `"featured"` index field. + +The `GetFields()` and `GetFieldValues()` methods each play a role in defining how the data should be indexed and made searchable. + +* `GetFields()` defines the behaviour of fields that are added to the index. In this example, the `"featured"` field is added as a "raw" string for efficient and accurate searching. +* `GetFieldValues()` is responsible for retrieving the values of the defined index fields. In this case, the `"featured"` field of content items of type `"author"`. It creates an `IndexFieldValue` with the appropriate field value (`"y"` for featured, `"n"` otherwise), which will be added to the index. + +Since our custom query option modifies the index structure, we will need to rebuild the _DeliveryApiContentIndex_. You can find it by navigating to the "Examine Management" dashboard in the "Settings" section. Once rebuilt, we can make a request to the Delivery API query endpoint as follows: + +**Request** + +```http +GET /umbraco/delivery/api/v2/content?fetch=featuredAuthors +``` + +**Response** + +```json +{ + "total": 3, + "items": [ + ... + ] +} +``` + +## Custom filter + +Filters handle the `filter` part of a query. + +Staying within the topic of blog posts and their authors, we will create a custom filter to find posts by specific author(s). + +This filter allows specifying the desired author(s) by their key (`Guid`) in an `author:` filter option. Multiple authors can be included by listing their keys as comma-separated-values, like: + +**Request** + +```http +GET /umbraco/delivery/api/v2/content?filter=author:7c630f15-8d93-4980-a0fc-027314dc827a,75380b4f-6d6e-47a1-9222-975cdfb2ac5f +``` + +The response will include the blog posts associated with the provided authors, enabling us to retrieve only the relevant results from the API. + +**Response** + +```json +{ + "total": 4, + "items": [ + ... + ] +} +``` + +Our filter implementation follows a similar structure to the custom selector we discussed earlier. We continue to utilize the `IContentIndexHandler` interface, but this time we introduce the `IFilterHandler`. This combination gives us flexibility and control over the filtering behaviour. + +The procedure remains the same - we store and query the author key in a new `"authorId"` field within the index. Consequently, we will need to rebuild the index to reflect the changes. + +To illustrate the implementation, consider the following code example: + +{% code title="AuthorFilter.cs" lineNumbers="true" %} +```csharp +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.DeliveryApi; +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Docs.Samples; + +public class AuthorFilter : IFilterHandler, IContentIndexHandler +{ + private const string AuthorSpecifier = "author:"; + private const string FieldName = "authorId"; + + // Querying + public bool CanHandle(string query) + => query.StartsWith(AuthorSpecifier, StringComparison.OrdinalIgnoreCase); + + public FilterOption BuildFilterOption(string filter) + { + var fieldValue = filter.Substring(AuthorSpecifier.Length); + + // There might be several values for the filter + var values = fieldValue.Split(','); + + return new FilterOption + { + FieldName = FieldName, + Values = values, + Operator = FilterOperation.Is + }; + } + + // Indexing + public IEnumerable GetFieldValues(IContent content, string? culture) + { + GuidUdi? authorUdi = content.GetValue("author"); + + if (authorUdi is null) + { + return Array.Empty(); + } + + return new[] + { + new IndexFieldValue + { + FieldName = FieldName, + Values = new object[] { authorUdi.Guid } + } + }; + } + + public IEnumerable GetFields() => new[] + { + new IndexField + { + FieldName = FieldName, + FieldType = FieldType.StringRaw, + VariesByCulture = false + } + }; +} +``` +{% endcode %} + +The principal difference from the selector is that the filter implements `BuildFilterOption()` instead of `BuildSelectorOption()`. Here, the filter performs an exact match for any specified `Guid` in the query. Efficiently, this makes the filter perform an `OR` operation against the index. + +Since we need to perform an exact match, the index field (`authorId`) is once again defined as a "raw" string. Other options include "analyzed" and "sortable" strings. These support "contains" searches and alpha-numeric sorting, respectively. + +### Filter operators + +When implementing a filter, you can use the following operators: `Is`, `IsNot`, `Contains`, `DoesNotContain`, `GreaterThan`, `GreaterThanOrEqual`, `LessThan` and `LessThanOrEqual`. + +{% hint style="info" %} +The range operators (_the latter four_) only work with number and date fields - `FieldType.Number` and `FieldType.Date` respectively. +{% endhint %} + +It is possible to pass multiple values to each operator, and these values will be treated inclusively as an __or__ operator. For example, if `tag1` and `tag2` were passed into a filter using the `Is` operator, _any_ document containing __either__ `tag1` __or__ `tag2` would return. The request for this might look like this: + +```http +GET /umbraco/delivery/api/v2/content?filter=customTagFilter:tag1,tag2 +``` + +If you require this functionality to be restrictive i.e. `tag1` __and__ `tag2`, then the current approach would be to chain the custom filter. The request would change to look more like this: + +```http +GET /umbraco/delivery/api/v2/content?filter=customTagFilter:tag1&filter=customTagFilter:tag2 +``` + +## Custom sort + +Finally, we can also add custom handling for the `sort` part of the query. + +We'll add a custom sort handler that allows us to sort blog posts based on a custom `"publishDate"` Date Picker property. The implementation will allow for sorting the posts in ascending or descending order. + +{% hint style="info" %} +This sorting should only be used with query results that have a published date to ensure accurate results. +{% endhint %} + +To demonstrate this, consider the following implementation example: + +{% code title="PublishDateSort.cs" lineNumbers="true" %} +```csharp +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.DeliveryApi; +using Umbraco.Cms.Core.Models; + +namespace Umbraco.Docs.Samples; + +public class PublishDateSort : ISortHandler, IContentIndexHandler +{ + private const string SortOptionSpecifier = "publishDate:"; + private const string FieldName = "publishDate"; + + // Querying + public bool CanHandle(string query) + => query.StartsWith(SortOptionSpecifier, StringComparison.OrdinalIgnoreCase); + + public SortOption BuildSortOption(string sort) + { + var sortDirection = sort.Substring(SortOptionSpecifier.Length); + + return new SortOption + { + FieldName = FieldName, + Direction = sortDirection.StartsWith("asc", StringComparison.OrdinalIgnoreCase) + ? Direction.Ascending + : Direction.Descending + }; + } + + // Indexing + public IEnumerable GetFieldValues(IContent content, string? culture) + { + var publishDate = content.GetValue("publishDate"); + + if (publishDate is null) + { + return Enumerable.Empty(); + } + + return new[] + { + new IndexFieldValue + { + FieldName = FieldName, + Values = new object[] { publishDate } + } + }; + } + + public IEnumerable GetFields() + => new[] + { + new IndexField + { + FieldName = FieldName, + FieldType = FieldType.Date, + VariesByCulture = false + } + }; +} +``` +{% endcode %} + +The implementation follows the same structure as the other examples, defined by the `IContentIndexHandler` and `ISortHandler` interfaces. + +One point to highlight is that we store the `"publishDate"` value as a "date" field in the index, which allows for correct date sorting. + +Once more, when adding fields to the index, we need to rebuild it to reflect the changes. + +In the following example request, we also apply the author filter to retrieve only `"blogpost"` content nodes, which we know have the `"publishDate"` field. + +**Request** + +```http +GET /umbraco/delivery/api/v2/content?filter=author:7c630f15-8d93-4980-a0fc-027314dc827a&sort=publishDate:asc +``` + +**Response** + +```json +{ + "total": 2, + "items": [ + ... + ] +} +``` diff --git a/16/umbraco-cms/reference/content-delivery-api/images/preview-invariant-content.png b/16/umbraco-cms/reference/content-delivery-api/images/preview-invariant-content.png new file mode 100644 index 00000000000..142fbee46b4 Binary files /dev/null and b/16/umbraco-cms/reference/content-delivery-api/images/preview-invariant-content.png differ diff --git a/16/umbraco-cms/reference/content-delivery-api/images/preview-variant-content-da.png b/16/umbraco-cms/reference/content-delivery-api/images/preview-variant-content-da.png new file mode 100644 index 00000000000..0e2e961f376 Binary files /dev/null and b/16/umbraco-cms/reference/content-delivery-api/images/preview-variant-content-da.png differ diff --git a/16/umbraco-cms/reference/content-delivery-api/images/preview-variant-content-en.png b/16/umbraco-cms/reference/content-delivery-api/images/preview-variant-content-en.png new file mode 100644 index 00000000000..066e8ae90a1 Binary files /dev/null and b/16/umbraco-cms/reference/content-delivery-api/images/preview-variant-content-en.png differ diff --git a/16/umbraco-cms/reference/content-delivery-api/media-delivery-api.md b/16/umbraco-cms/reference/content-delivery-api/media-delivery-api.md new file mode 100644 index 00000000000..b271bb768ea --- /dev/null +++ b/16/umbraco-cms/reference/content-delivery-api/media-delivery-api.md @@ -0,0 +1,292 @@ +--- +description: Using the Media Delivery API. +--- + +# Media Delivery API + +The Media Delivery API allows for accessing the Umbraco media items in a headless manner. This API applies many of the same concepts as its content counterpart, although with fewer options. If you haven't already, please familiarize yourself with the [Content Delivery API](https://docs.umbraco.com/umbraco-cms/reference/content-delivery-api) before reading this article. + +{% hint style="info" %} +The Media Delivery API specification is created to mimic that of the Content Delivery API. However, the default implementation of this specification is limited and does not support the entire specification. + +Unlike the Content Delivery API, the Media Delivery API does not feature an extension model for querying. + +The reasoning behind is that third-party media systems might support a complete implementation of the specification. If the demand rises, the default implementation might eventually cover the entire specification. +{% endhint %} + +## Getting Started + +To use the Media Delivery API you must first enable it. Even if the Content Delivery API is enabled, the Media Delivery API remains disabled by default. + +The Media Delivery API is enabled by adding the `Media` section to the `DeliveryApi` configuration in `appsettings.json`: + +{% code title="appsettings.json" %} + +```json +{ + "Umbraco": { + "CMS": { + "DeliveryApi": { + "Enabled": true, + "PublicAccess": true, + "Media": { + "Enabled": true, + "PublicAccess": false + } + } + } + } +} +``` + +{% endcode %} + +As this configuration sample illustrates, it is possible to restrict public access to media independently from content. As with the Content Delivery API, media is publicly accessible by default when the Media Delivery API is enabled. + +{% hint style="info" %} +The `Media` configuration can only become more restrictive than the `DeliveryApi` configuration: + +* If `DeliveryApi:Enabled` is `false`, the `DeliveryApi:Media:Enabled` configuration option has no effect. The Media Delivery API cannot be enabled on its own. +* If `DeliveryApi:PublicAccess` is `false`, the `DeliveryApi:Media:PublicAccess` configuration option has no effect. The Media Delivery API cannot be publicly available if the Content Delivery API is not. +{% endhint %} + +## Endpoints + +The Media Delivery API can either be queried for a specific media item or a paged list of multiple items. + +{% hint style="info" %} +In the Media Delivery API, `id` parameters always refer to media item keys (`Guid`), not node ids (`integer`). +{% endhint %} + +{% swagger method="get" path="/media/item/{id}" baseUrl="/umbraco/delivery/api/v2" summary="Gets a media item by id" %} +{% swagger-description %} +Returns a single item. +{% endswagger-description %} + +{% swagger-parameter in="path" name="id" type="String" required="true" %} +GUID of the media item +{% endswagger-parameter %} + +{% swagger-parameter in="header" name="Api-Key" type="String" required="false" %} +Access token +{% endswagger-parameter %} + +{% swagger-parameter in="query" name="expand" type="String" required="false" %} +Which properties to expand in the response +{% endswagger-parameter %} + +{% swagger-parameter in="query" name="fields" type="String" required="false" %} +Which properties to include in the response (_by default all properties are included_) +{% endswagger-parameter %} + +{% swagger-response status="200: OK" description="Media item" %} + +{% endswagger-response %} + +{% swagger-response status="401: Unauthorized" description="Missing permissions after protection is set up" %} + +{% endswagger-response %} + +{% swagger-response status="404: Not Found" description="Media item not found" %} + +{% endswagger-response %} +{% endswagger %} + +{% swagger method="get" path="/media/item/{path}" baseUrl="/umbraco/delivery/api/v2" summary="Gets a media item by path" %} +{% swagger-description %} +Returns a single item. +{% endswagger-description %} + +{% swagger-parameter in="path" name="path" type="String" required="true" %} +Path of the media item. The path is composed by the names of any ancestor folders and the name of the media item itself, separated by + +`/` + +. +{% endswagger-parameter %} + +{% swagger-parameter in="header" name="Api-Key" type="String" required="false" %} +Access token +{% endswagger-parameter %} + +{% swagger-parameter in="query" name="expand" type="String" required="false" %} +Which properties to expand in the response +{% endswagger-parameter %} + +{% swagger-parameter in="query" name="fields" type="String" required="false" %} +Which properties to include in the response (_by default all properties are included_) +{% endswagger-parameter %} + +{% swagger-response status="200: OK" description="Media item" %} + +{% endswagger-response %} + +{% swagger-response status="401: Unauthorized" description="Missing permissions after protection is set up" %} + +{% endswagger-response %} + +{% swagger-response status="404: Not Found" description="Media item not found" %} + +{% endswagger-response %} +{% endswagger %} + +{% swagger method="get" path="/media/items" baseUrl="/umbraco/delivery/api/v2" summary="Gets media item(s) by id" %} +{% swagger-description %} +Returns single or multiple items by id. +{% endswagger-description %} + +{% swagger-parameter in="query" name="id" type="String Array" required="true" %} +GUIDs of the media items +{% endswagger-parameter %} + +{% swagger-parameter in="header" name="Api-Key" type="String" required="false" %} +Access token +{% endswagger-parameter %} + +{% swagger-parameter in="query" name="expand" type="String" required="false" %} +Which properties to expand in the response +{% endswagger-parameter %} + +{% swagger-parameter in="query" name="fields" type="String" required="false" %} +Which properties to include in the response (_by default all properties are included_) +{% endswagger-parameter %} + +{% swagger-response status="200: OK" description="List of media items" %} + +{% endswagger-response %} + +{% swagger-response status="401: Unauthorized" description="Missing permissions after protection is set up" %} + +{% endswagger-response %} +{% endswagger %} + +{% swagger method="get" path="/media" baseUrl="/umbraco/delivery/api/v2" summary="Gets media item(s) from a query" %} +{% swagger-description %} +Returns single or multiple items. +{% endswagger-description %} + +{% swagger-parameter in="query" name="fetch" type="String" required="true" %} +Structural query string option (e.g. `ancestors`, `children`, `descendants`). + +**Please note:** The default API implementation only supports `children`. +{% endswagger-parameter %} + +{% swagger-parameter in="query" name="filter" type="String Array" required="false" %} +Filtering query string options (e.g. `mediaType`, `name`) +{% endswagger-parameter %} + +{% swagger-parameter in="query" name="sort" type="String Array" required="false" %} +Sorting query string options (e.g. `createDate`, `name`, `sortOrder`, `updateDate`) +{% endswagger-parameter %} + +{% swagger-parameter in="query" name="skip" type="Integer" required="false" %} +Amount of items to skip +{% endswagger-parameter %} + +{% swagger-parameter in="query" name="take" type="Integer" required="false" %} +Amount of items to take. Type: Integer. Default: 10. Limits: No limits. Accepts 0. +{% endswagger-parameter %} + +{% swagger-parameter in="header" name="Api-Key" type="String" required="false" %} +Access token +{% endswagger-parameter %} + +{% swagger-parameter in="query" name="expand" type="String" required="false" %} +Which properties to expand in the response +{% endswagger-parameter %} + +{% swagger-parameter in="query" name="fields" type="String" required="false" %} +Which properties to include in the response (_by default all properties are included_) +{% endswagger-parameter %} + +{% swagger-response status="200: OK" description="Paginated list of media items" %} + +{% endswagger-response %} + +{% swagger-response status="400: Bad Request" description="Invalid request" %} + +{% endswagger-response %} +{% endswagger %} + +### Request samples + +Fetch a media item by its ID: + +```http +GET /umbraco/delivery/api/v2/media/item/3fa85f64-5717-4562-b3fc-2c963f66afa6 +``` + +Fetch a media item inside a folder structure by its full path, and expand its `author` property: + +```http +GET /umbraco/delivery/api/v2/media/item/root level folder/child folder/media item name/&expand=property:author +``` + +Fetch two media items by their ids: + +```http +GET /umbraco/delivery/api/v2/media/item?id=11178b4f-f8e2-4686-9697-6d990851a081&id=7cd00706-de93-4db8-8fc2-4b20e8419c30 +``` + +Fetch the first 10 media items of type `Image` at root level. Return the found items sorted by name ascending: + +```http +GET /umbraco/delivery/api/v2/media?fetch=children:/&filter=mediaType:Image&sort=name:asc&skip=0&take=10 +``` + +Fetch the first 5 media items inside a folder structure. Return only items of type `Image` whose item names contain "size". + +```http +GET /umbraco/delivery/api/v2/media?fetch=children:/root level folder/child folder/&filter=mediaType:Image&filter=name:size&skip=0&take=5 +``` + +## Media item JSON structure + +The Media Delivery API outputs the JSON structure outlined below to represent media items: + +* Item `path`, `createDate`, `updateDate`, `id`, `name`, and `mediaType` are always included in the response. +* `url`, `extension` and the size in `bytes` are included for all files (not for folders). +* `width` and `height` (in pixels) are included for most images. +* Depending on Umbraco Data Type configuration, `focalPoint` and `crops` are included for most images. +* Additional editorial properties from the media type can be found in the `properties` collection. + +```json +{ + "path": "string", + "createDate": "2023-08-22T08:13:57.510Z", + "updateDate": "2023-08-22T08:13:57.510Z", + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "name": "string", + "mediaType": "string", + "url": "string", + "extension": "string", + "width": 0, + "height": 0, + "bytes": 0, + "properties": { + "property1Alias": "string", + "property2Alias": 0, + "property3Alias": true, + "property4Alias": [], + "property5Alias": {}, + "property6Alias": null + }, + "focalPoint": { + "left": 0, + "top": 0 + }, + "crops": [ + { + "alias": "string", + "width": 0, + "height": 0, + "coordinates": { + "x1": 0, + "y1": 0, + "x2": 0, + "y2": 0 + } + } + ] +} +``` diff --git a/16/umbraco-cms/reference/content-delivery-api/output-caching.md b/16/umbraco-cms/reference/content-delivery-api/output-caching.md new file mode 100644 index 00000000000..d60988ebfda --- /dev/null +++ b/16/umbraco-cms/reference/content-delivery-api/output-caching.md @@ -0,0 +1,78 @@ +--- +description: Boosting Delivery API performance with output caching. +--- + +# Output caching + +In many cases the Delivery API output is stateless, and it is not absolutely necessary to push content updates immediately. In these cases the API can greatly benefit from using output caching. + +Output caching is an opt-in feature in the Delivery API. It can be configured individually for both the Content and Media Delivery APIs. When enabled, API outputs are cached on the server for each unique request, and re-used until the cache expires. + +Under the hood, the Delivery API utilizes the built-in [output caching middleware in ASP.NET Core](https://learn.microsoft.com/en-us/aspnet/core/performance/caching/output) to handle the cache. + +## Why use output caching? + +Output caching is primarily designed to increase performance. While the Delivery API is performant on its own, output caching takes the performance to another level. + +Another aspect to consider is the overall server load. Uncached requests require much more processing time than cached requests. Especially for high traffic sites, even a short-lived output cache makes a significant difference in the server load. This might result in a lesser need to scale instances, and thus a greener footprint for the site. + +However, output caching does come with a few trade-offs: + +- The cache lives in memory by default. This means the site will consume additional memory to handle the cache. +- The cache does not expire automatically when content changes. Therefore, editors will experience increased publishing time when making changes to existing content. + +{% hint style="info" %} +Requests made in preview mode are not subject to output caching. +{% endhint %} + +## When _not_ to use output caching + +Output caching can be a bad fit in some cases: + +- If editors require immediate publishing of content updates (see above). +- When using personalization in the API output. +- If a custom property editor requires re-rendering for every request (for example if a property value converter outputs the current time). + +## Configuring output caching + +Output caching must be explicitly enabled by configuration. To enable it, add the `OutputCache` section to the `DeliveryApi` configuration in `appsettings.json`: + +{% code title="appsettings.json" %} +```json +{ + "Umbraco": { + "CMS": { + "DeliveryApi": { + "Enabled": true, + "OutputCache": { + "Enabled": true, + "ContentDuration": "00:15:00", + "MediaDuration": "01:00:00" + } + } + } + } +} +``` +{% endcode %} + +The output cache duration (time-to-live) can be configured for the Content and Media Delivery APIs. In the example above, content output is cached for 15 minutes while media output is cached for an hour. + +## Load balancing considerations + +The default output caching mechanism is based on the individual server instance memory. When hosting in a load balanced environment, this likely will not work, as the memory cache isn't synchronized across instances. + +Instead you'll need to use a distributed caching solution like Redis cache. Starting with .NET 8, Microsoft supports output caching with Redis cache as backing store. Read more [here](https://learn.microsoft.com/en-us/aspnet/core/performance/caching/output#redis-cache). + +## Additional considerations + +While output caching is a great way to boost performance, it should never be used as a band-aid to solve poor uncached performance. The Delivery API is generally performant without caching. + +If you experience performance issues while querying the Delivery API, your first step should be to diagnose and fix the root cause. This could be any number of things, like: + +- Un-performant value converters. +- Overly complex queries. +- An inexpedient content architecture. +- ...or something else entirely. + +Hiding such problems behind output caching should only ever be considered as a short-term solution. In the long run it will not be a sustainable fix. diff --git a/16/umbraco-cms/reference/content-delivery-api/property-expansion-and-limiting.md b/16/umbraco-cms/reference/content-delivery-api/property-expansion-and-limiting.md new file mode 100644 index 00000000000..21bb3108cd3 --- /dev/null +++ b/16/umbraco-cms/reference/content-delivery-api/property-expansion-and-limiting.md @@ -0,0 +1,1376 @@ +--- +description: Using property expansion and limiting to shape the Delivery API output +--- + +# Property expansion and limiting + +{% hint style="info" %} +This article explains the mechanics of property expansion and limiting in depth. If you haven't already, please read the [Getting started](https://docs.umbraco.com/umbraco-cms/reference/content-delivery-api) article first - in particular the "Concepts" section. +{% endhint %} + +Property expansion and limiting applies only to select property editors. The following built-in property editors in Umbraco support expansion and limiting: + +- Picker editors + - Content Picker + - Media Picker + - Media Picker (legacy) + - Multinode Treepicker +- Block-based editors + - Block List + - Block Grid + - Rich Text Editor (with blocks) + +{% hint style="info" %} +When working with property expansion and limiting in API queries, there are two rules of thumb to keep in mind: + +1. Expandable properties are _not_ expanded by default. They must be expanded explicitly. +2. All properties are included in the API output by default. We can apply limiting to limit the included properties. +{% endhint %} + +## Working with picker editors + +In the following examples, we will be querying a content tree with blog posts and blog authors: + +- All blog posts are located under a root content item called "Posts". +- All authors are located under a root content item called "Authors". + +The blog post content type (`post`) contains two properties that support expansion and limiting: + +- `author`: A content picker for picking the author of the post. +- `coverImage`: A media picker for picking an image for the post. + +The author content type (`author`) contains a single property that supports expansion and limiting: + +- `picture`: A media picker for picking a picture of the author. + +When fetching a blog post, the `author` and `coverImage` properties are returned in their un-expanded representation by default. This representation does not contain any property data; the `properties` collections of `author` and `coverImage` are empty: + +**Request** + +```http +GET /umbraco/delivery/api/v2/content/item/set-your-content-free/ +Start-Item: posts +``` + +**Response** + +{% code title="Default Delivery API output" %} +```json +{ + "contentType": "post", + "name": "Set your content free", + "createDate": "2023-08-14T08:35:58.8397754", + "updateDate": "2023-08-14T08:54:41.8018872", + "route": { + "path": "/set-your-content-free/", + "startItem": { + "id": "d88fefc3-da04-493d-9533-294a7264b27f", + "path": "posts" + } + }, + "id": "5de65f2e-055e-4e6a-adc3-edb340dcd2e8", + "properties": { + "author": { + "contentType": "author", + "name": "Go Fish", + "createDate": "2023-08-14T08:35:58.9245488", + "updateDate": "2023-11-06T14:21:22.2160133", + "route": { + "path": "/go-fish/", + "startItem": { + "id": "a0e500e2-295e-43e0-ab85-71a44e690c31", + "path": "authors" + } + }, + "id": "f5baeee9-fe8d-4e54-b5c8-fc0373df61cf", + "properties": {} + }, + "coverImage": [ + { + "focalPoint": null, + "crops": [], + "id": "dbb62958-de31-465f-bdbe-5e9079b54b25", + "name": "Arrows", + "mediaType": "Image", + "url": "/media/lyulnwzr/arrows.png", + "extension": "png", + "width": 1080, + "height": 530, + "bytes": 24069, + "properties": {} + } + ], + "excerpt": "Fusce ut mauris ornare, mollis felis ac, convallis neque. Aenean eu tortor ac dui dictum lacinia. Aliquam erat volutpat. Sed malesuada congue imperdiet. Sed dictum aliquam velit. Nunc non nibh dignissim, consequat quam ac, mattis turpis. ", + "content": { + "markup": "

Maecenas consectetur tellus ut aliquet gravida. Mauris at tortor et tellus ultrices tempor vitae nec sapien. Etiam in luctus justo. Praesent rutrum turpis nec maximus congue. Sed sed convallis dui, ut luctus magna. Cras eget diam et sem consequat dictum ut in diam.

\n

Donec feugiat, quam ut varius ultricies, erat purus luctus ex, at sagittis tellus ante vel orci. Nunc dapibus purus feugiat, rhoncus est non, feugiat metus. Vivamus vulputate mauris id urna ultrices sagittis non ut est. Mauris mauris diam, interdum nec lectus et, vehicula faucibus risus. Etiam varius sem erat, nec venenatis elit pellentesque sed. Interdum et malesuada fames ac ante ipsum primis in faucibus.

\n

Sed nibh arcu, feugiat eu arcu sed, posuere lacinia urna. Phasellus interdum euismod sagittis. Etiam vel erat sed erat posuere placerat. Sed pulvinar tincidunt suscipit.

\n

Curabitur molestie turpis elit, vitae bibendum mi convallis a. Etiam convallis, massa vitae sagittis consectetur, lacus odio condimentum felis, ac congue ligula odio eu tortor. Proin dignissim sit amet eros non lobortis. Sed blandit dolor id magna pulvinar auctor. Maecenas sed mi vel arcu pretium sodales in quis urna. Nunc lectus nunc, posuere efficitur dignissim congue, mollis at orci.

", + "blocks": [] + }, + "tags": [ + "Content", + "Awesome" + ] + }, + "cultures": {} +} +``` +{% endcode %} + +If we want to show the author's picture when rendering the blog post, we need to _expand_ the `author` property. By expanding the property, the author properties (including `picture`) are included in the output. We can achieve this by appending the `expand` parameter to our request. + +The `expand` parameter syntax is as follows: + +`expand=properties[propertyAlias1,propertyAlias2,propertyAlias3]` + +Within the `properties` part of the `expand` parameter we can list the aliases of the properties we wish to expand. If we want to expand all expandable properties, we can use the operator `$all` instead: + +`expand=properties[$all]` + +**Request** + +```http +GET /umbraco/delivery/api/v2/content/item/set-your-content-free/ + ?expand=properties[author] +Start-Item: posts +``` + +**Response** + +{% code title="Delivery API output with property expansion" %} +```json +{ + "contentType": "post", + "name": "Set your content free", + "createDate": "2023-08-14T08:35:58.8397754", + "updateDate": "2023-08-14T08:54:41.8018872", + "route": { + "path": "/set-your-content-free/", + "startItem": { + "id": "d88fefc3-da04-493d-9533-294a7264b27f", + "path": "posts" + } + }, + "id": "5de65f2e-055e-4e6a-adc3-edb340dcd2e8", + "properties": { + "author": { + "contentType": "author", + "name": "Go Fish", + "createDate": "2023-08-14T08:35:58.9245488", + "updateDate": "2023-11-06T14:21:22.2160133", + "route": { + "path": "/go-fish/", + "startItem": { + "id": "a0e500e2-295e-43e0-ab85-71a44e690c31", + "path": "authors" + } + }, + "id": "f5baeee9-fe8d-4e54-b5c8-fc0373df61cf", + "properties": { + "picture": [ + { + "focalPoint": null, + "crops": [], + "id": "598c8ce5-1323-4926-98a4-0f3a395dae2d", + "name": "Fishtank", + "mediaType": "Image", + "url": "/media/2ogiywjd/fishtank.png", + "extension": "png", + "width": 1080, + "height": 1080, + "bytes": 26941, + "properties": {} + } + ], + "biography": "Cras fermentum enim vitae varius tristique. Suspendisse tempor quis lacus vitae facilisis. Ut eget mauris tempus dui pulvinar egestas vel eget nibh.\n\nDonec hendrerit sem eu diam sodales luctus non sed urna. Ut sapien ex, imperdiet non eleifend nec, venenatis et purus. Integer tincidunt cursus cursus.\n\nSuspendisse euismod sem nisi, aliquam dignissim sem dictum non. Ut et ex lacus. Fusce ac nisi mattis, ultrices nibh ac, facilisis leo. Aliquam faucibus, elit eu posuere scelerisque, mi enim accumsan lorem, quis rhoncus libero massa quis ligula.", + "dateOfBirth": "1996-04-04T00:00:00Z" + } + }, + "coverImage": [ + { + "focalPoint": null, + "crops": [], + "id": "dbb62958-de31-465f-bdbe-5e9079b54b25", + "name": "Arrows", + "mediaType": "Image", + "url": "/media/lyulnwzr/arrows.png", + "extension": "png", + "width": 1080, + "height": 530, + "bytes": 24069, + "properties": {} + } + ], + "excerpt": "Fusce ut mauris ornare, mollis felis ac, convallis neque. Aenean eu tortor ac dui dictum lacinia. Aliquam erat volutpat. Sed malesuada congue imperdiet. Sed dictum aliquam velit. Nunc non nibh dignissim, consequat quam ac, mattis turpis. ", + "content": { + "markup": "

Maecenas consectetur tellus ut aliquet gravida. Mauris at tortor et tellus ultrices tempor vitae nec sapien. Etiam in luctus justo. Praesent rutrum turpis nec maximus congue. Sed sed convallis dui, ut luctus magna. Cras eget diam et sem consequat dictum ut in diam.

\n

Donec feugiat, quam ut varius ultricies, erat purus luctus ex, at sagittis tellus ante vel orci. Nunc dapibus purus feugiat, rhoncus est non, feugiat metus. Vivamus vulputate mauris id urna ultrices sagittis non ut est. Mauris mauris diam, interdum nec lectus et, vehicula faucibus risus. Etiam varius sem erat, nec venenatis elit pellentesque sed. Interdum et malesuada fames ac ante ipsum primis in faucibus.

\n

Sed nibh arcu, feugiat eu arcu sed, posuere lacinia urna. Phasellus interdum euismod sagittis. Etiam vel erat sed erat posuere placerat. Sed pulvinar tincidunt suscipit.

\n

Curabitur molestie turpis elit, vitae bibendum mi convallis a. Etiam convallis, massa vitae sagittis consectetur, lacus odio condimentum felis, ac congue ligula odio eu tortor. Proin dignissim sit amet eros non lobortis. Sed blandit dolor id magna pulvinar auctor. Maecenas sed mi vel arcu pretium sodales in quis urna. Nunc lectus nunc, posuere efficitur dignissim congue, mollis at orci.

\n

Aenean elit arcu, cursus sit amet vulputate eu, dignissim sit amet lorem. Nunc tempus rhoncus lorem, sit amet finibus mi scelerisque sollicitudin. Pellentesque auctor, nunc facilisis molestie ullamcorper, justo magna pellentesque dui, vel posuere est massa eget nibh. Sed dignissim eget purus vitae malesuada. Maecenas eu ultricies nunc. Sed porta pretium ligula, non auctor arcu blandit elementum. Nullam nibh metus, consequat non imperdiet et, scelerisque id urna. Vivamus mollis, dolor ac fringilla bibendum, leo turpis finibus libero, sit amet porttitor nunc magna non diam.

\n

Curabitur cursus ullamcorper mauris. Fusce porta turpis at metus fermentum, commodo rhoncus leo dignissim. Etiam eu eleifend elit, sit amet pulvinar nibh. In pharetra massa massa, sit amet convallis leo consectetur vitae. In id pharetra sapien, sit amet tincidunt enim.

\n

Duis velit lectus, mollis eu eros sed, dignissim scelerisque neque. Mauris blandit sit amet diam dignissim cursus.

\n

Integer ornare ultrices finibus. Nullam iaculis eget sapien in porttitor. Etiam sit amet viverra mauris. Vestibulum risus ligula, placerat eget tortor vel, mattis porta odio. Vestibulum non euismod lacus. Integer quis velit suscipit, commodo mi sed, rutrum metus. Donec auctor ex ut elit vehicula, quis tincidunt odio laoreet. Suspendisse pharetra sit amet ante quis consequat. Duis finibus pellentesque urna ac molestie.

", + "blocks": [] + }, + "tags": [ + "Content", + "Awesome" + ] + }, + "cultures": {} +} +``` +{% endcode %} + +Now we have the `picture` data in the `properties` collection of `author`. However, the rest of the author's properties (`biography` and `dateOfBirth`) are also present in our output, so we are slightly over-fetching. We will take care of that later. + +First, we need to get the alt texts of our images (the blog post `coverImage` and the author `picture`). The alt text in this case is a text string property (`altText`) on the media type. Fetching the alt texts is possible because property expansion can be performed both across multiple properties and in a nested fashion. + +For nested property expansion, the `expand` parameter syntax is as follows: + +`expand=properties[propertyAlias[properties[nestedPropertyAlias1,nestedPropertyAlias2]]]` + +Nested property expansion can also be combined with the `$all` operator: + +`expand=properties[$all[properties[nestedPropertyAlias1,nestedPropertyAlias2]]]` + +{% hint style="info" %} +There is no API limit to how "deep" the nesting can go. Eventually though, the total length of the request URL may become a hard limit to the size of the query. +{% endhint %} + +Let's amend the `expand` parameter to accommodate expansion of the images: + +**Request** + +```http +GET /umbraco/delivery/api/v2/content/item/set-your-content-free/ + ?expand=properties[coverImage,author[properties[picture]]] +Start-Item: posts +``` + +**Response** + +{% code title="Delivery API output with nested property expansion" %} +```json +{ + "contentType": "post", + "name": "Set your content free", + "createDate": "2023-08-14T08:35:58.8397754", + "updateDate": "2023-08-14T08:54:41.8018872", + "route": { + "path": "/set-your-content-free/", + "startItem": { + "id": "d88fefc3-da04-493d-9533-294a7264b27f", + "path": "posts" + } + }, + "id": "5de65f2e-055e-4e6a-adc3-edb340dcd2e8", + "properties": { + "author": { + "contentType": "author", + "name": "Go Fish", + "createDate": "2023-08-14T08:35:58.9245488", + "updateDate": "2023-11-06T14:21:22.2160133", + "route": { + "path": "/go-fish/", + "startItem": { + "id": "a0e500e2-295e-43e0-ab85-71a44e690c31", + "path": "authors" + } + }, + "id": "f5baeee9-fe8d-4e54-b5c8-fc0373df61cf", + "properties": { + "picture": [ + { + "focalPoint": null, + "crops": [], + "id": "598c8ce5-1323-4926-98a4-0f3a395dae2d", + "name": "Fishtank", + "mediaType": "Image", + "url": "/media/2ogiywjd/fishtank.png", + "extension": "png", + "width": 1080, + "height": 1080, + "bytes": 26941, + "properties": { + "altText": "An image of the author Fishtank" + } + } + ], + "biography": "Cras fermentum enim vitae varius tristique. Suspendisse tempor quis lacus vitae facilisis. Ut eget mauris tempus dui pulvinar egestas vel eget nibh.\n\nDonec hendrerit sem eu diam sodales luctus non sed urna. Ut sapien ex, imperdiet non eleifend nec, venenatis et purus. Integer tincidunt cursus cursus.\n\nSuspendisse euismod sem nisi, aliquam dignissim sem dictum non. Ut et ex lacus. Fusce ac nisi mattis, ultrices nibh ac, facilisis leo. Aliquam faucibus, elit eu posuere scelerisque, mi enim accumsan lorem, quis rhoncus libero massa quis ligula.", + "dateOfBirth": "1996-04-04T00:00:00Z" + } + }, + "coverImage": [ + { + "focalPoint": null, + "crops": [], + "id": "dbb62958-de31-465f-bdbe-5e9079b54b25", + "name": "Arrows", + "mediaType": "Image", + "url": "/media/lyulnwzr/arrows.png", + "extension": "png", + "width": 1080, + "height": 530, + "bytes": 24069, + "properties": { + "altText": "Some arrows pointing in different directions (but generally upwards)" + } + } + ], + "excerpt": "Fusce ut mauris ornare, mollis felis ac, convallis neque. Aenean eu tortor ac dui dictum lacinia. Aliquam erat volutpat. Sed malesuada congue imperdiet. Sed dictum aliquam velit. Nunc non nibh dignissim, consequat quam ac, mattis turpis. ", + "content": { + "markup": "

Maecenas consectetur tellus ut aliquet gravida. Mauris at tortor et tellus ultrices tempor vitae nec sapien. Etiam in luctus justo. Praesent rutrum turpis nec maximus congue. Sed sed convallis dui, ut luctus magna. Cras eget diam et sem consequat dictum ut in diam.

\n

Donec feugiat, quam ut varius ultricies, erat purus luctus ex, at sagittis tellus ante vel orci. Nunc dapibus purus feugiat, rhoncus est non, feugiat metus. Vivamus vulputate mauris id urna ultrices sagittis non ut est. Mauris mauris diam, interdum nec lectus et, vehicula faucibus risus. Etiam varius sem erat, nec venenatis elit pellentesque sed. Interdum et malesuada fames ac ante ipsum primis in faucibus.

\n

Sed nibh arcu, feugiat eu arcu sed, posuere lacinia urna. Phasellus interdum euismod sagittis. Etiam vel erat sed erat posuere placerat. Sed pulvinar tincidunt suscipit.

\n

Curabitur molestie turpis elit, vitae bibendum mi convallis a. Etiam convallis, massa vitae sagittis consectetur, lacus odio condimentum felis, ac congue ligula odio eu tortor. Proin dignissim sit amet eros non lobortis. Sed blandit dolor id magna pulvinar auctor. Maecenas sed mi vel arcu pretium sodales in quis urna. Nunc lectus nunc, posuere efficitur dignissim congue, mollis at orci.

\n

Aenean elit arcu, cursus sit amet vulputate eu, dignissim sit amet lorem. Nunc tempus rhoncus lorem, sit amet finibus mi scelerisque sollicitudin. Pellentesque auctor, nunc facilisis molestie ullamcorper, justo magna pellentesque dui, vel posuere est massa eget nibh. Sed dignissim eget purus vitae malesuada. Maecenas eu ultricies nunc. Sed porta pretium ligula, non auctor arcu blandit elementum. Nullam nibh metus, consequat non imperdiet et, scelerisque id urna. Vivamus mollis, dolor ac fringilla bibendum, leo turpis finibus libero, sit amet porttitor nunc magna non diam.

\n

Curabitur cursus ullamcorper mauris. Fusce porta turpis at metus fermentum, commodo rhoncus leo dignissim. Etiam eu eleifend elit, sit amet pulvinar nibh. In pharetra massa massa, sit amet convallis leo consectetur vitae. In id pharetra sapien, sit amet tincidunt enim.

\n

Duis velit lectus, mollis eu eros sed, dignissim scelerisque neque. Mauris blandit sit amet diam dignissim cursus.

\n

Integer ornare ultrices finibus. Nullam iaculis eget sapien in porttitor. Etiam sit amet viverra mauris. Vestibulum risus ligula, placerat eget tortor vel, mattis porta odio. Vestibulum non euismod lacus. Integer quis velit suscipit, commodo mi sed, rutrum metus. Donec auctor ex ut elit vehicula, quis tincidunt odio laoreet. Suspendisse pharetra sit amet ante quis consequat. Duis finibus pellentesque urna ac molestie.

", + "blocks": [] + }, + "tags": [ + "Content", + "Awesome" + ] + }, + "cultures": {} +} +``` +{% endcode %} + +As mentioned above we are slightly over-fetching. We don't need all the author data - we are only interested in the author's `picture`. To fix this we can apply property limiting by adding the `fields` parameter to our request. + +The `fields` parameter allows us to limit the properties in the output to only those specified. The parameter uses the same syntax as the `expand` parameter. + +Our ideal blog post output contains: + +- All the properties of the blog post, including the `altText` of the post `coverImage`. +- Only the `picture` property of `author`, including the `altText` of the author `picture`. + +As with property expansion, we can use the `$all` operator in the `fields` parameter. This will include everything at any given query level. We'll use this to include all the blog post properties in the output without having to specify each property explicitly: + +**Request** + +```http +GET /umbraco/delivery/api/v2/content/item/set-your-content-free/ + ?expand=properties[coverImage,author[properties[picture]]] + &fields=properties[$all[properties[altText,picture[properties[altText]]]]] +Start-Item: posts +``` + +**Response** + +{% code title="Delivery API output with property expansion and limiting" %} +```json +{ + "contentType": "post", + "name": "Set your content free", + "createDate": "2023-08-14T08:35:58.8397754", + "updateDate": "2023-08-14T08:54:41.8018872", + "route": { + "path": "/set-your-content-free/", + "startItem": { + "id": "d88fefc3-da04-493d-9533-294a7264b27f", + "path": "posts" + } + }, + "id": "5de65f2e-055e-4e6a-adc3-edb340dcd2e8", + "properties": { + "author": { + "contentType": "author", + "name": "Go Fish", + "createDate": "2023-08-14T08:35:58.9245488", + "updateDate": "2023-11-06T14:21:22.2160133", + "route": { + "path": "/go-fish/", + "startItem": { + "id": "a0e500e2-295e-43e0-ab85-71a44e690c31", + "path": "authors" + } + }, + "id": "f5baeee9-fe8d-4e54-b5c8-fc0373df61cf", + "properties": { + "picture": [ + { + "focalPoint": null, + "crops": [], + "id": "598c8ce5-1323-4926-98a4-0f3a395dae2d", + "name": "Fishtank", + "mediaType": "Image", + "url": "/media/2ogiywjd/fishtank.png", + "extension": "png", + "width": 1080, + "height": 1080, + "bytes": 26941, + "properties": { + "altText": "An image of the author Fishtank" + } + } + ] + } + }, + "coverImage": [ + { + "focalPoint": null, + "crops": [], + "id": "dbb62958-de31-465f-bdbe-5e9079b54b25", + "name": "Arrows", + "mediaType": "Image", + "url": "/media/lyulnwzr/arrows.png", + "extension": "png", + "width": 1080, + "height": 530, + "bytes": 24069, + "properties": { + "altText": "Some arrows pointing in different directions (but generally upwards)" + } + } + ], + "excerpt": "Fusce ut mauris ornare, mollis felis ac, convallis neque. Aenean eu tortor ac dui dictum lacinia. Aliquam erat volutpat. Sed malesuada congue imperdiet. Sed dictum aliquam velit. Nunc non nibh dignissim, consequat quam ac, mattis turpis. ", + "content": { + "markup": "

Maecenas consectetur tellus ut aliquet gravida. Mauris at tortor et tellus ultrices tempor vitae nec sapien. Etiam in luctus justo. Praesent rutrum turpis nec maximus congue. Sed sed convallis dui, ut luctus magna. Cras eget diam et sem consequat dictum ut in diam.

\n

Donec feugiat, quam ut varius ultricies, erat purus luctus ex, at sagittis tellus ante vel orci. Nunc dapibus purus feugiat, rhoncus est non, feugiat metus. Vivamus vulputate mauris id urna ultrices sagittis non ut est. Mauris mauris diam, interdum nec lectus et, vehicula faucibus risus. Etiam varius sem erat, nec venenatis elit pellentesque sed. Interdum et malesuada fames ac ante ipsum primis in faucibus.

\n

Sed nibh arcu, feugiat eu arcu sed, posuere lacinia urna. Phasellus interdum euismod sagittis. Etiam vel erat sed erat posuere placerat. Sed pulvinar tincidunt suscipit.

\n

Curabitur molestie turpis elit, vitae bibendum mi convallis a. Etiam convallis, massa vitae sagittis consectetur, lacus odio condimentum felis, ac congue ligula odio eu tortor. Proin dignissim sit amet eros non lobortis. Sed blandit dolor id magna pulvinar auctor. Maecenas sed mi vel arcu pretium sodales in quis urna. Nunc lectus nunc, posuere efficitur dignissim congue, mollis at orci.

\n

Aenean elit arcu, cursus sit amet vulputate eu, dignissim sit amet lorem. Nunc tempus rhoncus lorem, sit amet finibus mi scelerisque sollicitudin. Pellentesque auctor, nunc facilisis molestie ullamcorper, justo magna pellentesque dui, vel posuere est massa eget nibh. Sed dignissim eget purus vitae malesuada. Maecenas eu ultricies nunc. Sed porta pretium ligula, non auctor arcu blandit elementum. Nullam nibh metus, consequat non imperdiet et, scelerisque id urna. Vivamus mollis, dolor ac fringilla bibendum, leo turpis finibus libero, sit amet porttitor nunc magna non diam.

\n

Curabitur cursus ullamcorper mauris. Fusce porta turpis at metus fermentum, commodo rhoncus leo dignissim. Etiam eu eleifend elit, sit amet pulvinar nibh. In pharetra massa massa, sit amet convallis leo consectetur vitae. In id pharetra sapien, sit amet tincidunt enim.

\n

Duis velit lectus, mollis eu eros sed, dignissim scelerisque neque. Mauris blandit sit amet diam dignissim cursus.

\n

Integer ornare ultrices finibus. Nullam iaculis eget sapien in porttitor. Etiam sit amet viverra mauris. Vestibulum risus ligula, placerat eget tortor vel, mattis porta odio. Vestibulum non euismod lacus. Integer quis velit suscipit, commodo mi sed, rutrum metus. Donec auctor ex ut elit vehicula, quis tincidunt odio laoreet. Suspendisse pharetra sit amet ante quis consequat. Duis finibus pellentesque urna ac molestie.

", + "blocks": [] + }, + "tags": [ + "Content", + "Awesome" + ] + }, + "cultures": {} +} +``` +{% endcode %} + +Now the API output contains only the properties we need to render the blog post. + +Property limiting is particularly useful when querying multiple items. For example, if we were building a condensed list of blog posts, we likely wouldn't need the author data nor the blog post content. By applying limiting to a filtered query, we can tailor the API output specifically to this scenario: + +**Request** + +```http +GET /umbraco/delivery/api/v2/content/ + ?filter=contentType:post + &expand=properties[coverImage] + &fields=properties[excerpt,tags,coverImage[properties[altText]]] +Start-Item: posts +``` + +**Response** + +{% code title="Delivery API query output with property expansion and limiting" %} +```json +{ + "total": 6, + "items": [ + { + "contentType": "post", + "name": "Set your content free", + "createDate": "2023-08-14T08:35:58.8397754", + "updateDate": "2023-08-14T08:54:41.8018872", + "route": { + "path": "/set-your-content-free/", + "startItem": { + "id": "d88fefc3-da04-493d-9533-294a7264b27f", + "path": "posts" + } + }, + "id": "5de65f2e-055e-4e6a-adc3-edb340dcd2e8", + "properties": { + "coverImage": [ + { + "focalPoint": null, + "crops": [], + "id": "dbb62958-de31-465f-bdbe-5e9079b54b25", + "name": "Arrows", + "mediaType": "Image", + "url": "/media/lyulnwzr/arrows.png", + "extension": "png", + "width": 1080, + "height": 530, + "bytes": 24069, + "properties": { + "altText": "Some arrows pointing in different directions (but generally upwards)" + } + } + ], + "excerpt": "Fusce ut mauris ornare, mollis felis ac, convallis neque. Aenean eu tortor ac dui dictum lacinia. Aliquam erat volutpat. Sed malesuada congue imperdiet. Sed dictum aliquam velit. Nunc non nibh dignissim, consequat quam ac, mattis turpis. ", + "tags": [ + "Content", + "Awesome" + ] + }, + "cultures": {} + }, + { + "contentType": "post", + "name": "Building a community", + "createDate": "2023-08-14T08:35:58.6755792", + "updateDate": "2023-08-14T08:54:45.5526111", + "route": { + "path": "/building-a-community/", + "startItem": { + "id": "d88fefc3-da04-493d-9533-294a7264b27f", + "path": "posts" + } + }, + "id": "6e1bc040-f382-402c-ac56-3328d5f424c7", + "properties": { + "coverImage": [ + { + "focalPoint": null, + "crops": [], + "id": "9757a20c-3e98-49ef-8103-c7fa746856e7", + "name": "Community", + "mediaType": "Image", + "url": "/media/bkrfpf2y/community.png", + "extension": "png", + "width": 1080, + "height": 540, + "bytes": 25832, + "properties": { + "altText": "Two hands constructing a heart from coloured pieces" + } + } + ], + "excerpt": "Maecenas ipsum dui, lobortis non dui eleifend, dapibus bibendum dui. Pellentesque dolor felis, mollis nec diam eget, fermentum rutrum ipsum. Vestibulum condimentum urna id turpis tempus, id finibus sem facilisis. Nullam commodo felis quis ante posuere, dictum suscipit nisl suscipit.", + "tags": [ + "Community", + "Inspiration", + "Awesome" + ] + }, + "cultures": {} + }, + ... + ] +} +``` +{% endcode %} + +## Working with block-based editors + +{% hint style="info" %} +If you are not familiar with block-based editors, please refer to [this article](https://docs.umbraco.com/umbraco-cms/fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/block-editor) for the general concepts of these. +{% endhint %} + +In the API output, a block has little value without its contained properties. Therefore, the content and settings properties of blocks are always included in the output. However, these properties are not expanded. As such, we can apply expansion and limiting to the contained properties. + +In the following examples we'll request different types of articles, all of which are located under a root content item called "Articles": + +- An article with a Block List property (`blockList`). +- An article with a Block Grid property (`blockGrid`). +- An article with a Rich Text Editor property (`richText`). + +All these properties are configured with a "Featured Post" block which consists of: + +- A content model (`featuredPost`) that contains: + - `title`: A text string property. + - `post`: A content picker property that allows for picking a blog post. +- A settings model (`featuredPostSettings`) that contains: + - `backgroundColor`: An approved color property. + - `showTags`: A toggle property. + +The goal is once again to build a condensed list of blog posts. But this time we'll build the list from the "Featured Post" blocks within each block editor. + +To build the list we need the block `title`, the `coverImage` and `excerpt` from the picked post, and the `backgroundColor` from the block settings. Thus, we need to: + +- Expand the `post` property to retrieve the `altText` of the post `coverImage`. +- Limit both the block-level properties and the nested `post` properties, as to only output the properties relevant for building the condensed list. + +{% hint style="info" %} +For comparison, the samples show both the default output and the output with expansion and limiting applied. Notice that: + +- The `expand` and `fields` parameter syntax is the same for all editors, even though their rendered output is structurally different. +- The `expand` and `fields` parameters target both the content and settings parts of each block. +{% endhint %} + +### Block List + +Default output without expansion and limiting: + +**Request** + +```http +GET /umbraco/delivery/api/v2/content/item/article-with-block-list/ +Start-Item: articles +``` + +**Response** + +{% code title="Default Delivery API output" %} +```json +{ + "contentType": "articleWithBlockList", + "name": "Article with Block List", + "createDate": "2023-11-07T16:14:14.0913877", + "updateDate": "2023-11-07T16:30:58.0572288", + "route": { + "path": "/article-with-block-list/", + "startItem": { + "id": "5c8823f6-6442-4d04-9c14-f3411bd41aa8", + "path": "articles" + } + }, + "id": "c1d139df-c08d-4225-909f-f176d727dce6", + "properties": { + "blockList": { + "items": [ + { + "content": { + "contentType": "featuredPost", + "id": "4d5931fb-ad3c-4759-9824-777a2f3c6a85", + "properties": { + "title": "Free your content with the Delivery API", + "post": { + "contentType": "post", + "name": "Set your content free", + "createDate": "2023-08-14T08:35:58.8397754", + "updateDate": "2023-08-14T08:54:41.8018872", + "route": { + "path": "/set-your-content-free/", + "startItem": { + "id": "d88fefc3-da04-493d-9533-294a7264b27f", + "path": "posts" + } + }, + "id": "5de65f2e-055e-4e6a-adc3-edb340dcd2e8", + "properties": {} + } + } + }, + "settings": { + "contentType": "featuredPostSettings", + "id": "aff73d6b-cfd1-4471-92d8-68fc07fdb6fd", + "properties": { + "backgroundColor": "ce7e00", + "showTags": true + } + } + }, + { + "content": { + "contentType": "featuredPost", + "id": "76dabfff-7e86-4404-9a89-1b8274ac4f8e", + "properties": { + "title": "Community is key", + "post": { + "contentType": "post", + "name": "Building a community", + "createDate": "2023-08-14T08:35:58.6755792", + "updateDate": "2023-08-14T08:54:45.5526111", + "route": { + "path": "/building-a-community/", + "startItem": { + "id": "d88fefc3-da04-493d-9533-294a7264b27f", + "path": "posts" + } + }, + "id": "6e1bc040-f382-402c-ac56-3328d5f424c7", + "properties": {} + } + } + }, + "settings": { + "contentType": "featuredPostSettings", + "id": "175325ab-b4a0-489d-a196-c1cf32b41ca1", + "properties": { + "backgroundColor": "6fa8dc", + "showTags": false + } + } + } + ] + } + }, + "cultures": {} +} +``` +{% endcode %} + +Output with property expansion and limiting: + +**Request** + +```http +GET /umbraco/delivery/api/v2/content/item/article-with-block-list/ + ?expand=properties[blockList[properties[post[properties[coverImage]]]]] + &fields=properties[blockList[properties[title,post[properties[excerpt,coverImage]],backgroundColor]]] +Start-Item: articles +``` + +**Response** + +{% code title="Delivery API output with expansion and limiting" %} +```json +{ + "contentType": "articleWithBlockList", + "name": "Article with Block List", + "createDate": "2023-11-07T16:14:14.0913877", + "updateDate": "2023-11-07T16:34:45.6079582", + "route": { + "path": "/article-with-block-list/", + "startItem": { + "id": "5c8823f6-6442-4d04-9c14-f3411bd41aa8", + "path": "articles" + } + }, + "id": "c1d139df-c08d-4225-909f-f176d727dce6", + "properties": { + "blockList": { + "items": [ + { + "content": { + "contentType": "featuredPost", + "id": "4d5931fb-ad3c-4759-9824-777a2f3c6a85", + "properties": { + "title": "Free your content with the Delivery API", + "post": { + "contentType": "post", + "name": "Set your content free", + "createDate": "2023-08-14T08:35:58.8397754", + "updateDate": "2023-08-14T08:54:41.8018872", + "route": { + "path": "/set-your-content-free/", + "startItem": { + "id": "d88fefc3-da04-493d-9533-294a7264b27f", + "path": "posts" + } + }, + "id": "5de65f2e-055e-4e6a-adc3-edb340dcd2e8", + "properties": { + "coverImage": [ + { + "focalPoint": null, + "crops": [], + "id": "dbb62958-de31-465f-bdbe-5e9079b54b25", + "name": "Arrows", + "mediaType": "Image", + "url": "/media/lyulnwzr/arrows.png", + "extension": "png", + "width": 1080, + "height": 530, + "bytes": 24069, + "properties": { + "altText": "Some arrows pointing in different directions (but generally upwards)" + } + } + ], + "excerpt": "Fusce ut mauris ornare, mollis felis ac, convallis neque. Aenean eu tortor ac dui dictum lacinia. Aliquam erat volutpat. Sed malesuada congue imperdiet. Sed dictum aliquam velit. Nunc non nibh dignissim, consequat quam ac, mattis turpis. " + } + } + } + }, + "settings": { + "contentType": "featuredPostSettings", + "id": "aff73d6b-cfd1-4471-92d8-68fc07fdb6fd", + "properties": { + "backgroundColor": "ce7e00" + } + } + }, + { + "content": { + "contentType": "featuredPost", + "id": "76dabfff-7e86-4404-9a89-1b8274ac4f8e", + "properties": { + "title": "Community is key", + "post": { + "contentType": "post", + "name": "Building a community", + "createDate": "2023-08-14T08:35:58.6755792", + "updateDate": "2023-08-14T08:54:45.5526111", + "route": { + "path": "/building-a-community/", + "startItem": { + "id": "d88fefc3-da04-493d-9533-294a7264b27f", + "path": "posts" + } + }, + "id": "6e1bc040-f382-402c-ac56-3328d5f424c7", + "properties": { + "coverImage": [ + { + "focalPoint": null, + "crops": [], + "id": "9757a20c-3e98-49ef-8103-c7fa746856e7", + "name": "Community", + "mediaType": "Image", + "url": "/media/bkrfpf2y/community.png", + "extension": "png", + "width": 1080, + "height": 540, + "bytes": 25832, + "properties": { + "altText": "Two hands constructing a heart from coloured pieces" + } + } + ], + "excerpt": "Maecenas ipsum dui, lobortis non dui eleifend, dapibus bibendum dui. Pellentesque dolor felis, mollis nec diam eget, fermentum rutrum ipsum. Vestibulum condimentum urna id turpis tempus, id finibus sem facilisis. Nullam commodo felis quis ante posuere, dictum suscipit nisl suscipit." + } + } + } + }, + "settings": { + "contentType": "featuredPostSettings", + "id": "175325ab-b4a0-489d-a196-c1cf32b41ca1", + "properties": { + "backgroundColor": "6fa8dc" + } + } + } + ] + } + }, + "cultures": {} +} +``` +{% endcode %} + +### Block Grid + +Default output without expansion and limiting: + +**Request** + +```http +GET /umbraco/delivery/api/v2/content/item/article-with-block-grid/ +Start-Item: articles +``` + +**Response** + +{% code title="Default Delivery API output" %} +```json +{ + "contentType": "articleWithBlockGrid", + "name": "Article with Block Grid", + "createDate": "2023-11-07T16:34:19.1629081", + "updateDate": "2023-11-07T16:34:36.3701542", + "route": { + "path": "/article-with-block-grid/", + "startItem": { + "id": "5c8823f6-6442-4d04-9c14-f3411bd41aa8", + "path": "articles" + } + }, + "id": "17ed80d1-79e0-43b9-9ad6-7dad6a78e220", + "properties": { + "blockGrid": { + "gridColumns": 12, + "items": [ + { + "rowSpan": 1, + "columnSpan": 12, + "areaGridColumns": 12, + "areas": [ + { + "alias": "left", + "rowSpan": 1, + "columnSpan": 6, + "items": [ + { + "rowSpan": 1, + "columnSpan": 6, + "areaGridColumns": 12, + "areas": [], + "content": { + "contentType": "featuredPost", + "id": "8a509b3d-20f1-4b92-b74c-c74a309baec8", + "properties": { + "title": "Free your content with the Delivery API", + "post": { + "contentType": "post", + "name": "Set your content free", + "createDate": "2023-08-14T08:35:58.8397754", + "updateDate": "2023-08-14T08:54:41.8018872", + "route": { + "path": "/set-your-content-free/", + "startItem": { + "id": "d88fefc3-da04-493d-9533-294a7264b27f", + "path": "posts" + } + }, + "id": "5de65f2e-055e-4e6a-adc3-edb340dcd2e8", + "properties": {} + } + } + }, + "settings": { + "contentType": "featuredPostSettings", + "id": "81e51973-5107-4e9d-a1aa-71752ea7a9f4", + "properties": { + "backgroundColor": "ce7e00", + "showTags": true + } + } + } + ] + }, + { + "alias": "right", + "rowSpan": 1, + "columnSpan": 6, + "items": [ + { + "rowSpan": 1, + "columnSpan": 6, + "areaGridColumns": 12, + "areas": [], + "content": { + "contentType": "featuredPost", + "id": "de53752f-f3f1-4d17-a0f3-14ea62456fba", + "properties": { + "title": "Community is key", + "post": { + "contentType": "post", + "name": "Building a community", + "createDate": "2023-08-14T08:35:58.6755792", + "updateDate": "2023-08-14T08:54:45.5526111", + "route": { + "path": "/building-a-community/", + "startItem": { + "id": "d88fefc3-da04-493d-9533-294a7264b27f", + "path": "posts" + } + }, + "id": "6e1bc040-f382-402c-ac56-3328d5f424c7", + "properties": {} + } + } + }, + "settings": { + "contentType": "featuredPostSettings", + "id": "6471bae6-1341-4bbd-9e19-e61e8df4446d", + "properties": { + "backgroundColor": "6fa8dc", + "showTags": false + } + } + } + ] + } + ], + "content": { + "contentType": "twoColumnLayout", + "id": "40d5cf9c-809c-41ed-8639-84d894c2e663", + "properties": {} + }, + "settings": null + } + ] + } + }, + "cultures": {} +} +``` +{% endcode %} + +Output with property expansion and limiting: + +**Request** + +```http +GET /umbraco/delivery/api/v2/content/item/article-with-block-grid/ + ?expand=properties[blockGrid[properties[post[properties[coverImage]]]]] + &fields=properties[blockGrid[properties[title,post[properties[excerpt,coverImage]],backgroundColor]]] +Start-Item: articles +``` + +**Response** + +{% code title="Delivery API output with expansion and limiting" %} +```json +{ + "contentType": "articleWithBlockGrid", + "name": "Article with Block Grid", + "createDate": "2023-11-07T16:34:19.1629081", + "updateDate": "2023-11-07T16:34:36.3701542", + "route": { + "path": "/article-with-block-grid/", + "startItem": { + "id": "5c8823f6-6442-4d04-9c14-f3411bd41aa8", + "path": "articles" + } + }, + "id": "17ed80d1-79e0-43b9-9ad6-7dad6a78e220", + "properties": { + "blockGrid": { + "gridColumns": 12, + "items": [ + { + "rowSpan": 1, + "columnSpan": 12, + "areaGridColumns": 12, + "areas": [ + { + "alias": "left", + "rowSpan": 1, + "columnSpan": 6, + "items": [ + { + "rowSpan": 1, + "columnSpan": 6, + "areaGridColumns": 12, + "areas": [], + "content": { + "contentType": "featuredPost", + "id": "8a509b3d-20f1-4b92-b74c-c74a309baec8", + "properties": { + "title": "Free your content with the Delivery API", + "post": { + "contentType": "post", + "name": "Set your content free", + "createDate": "2023-08-14T08:35:58.8397754", + "updateDate": "2023-08-14T08:54:41.8018872", + "route": { + "path": "/set-your-content-free/", + "startItem": { + "id": "d88fefc3-da04-493d-9533-294a7264b27f", + "path": "posts" + } + }, + "id": "5de65f2e-055e-4e6a-adc3-edb340dcd2e8", + "properties": { + "coverImage": [ + { + "focalPoint": null, + "crops": [], + "id": "dbb62958-de31-465f-bdbe-5e9079b54b25", + "name": "Arrows", + "mediaType": "Image", + "url": "/media/lyulnwzr/arrows.png", + "extension": "png", + "width": 1080, + "height": 530, + "bytes": 24069, + "properties": { + "altText": "Some arrows pointing in different directions (but generally upwards)" + } + } + ], + "excerpt": "Fusce ut mauris ornare, mollis felis ac, convallis neque. Aenean eu tortor ac dui dictum lacinia. Aliquam erat volutpat. Sed malesuada congue imperdiet. Sed dictum aliquam velit. Nunc non nibh dignissim, consequat quam ac, mattis turpis. " + } + } + } + }, + "settings": { + "contentType": "featuredPostSettings", + "id": "81e51973-5107-4e9d-a1aa-71752ea7a9f4", + "properties": { + "backgroundColor": "ce7e00" + } + } + } + ] + }, + { + "alias": "right", + "rowSpan": 1, + "columnSpan": 6, + "items": [ + { + "rowSpan": 1, + "columnSpan": 6, + "areaGridColumns": 12, + "areas": [], + "content": { + "contentType": "featuredPost", + "id": "de53752f-f3f1-4d17-a0f3-14ea62456fba", + "properties": { + "title": "Community is key", + "post": { + "contentType": "post", + "name": "Building a community", + "createDate": "2023-08-14T08:35:58.6755792", + "updateDate": "2023-08-14T08:54:45.5526111", + "route": { + "path": "/building-a-community/", + "startItem": { + "id": "d88fefc3-da04-493d-9533-294a7264b27f", + "path": "posts" + } + }, + "id": "6e1bc040-f382-402c-ac56-3328d5f424c7", + "properties": { + "coverImage": [ + { + "focalPoint": null, + "crops": [], + "id": "9757a20c-3e98-49ef-8103-c7fa746856e7", + "name": "Community", + "mediaType": "Image", + "url": "/media/bkrfpf2y/community.png", + "extension": "png", + "width": 1080, + "height": 540, + "bytes": 25832, + "properties": { + "altText": "Two hands constructing a heart from coloured pieces" + } + } + ], + "excerpt": "Maecenas ipsum dui, lobortis non dui eleifend, dapibus bibendum dui. Pellentesque dolor felis, mollis nec diam eget, fermentum rutrum ipsum. Vestibulum condimentum urna id turpis tempus, id finibus sem facilisis. Nullam commodo felis quis ante posuere, dictum suscipit nisl suscipit." + } + } + } + }, + "settings": { + "contentType": "featuredPostSettings", + "id": "6471bae6-1341-4bbd-9e19-e61e8df4446d", + "properties": { + "backgroundColor": "6fa8dc" + } + } + } + ] + } + ], + "content": { + "contentType": "twoColumnLayout", + "id": "40d5cf9c-809c-41ed-8639-84d894c2e663", + "properties": {} + }, + "settings": null + } + ] + } + }, + "cultures": {} +} +``` +{% endcode %} + +### Rich Text Editor (with blocks) + +**Request** + +```http +GET /umbraco/delivery/api/v2/content/item/article-with-rich-text/ +Start-Item: articles +``` + +**Response** + +{% code title="Default Delivery API output" %} +```json +{ + "contentType": "articleWithRichText", + "name": "Article with Rich Text", + "createDate": "2023-11-07T16:33:51.5326056", + "updateDate": "2023-11-07T16:34:11.0504555", + "route": { + "path": "/article-with-rich-text/", + "startItem": { + "id": "5c8823f6-6442-4d04-9c14-f3411bd41aa8", + "path": "articles" + } + }, + "id": "785a7731-4312-4ed6-b11c-2f82db85c930", + "properties": { + "richText": { + "markup": "

Proin mattis enim arcu, ac maximus magna auctor a. Proin sed porttitor nibh, eget venenatis felis. Pellentesque mattis feugiat ultrices. Duis libero velit, sagittis eu nunc euismod, lacinia aliquam elit.

\n\n

Nulla quis fringilla sem. Integer id lacus sit amet ante gravida ullamcorper ac in neque. Etiam at ipsum a augue laoreet commodo. Vestibulum bibendum viverra diam, vel dignissim nunc suscipit id. 

\n\n

 

", + "blocks": [ + { + "content": { + "contentType": "featuredPost", + "id": "a42d825e-383c-4699-ae7f-eb851c0a5b13", + "properties": { + "title": "Free your content with the Delivery API", + "post": { + "contentType": "post", + "name": "Set your content free", + "createDate": "2023-08-14T08:35:58.8397754", + "updateDate": "2023-08-14T08:54:41.8018872", + "route": { + "path": "/set-your-content-free/", + "startItem": { + "id": "d88fefc3-da04-493d-9533-294a7264b27f", + "path": "posts" + } + }, + "id": "5de65f2e-055e-4e6a-adc3-edb340dcd2e8", + "properties": {} + } + } + }, + "settings": { + "contentType": "featuredPostSettings", + "id": "109e1f19-93b9-4f5a-9ab2-315aa618c42a", + "properties": { + "backgroundColor": "ce7e00", + "showTags": true + } + } + }, + { + "content": { + "contentType": "featuredPost", + "id": "724ecae7-3118-46a8-8dc2-a9bf9e10efdb", + "properties": { + "title": "Community is key", + "post": { + "contentType": "post", + "name": "Building a community", + "createDate": "2023-08-14T08:35:58.6755792", + "updateDate": "2023-08-14T08:54:45.5526111", + "route": { + "path": "/building-a-community/", + "startItem": { + "id": "d88fefc3-da04-493d-9533-294a7264b27f", + "path": "posts" + } + }, + "id": "6e1bc040-f382-402c-ac56-3328d5f424c7", + "properties": {} + } + } + }, + "settings": { + "contentType": "featuredPostSettings", + "id": "82b60071-38f6-49e8-896c-da7094895556", + "properties": { + "backgroundColor": "6fa8dc", + "showTags": false + } + } + } + ] + } + }, + "cultures": {} +} +``` +{% endcode %} + +Output with property expansion and limiting: + +**Request** + +```http +GET /umbraco/delivery/api/v2/content/item/article-with-rich-text/ + ?expand=properties[richText[properties[post[properties[coverImage]]]]] + &fields=properties[richText[properties[title,post[properties[excerpt,coverImage]],backgroundColor]]] +Start-Item: articles +``` + +**Response** + +{% code title="Delivery API output with expansion and limiting" %} +```json +{ + "contentType": "articleWithRichText", + "name": "Article with Rich Text", + "createDate": "2023-11-07T16:33:51.5326056", + "updateDate": "2023-11-07T16:34:11.0504555", + "route": { + "path": "/article-with-rich-text/", + "startItem": { + "id": "5c8823f6-6442-4d04-9c14-f3411bd41aa8", + "path": "articles" + } + }, + "id": "785a7731-4312-4ed6-b11c-2f82db85c930", + "properties": { + "richText": { + "markup": "

Proin mattis enim arcu, ac maximus magna auctor a. Proin sed porttitor nibh, eget venenatis felis. Pellentesque mattis feugiat ultrices. Duis libero velit, sagittis eu nunc euismod, lacinia aliquam elit.

\n\n

Nulla quis fringilla sem. Integer id lacus sit amet ante gravida ullamcorper ac in neque. Etiam at ipsum a augue laoreet commodo. Vestibulum bibendum viverra diam, vel dignissim nunc suscipit id. 

\n\n

 

", + "blocks": [ + { + "content": { + "contentType": "featuredPost", + "id": "a42d825e-383c-4699-ae7f-eb851c0a5b13", + "properties": { + "title": "Free your content with the Delivery API", + "post": { + "contentType": "post", + "name": "Set your content free", + "createDate": "2023-08-14T08:35:58.8397754", + "updateDate": "2023-08-14T08:54:41.8018872", + "route": { + "path": "/set-your-content-free/", + "startItem": { + "id": "d88fefc3-da04-493d-9533-294a7264b27f", + "path": "posts" + } + }, + "id": "5de65f2e-055e-4e6a-adc3-edb340dcd2e8", + "properties": { + "coverImage": [ + { + "focalPoint": null, + "crops": [], + "id": "dbb62958-de31-465f-bdbe-5e9079b54b25", + "name": "Arrows", + "mediaType": "Image", + "url": "/media/lyulnwzr/arrows.png", + "extension": "png", + "width": 1080, + "height": 530, + "bytes": 24069, + "properties": { + "altText": "Some arrows pointing in different directions (but generally upwards)" + } + } + ], + "excerpt": "Fusce ut mauris ornare, mollis felis ac, convallis neque. Aenean eu tortor ac dui dictum lacinia. Aliquam erat volutpat. Sed malesuada congue imperdiet. Sed dictum aliquam velit. Nunc non nibh dignissim, consequat quam ac, mattis turpis. " + } + } + } + }, + "settings": { + "contentType": "featuredPostSettings", + "id": "109e1f19-93b9-4f5a-9ab2-315aa618c42a", + "properties": { + "backgroundColor": "ce7e00" + } + } + }, + { + "content": { + "contentType": "featuredPost", + "id": "724ecae7-3118-46a8-8dc2-a9bf9e10efdb", + "properties": { + "title": "Community is key", + "post": { + "contentType": "post", + "name": "Building a community", + "createDate": "2023-08-14T08:35:58.6755792", + "updateDate": "2023-08-14T08:54:45.5526111", + "route": { + "path": "/building-a-community/", + "startItem": { + "id": "d88fefc3-da04-493d-9533-294a7264b27f", + "path": "posts" + } + }, + "id": "6e1bc040-f382-402c-ac56-3328d5f424c7", + "properties": { + "coverImage": [ + { + "focalPoint": null, + "crops": [], + "id": "9757a20c-3e98-49ef-8103-c7fa746856e7", + "name": "Community", + "mediaType": "Image", + "url": "/media/bkrfpf2y/community.png", + "extension": "png", + "width": 1080, + "height": 540, + "bytes": 25832, + "properties": { + "altText": "Two hands constructing a heart from coloured pieces" + } + } + ], + "excerpt": "Maecenas ipsum dui, lobortis non dui eleifend, dapibus bibendum dui. Pellentesque dolor felis, mollis nec diam eget, fermentum rutrum ipsum. Vestibulum condimentum urna id turpis tempus, id finibus sem facilisis. Nullam commodo felis quis ante posuere, dictum suscipit nisl suscipit." + } + } + } + }, + "settings": { + "contentType": "featuredPostSettings", + "id": "82b60071-38f6-49e8-896c-da7094895556", + "properties": { + "backgroundColor": "6fa8dc" + } + } + } + ] + } + }, + "cultures": {} +} +``` +{% endcode %} + +## Closing remarks + +Property expansion and limiting is a powerful feature that can boost our application performance. With this, we can prevent additional requests to obtain data for linked items, and we can tailor the output to specific use cases. + +However, it is also a complex feature. The query syntax quickly gets complicated, particularly when targeting block editors. You will likely need to experiment to get the query exactly right. Hopefully, the examples in this article will guide you in applying expansion and limiting to your own content. diff --git a/16/umbraco-cms/reference/content-delivery-api/protected-content-in-the-delivery-api/README.md b/16/umbraco-cms/reference/content-delivery-api/protected-content-in-the-delivery-api/README.md new file mode 100644 index 00000000000..f6862f78515 --- /dev/null +++ b/16/umbraco-cms/reference/content-delivery-api/protected-content-in-the-delivery-api/README.md @@ -0,0 +1,478 @@ +--- +description: >- + How to use member authorization with the Delivery API to access protected + content. +--- + +# Protected content in the Delivery API + +Umbraco allows for restricting access to content. Using the "Public access" feature, specific content items can be protected and made accessible only for authorized members. The same is possible in the Delivery API. +By default, protected content is ignored by the Delivery API, and is never exposed through any API endpoints. However, by enabling member authorization in the Delivery API, protected content can be accessed by means of access tokens. + +{% hint style="info" %} +If you are not familiar with members in Umbraco, please read the [Members](../../../fundamentals/data/members.md) article. +{% endhint %} + +{% hint style="info" %} +This article describes how to access protected content in a client-to-server context, using an interactive authorization flow. +If you are looking to achieve server-to-server access to protected content, please refer to [server-to-server access article](server-to-server-access.md) instead. +{% endhint %} + +## Member authorization + +Member authentication and authorization in the Delivery API is performed using the OpenId Connect flow _Authorization Code Flow + Proof Key of Code Exchange (PKCE)_. This is a complex authorization flow, and it is beyond the scope of this article to explain it. Many articles can be found online that explain the flow in detail. +Most programming languages have OpenId Connect client libraries to handle the complexity for us. [`AppAuth`](https://appauth.io/) is a great example of such a library. In ASP.NET Core, OpenId Connect support is built into the framework. + +### Enabling member authorization + +Member authorization is an opt-in feature of the Delivery API. To enable it, configure `MemberAuthorization:AuthorizationCodeFlow` in the `DeliveryApi` section of `appsettings.json`: + +- `Enabled` must be `true`. +- One or more `LoginRedirectUrls` must be configured. These specify where the server is allowed to redirect the client after a successful authorization. +- Optionally one or more `LogoutRedirectUrls` must be configured. These specify where the server is allowed to redirect the client after successfully terminating a session. + - These are only necessary if logout is implemented in the client. + +{% hint style="warning" %} +All redirect URLs must be absolute and contain the full path to the expected resource. It is not possible to use wildcards or to allow all paths under a given domain. +{% endhint %} + +{% code title="appsettings.json" %} + +```json +{ + "Umbraco": { + "CMS": { + "DeliveryApi": { + "Enabled": true, + "MemberAuthorization": { + "AuthorizationCodeFlow": { + "Enabled": true, + "LoginRedirectUrls": [ + "https://absolute.redirect.url/path/after/login" + ], + "LogoutRedirectUrls": [ + "https://absolute.redirect.url/path/after/logout" + ] + } + } + } + } + } +} +``` + +{% endcode %} + +{% hint style="info" %} +When changing the `MemberAuthorization` configuration, Umbraco must be restarted to pick up on the changes. +{% endhint %} + +{% hint style="warning" %} +When enabling or disabling member authentication, the `DeliveryApiContentIndex` must be rebuilt to correctly reflect the existing content protection state. +The index can be rebuilt from the [Examine Management dashboard](../../searching/examine/examine-management.md). +{% endhint %} + +## Server endpoints + +Many client libraries support automatic discovery of the server OpenId endpoints. This is also supported by the Delivery API, so likely we do not have to worry about the server endpoints. +If automatic discovery is not applicable, the server endpoints must be configured manually. The server endpoints can be found at `https://{server-host}/.well-known/openid-configuration`. + +{% hint style="info" %} +Keep in mind that the API versions can change over time, which might affect the configuration. +{% endhint %} + +## Client configuration + +To connect the client and the server, we need to apply some configuration details to the connection: + +- The `client_id` must be `umbraco-member`. +- The `response_type` must be `code`. +- The `redirect_uri` must be one of the configured `LoginRedirectUrls`. +- The `scope` must either be empty, or be `openid` and/or `offline_access`. +- _PKCE_ must be enabled. + +For inspiration, the [samples section](./#basic-client-configuration) at the end of this article shows how to configure an ASP.NET Core client. + +## Logging in members + +_Authorization Code Flow + Proof Key of Code Exchange (PKCE)_ requires the authentication service (identity provider) to be separate from the client application. This is to ensure that credentials are never exposed directly to the client application. +As an authentication service, we can use both Umbraco's built-in member authentication and external identity providers. By default the Delivery API attempts to use the built-in member authentication. + +### How to use the built-in member authentication + +First and foremost we need a login page. By ASP.NET Core defaults, this page should be located at `/Account/Login`. However, we can change the default path by adding the following piece of code: + +{% code title="ConfigureCustomMemberLoginPathExtensions.cs" %} + +```csharp +using Microsoft.AspNetCore.Authentication.Cookies; +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.Options; +namespace Umbraco.Samples; +public static class ConfigureCustomMemberLoginPathExtensions +{ + public static IUmbracoBuilder SetCustomMemberLoginPath(this IUmbracoBuilder builder) + { + builder.Services.ConfigureOptions(); + return builder; + } + private class ConfigureCustomMemberLoginPath : IConfigureNamedOptions + { + public void Configure(string? name, CookieAuthenticationOptions options) + { + if (name != IdentityConstants.ApplicationScheme) + { + return; + } + Configure(options); + } + // replace options.LoginPath with the path you want to use for default member logins + public void Configure(CookieAuthenticationOptions options) + => options.LoginPath = "/path/to/the-custom-login-page"; + } +} +``` + +{% endcode %} + +To invoke this code, we need to call `SetCustomMemberLoginPath()` in `Program.cs`: + +{% code title="Program.cs" %} + +```csharp +builder.CreateUmbracoBuilder() + .AddBackOffice() + .AddWebsite() + .AddDeliveryApi() + .AddComposers() + // add this line + .SetCustomMemberLoginPath() + .Build(); +``` + +{% endcode %} + +No matter the path to the login page, we still need a page to render the login screen. Create a content item located at the login page path, and use this template to render it: + +{% code title="Login.cshtml" %} + +```html +@inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage +@using Umbraco.Cms.Web.Common.Models +@using Umbraco.Cms.Web.Website.Controllers +@using Microsoft.AspNetCore.Mvc.TagHelpers +@{ + Layout = null; + var loginModel = new LoginModel + { + // important: we must set the redirect URL from the query string + RedirectUrl = Context.Request.Query["ReturnUrl"] + }; +} + + + Login page + + +@using (Html.BeginUmbracoForm("HandleLogin", new { RedirectUrl = loginModel.RedirectUrl })) +{ +

Log in with a local account.

+
+
+
+ + + +
+
+ + + +
+ +} + + +``` + +{% endcode %} + +With all this in place, it's time to test the setup. Use a browser to perform a request to `https://{server-host}/umbraco/delivery/api/v1/security/member/authorize` with these query string parameters: + +- `client_id=umbraco-member` +- `redirect_uri=https://absolute.redirect.url/path/after/login` (replace the value with one of the configured login redirect URLs) +- `response_type=code` +- `code_challenge=WZRHGrsBESr8wYFZ9sx0tPURuZgG2lmzyvWpwXPKz8U` +- `code_challenge_method=S256` + +If everything works as expected, the request will yield a redirect to the login page. Completing the login form will cause a redirect to the specified redirect URL with a `code` query string parameter. The `code` can subsequently be exchanged for an access token, which can be used to access protected content. + +{% hint style="info" %} +Do not worry about the URL construction and subsequent handling of the `code` parameter. This complexity is what the OpenId Connect client libraries handle for us. +{% endhint %} + +For more inspiration on using the built-in member authentication, check the [Members Registration and Login](../../../tutorials/members-registration-and-login.md) article. Here you will also learn how to create member sign-up functionality. + +### How to use external identity providers + +Umbraco allows adding external identity providers for both backoffice users and members. The process is documented in detail in the [External Login Providers](../../security/external-login-providers.md) article. + +The Delivery API supports the same functionality. In the following we'll be using GitHub to test this. + +First, we need to create an OAuth App in GitHub. This is done in the [GitHub Developer Settings](https://github.com/settings/developers). Use `https://{server-host}/umbraco/signin-github` as authorization callback URL in the App. + +Once the App is created, generate a new client secret within the App. Make sure to copy both the client ID of your App and the generated secret. + +Now we need to connect Umbraco members with the App: + +1. Add the NuGet package `AspNet.Security.OAuth.GitHub` to your Umbraco project. +2. Add the code below to configure the connection to the App. Remember to update the OAuth client ID and secret. + +{% code title="GitHubAuthenticationExtensions.cs" %} + +```csharp +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core; +using Umbraco.Cms.Web.Common.Security; +namespace Umbraco.Samples; +public static class GitHubAuthenticationExtensions +{ + private const string Scheme = "GitHub"; + public static IUmbracoBuilder AddGitHubAuthentication(this IUmbracoBuilder builder) + { + builder.Services.ConfigureOptions(); + builder.AddMemberExternalLogins(logins => + { + logins.AddMemberLogin( + membersAuthenticationBuilder => + { + membersAuthenticationBuilder.AddGitHub( + membersAuthenticationBuilder.SchemeForMembers(Scheme), + options => + { + // add your client ID and secret here + options.ClientId = "{OAuth App client ID}"; + options.ClientSecret = "{OAuth App client secret}"; + options.CallbackPath = "/umbraco/signin-github"; + options.Scope.Add("user:email"); + options.SaveTokens = true; + }); + }); + }); + return builder; + } + private class GitHubMemberExternalLoginProviderOptions : IConfigureNamedOptions + { + public void Configure(string? name, MemberExternalLoginProviderOptions options) + { + if (name is not $"{Constants.Security.MemberExternalAuthenticationTypePrefix}{Scheme}") + { + return; + } + Configure(options); + } + public void Configure(MemberExternalLoginProviderOptions options) + => options.AutoLinkOptions = new MemberExternalSignInAutoLinkOptions( + autoLinkExternalAccount: true, + defaultCulture: null, + defaultIsApproved: true, + defaultMemberTypeAlias: Constants.Security.DefaultMemberTypeAlias); + } +} +``` + +{% endcode %} + +Finally, we need to invoke the connection configuration by calling `AddGitHubAuthentication()` in `Program.cs`. + +{% code title="Program.cs" %} + +```csharp +builder.CreateUmbracoBuilder() + .AddBackOffice() + .AddWebsite() + .AddDeliveryApi() + .AddComposers() + // add this line + .AddGitHubAuthentication() + .Build(); +``` + +{% endcode %} + +{% hint style="info" %} +There are multiple ways of registering extensions and dependencies like these in your Umbraco project. Which method to use depends on your implementation and preferred way of working. +Learn more about this in the [Dependency Injection](../../using-ioc.md) article. +{% endhint %} + +Now we can test the setup. We'll be calling `https://{server-host}/umbraco/delivery/api/v1/security/member/authorize` as described previously, but we need to add one more query string parameter: + +- `identity_provider=UmbracoMembers.GitHub` + +If the setup is correct, the request will yield a redirect to the GitHub login page. Here we need to authorize the GitHub OAuth App we created earlier, in order to complete the login. Upon completion, a series of redirects will once more take us to the specified redirect URL with a `code` query string parameter. + +Different client libraries have different ways of declaring the `identity_provider` in the authorization request. The [samples section](./#using-a-named-identity-provider) shows how to configure this in an ASP.NET Core client. + +### Combining built-in member authentication and external identity providers + +We can also add the external identity providers to the member authentication login screen. This way the end user can decide whether to log in as a registered member, or use an external identity provider. + +The [Login partial view](https://github.com/umbraco/Umbraco-CMS/blob/contrib/src/Umbraco.Core/EmbeddedResources/Snippets/Login.cshtml) features an implementation of this combined login experience. + +## Accessing protected content + +When the authorization flow completes we'll obtain an access token. This token can be used as a bearer token to access protected content for the logged-in member: + +```http +GET /umbraco/delivery/api/v2/content/{query} +Authentication: Bearer {access token} +``` + +Access tokens expire after one hour. Once expired, a new access token must be obtained to continue accessing protected content. + +## Refresh tokens + +Refresh tokens provide a means to obtain a new access token without having to go through the authentication flow. A refresh token is issued automatically by the Delivery API when the `offline_access` scope is specified in the authorization request. + +{% hint style="info" %} +Refresh tokens are subject to certain limitations and can result in security issues if not applied correctly. All this is beyond the scope of this article to explain in detail. Please familiarize yourself with the inner workings of refresh tokens before applying them in a solution. +{% endhint %} + +## Logging out members + +The member authorization is tied to the access and refresh tokens obtained in the authorization flow. Discarding these tokens efficiently terminates the access to protected content. + +However, the tokens are still valid and can be reapplied until they expire. Depending on your scenario, it might be prudent to revoke the tokens and maybe even terminate the session on the server. + +### Revoking tokens + +Access and refresh tokens can be revoked by performing a `POST` request containing the token: + +```http +POST /umbraco/delivery/api/v1/security/member/revoke + client_id=umbraco-member + token={token to revoke} +Content-Type: application/x-www-form-urlencoded +``` + +### Terminating a session + +When terminating a session on the server, the member is logged out of Umbraco. This means any subsequent authorization attempt will require an explicit login. + +To terminate the active session for any given member, you must redirect the browser to the signout endpoint. The request must contain one of the white-listed `LogoutRedirectUrls` from the `appsettings.json`: + +```http +GET /umbraco/delivery/api/v1/security/member/signout?post_logout_redirect_uri={valid URL from LogoutRedirectUrls} +``` + +### User info + +The "user info" endpoint is part of the [OpenId Connect core spec](https://openid.net/specs/openid-connect-core-1_0.html#UserInfo). + +This implementation returns a few of the [standard claims](https://openid.net/specs/openid-connect-basic-1_0.html#StandardClaims), all of which are subject of availability: + +- `sub` (required claim) +- `name` (if available) +- `email` (if available) + +On top of this, the member groups (if any) are returned in the role claim. + +The implementation is build to be extendable, so custom claims can be added to these claims - and the core claims can be removed, too. + +```http +GET /umbraco/delivery/api/v1/security/member/userinfo +``` + +## Testing with Swagger + +The Delivery API Swagger document can be configured to support member authentication. + +Before we can do that, we need two things in place: + +1. We have to implement a login page [as described above](#logging-in-members). +2. We must add `https://{server-host}/umbraco/swagger/oauth2-redirect.html` to the configured `LoginRedirectUrls`. + +With these in place, we can enable member authentication in Swagger for the Delivery API by adding the following to `Program.cs`: + +{% code title="Program.cs" %} + +```csharp +builder.CreateUmbracoBuilder() + .AddBackOffice() + .AddWebsite() + .AddDeliveryApi() + .AddComposers() + .Build(); +builder.Services.ConfigureOptions(); +``` + +{% endcode %} + +The Swagger UI will now feature authorization. + +{% hint style="info" %} +Remember to use `umbraco-member` as `client_id` when authorizing. `client_secret` can be omitted, as it is not used by the authorization flow. +{% endhint %} + +## Client configuration samples + +The following samples show how to configure an ASP.NET Core client to utilize member authorization in the Delivery API. +To put these samples into context, please refer to the article above. + +### Basic client configuration + +{% code title="Program.cs" %} + +```csharp +builder.Services.AddAuthentication(options => + { + options.DefaultAuthenticateScheme = "cookie"; + options.DefaultSignInScheme = "cookie"; + options.DefaultChallengeScheme = "oidc"; + }) + .AddCookie("cookie") + .AddOpenIdConnect("oidc", options => + { + // the Umbraco site is the "authority" (also referred to as "issuer") + options.Authority = "https://{server-host}"; + // set the "client_id" parameter + options.ClientId = "umbraco-member"; + // set the "response_type" parameter + options.ResponseType = "code"; + // set the "redirect_uri" parameter (will be converted to an absolute URL by the framework) + options.CallbackPath = "/signin-oidc"; + // set the "scope" parameter (remove the default scopes and only use the "openid" scope) + options.Scope.Clear(); + options.Scope.Add("openid"); + // enable PKCE + options.UsePkce = true; + // use a form POST as response mode + options.ResponseMode = "form_post"; + }); +``` + +{% endcode %} + +### Using a named identity provider + +{% code title="Program.cs" %} + +```csharp +builder.Services.AddAuthentication(...) + .AddOpenIdConnect("oidc", options => + { + // ... + // set the "identity_provider" parameter + options.Events.OnRedirectToIdentityProvider = context => + { + context.ProtocolMessage.IdentityProvider = "UmbracoMembers.GitHub"; + return Task.CompletedTask; + }; + }); +``` + +{% endcode %} + +## Limitations + +When using external identity providers, Umbraco still allows for performing local two-factor authentication for members. This feature is not available in the Delivery API. Instead, two-factor authentication should be performed at the identity provider. diff --git a/16/umbraco-cms/reference/content-delivery-api/protected-content-in-the-delivery-api/images/api-member.png b/16/umbraco-cms/reference/content-delivery-api/protected-content-in-the-delivery-api/images/api-member.png new file mode 100644 index 00000000000..62925be5ec6 Binary files /dev/null and b/16/umbraco-cms/reference/content-delivery-api/protected-content-in-the-delivery-api/images/api-member.png differ diff --git a/16/umbraco-cms/reference/content-delivery-api/protected-content-in-the-delivery-api/server-to-server-access.md b/16/umbraco-cms/reference/content-delivery-api/protected-content-in-the-delivery-api/server-to-server-access.md new file mode 100644 index 00000000000..8c042c41ded --- /dev/null +++ b/16/umbraco-cms/reference/content-delivery-api/protected-content-in-the-delivery-api/server-to-server-access.md @@ -0,0 +1,197 @@ +--- +description: How to fetch protected content from the Delivery API with a server-to-server approach. +--- + +# Server-to-server access to protected content in the Delivery API + +If protected content is consumed from the Delivery API in a server-to-server context, the [interactive authorization flow](README.md) won't work. Instead, we have to utilize the OpenId Connect Client Credentials flow, which is configured in the application settings. + +## Configuration + +In the Delivery API, Client Credentials map known Members to client IDs and secrets. These Members are known as API Members. When an API consumer uses the Client Credentials of an API Member, the consumer efficiently assumes the identity of this API Member. + +{% hint style="info" %} +An API Member works the same as a regular Member, with the added option of authorizing with Client Credentials. +{% endhint %} + +In the following configuration example, the Member "member@local" is mapped to a set of Client Credentials: + +{% code title="appsettings.json" %} + +```json +{ + "Umbraco": { + "CMS": { + "DeliveryApi": { + "Enabled": true, + "MemberAuthorization": { + "ClientCredentialsFlow": { + "Enabled": true, + "AssociatedMembers": [ + { + "ClientId": "my-client", + "ClientSecret": "my-client-secret", + "UserName": "member@local" + } + ] + } + } + } + } + } +} +``` + +{% endcode %} + +After restarting the site, the backoffice will list "member@local" as an API Member: + +![An API Member in the backoffice](images/api-member.png) + +## Authorizing and consuming the Delivery API + +The configured Client Credentials can be exchanged for an access token using the Delivery API token endpoint. Subsequently, the access token can be used as a Bearer token to retrieve protected content from the Delivery API. + +The following code sample illustrates how this can be done. + +{% hint style="info" %} +This sample requires the NuGet packages [`Microsoft.Extensions.Hosting`](https://www.nuget.org/packages/Microsoft.Extensions.Hosting) and [`IdentityModel`](https://www.nuget.org/packages/IdentityModel) to run. +{% endhint %} + +You should _always_ reuse access tokens for the duration of their lifetime. This will increase performance both for your Delivery API consumer and for the Delivery API itself. + +{% hint style="info" %} +The code sample handles token reuse in the `ApiAccessTokenService` service. It must be registered as a singleton service to work. +{% endhint %} + +In the code sample, the token endpoint is hardcoded in the token exchange request. The Delivery API also supports OpenId Connect Discovery for API Members, if you prefer that. + +{% code title="Program.cs" lineNumbers="true" %} +```csharp +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using System.Net.Http.Json; +using IdentityModel.Client; + +var builder = Host.CreateApplicationBuilder(args); +builder.Services.AddSingleton(); +builder.Services.AddTransient(); + +using IHost host = builder.Build(); +var consumer = host.Services.GetRequiredService(); +await consumer.ExecuteAsync(); + +public static class Constants +{ + // the base URL of the Umbraco site - change this to fit your custom setup + public static string Host => "https://localhost:44391"; +} + +// This is the API consumer, which will be listing the first few available content items - including protected ones. +public class ApiConsumerService +{ + private readonly ApiAccessTokenService _apiAccessTokenService; + + public ApiConsumerService(ApiAccessTokenService apiAccessTokenService) + => _apiAccessTokenService = apiAccessTokenService; + + public async Task ExecuteAsync() + { + // get an access token from the access token service. + var accessToken = _apiAccessTokenService.GetAccessToken(); + if (accessToken is null) + { + Console.WriteLine("Could not get an access token, aborting."); + return; + } + + var client = new HttpClient(); + client.SetBearerToken(accessToken); + + // fetch [pageSize] content items from the "all content" Delivery API endpoint. + const int pageSize = 5; + var apiResponse = await client.GetAsync($"{Constants.Host}/umbraco/delivery/api/v2/content?take={pageSize}"); + var apiContentResponse = await apiResponse + .EnsureSuccessStatusCode() + .Content + .ReadFromJsonAsync(); + + if (apiContentResponse is null) + { + Console.WriteLine("Could not parse content from the API response."); + return; + } + + Console.WriteLine($"There are {apiContentResponse.Total} items in total - listing the first {pageSize} items."); + foreach (var item in apiContentResponse.Items) + { + Console.WriteLine($"- {item.Name} ({item.Id})"); + } + } +} + +// This service ensures the reuse of access tokens for the duration of their lifetime. +// It must be registered as a singleton service to work properly. +public class ApiAccessTokenService +{ + private readonly Lock _lock = new(); + + private string? _accessToken; + private DateTime _accessTokenExpiry = DateTime.MinValue; + + public string? GetAccessToken() + { + if (_accessTokenExpiry > DateTime.UtcNow) + { + // we already have a token, reuse it. + return _accessToken; + } + + using (_lock.EnterScope()) + { + if (_accessTokenExpiry > DateTime.UtcNow) + { + // another thread fetched a new token before this thread entered the lock, reuse it. + return _accessToken; + } + + var client = new HttpClient(); + var tokenResponse = client.RequestClientCredentialsTokenAsync( + new ClientCredentialsTokenRequest + { + Address = $"{Constants.Host}/umbraco/delivery/api/v1/security/member/token", + ClientId = "umbraco-member-my-client", + ClientSecret = "my-client-secret" + } + ) + // cannot await inside a using. + .GetAwaiter().GetResult(); + + if (tokenResponse.IsError || tokenResponse.AccessToken is null) + { + Console.WriteLine($"Error obtaining a token: {tokenResponse.ErrorDescription}"); + return null; + } + + _accessToken = tokenResponse.AccessToken; + _accessTokenExpiry = DateTime.UtcNow.AddSeconds(tokenResponse.ExpiresIn - 20); + return tokenResponse.AccessToken; + } + } +} + +public class ApiContentResponse +{ + public required int Total { get; set; } + + public required ApiContentItemResponse[] Items { get; set; } +} + +public class ApiContentItemResponse +{ + public required Guid Id { get; set; } + + public required string Name { get; set; } +} +``` +{% endcode %} diff --git a/16/umbraco-cms/reference/content-type-filters.md b/16/umbraco-cms/reference/content-type-filters.md new file mode 100644 index 00000000000..cbfa5fc4dfb --- /dev/null +++ b/16/umbraco-cms/reference/content-type-filters.md @@ -0,0 +1,78 @@ +--- +description: Describes how to use Content Type Filters to restrict the allowed content options available to editors. +--- + +# Filtering Allowed Content Types + +When content editors add new content they are presented with a dialog where they must select the type of content they want to create. The options available are defined when setting up the Document, Media, and Member types in the **Settings** section. + +Implementors and package creators can add additional logic to determine which options are available to the editors. + +This is possible using Content Type Filters. + +{% hint style="info" %} +The use cases supported here are similar to those where the `SendingAllowedChildrenNotification` would be used in Umbraco 13 or earlier. +{% endhint %} + +## Implementing a Content Type Filter + +To create a Content Type Filter you use a class that implements the `IContentTypeFilter` interface (found in the `Umbraco.Cms.Core.Services.Filters` namespace). + +There are two methods you can implement: + +* One for filtering the content types allowed at the content root +* One for the content types allowed below a given parent node. + +If you don't want to filter using one of the two approaches, you can return the provided collection unmodified. + +### Example Use Case + +The following example shows a typical use case. Often websites will have a "Home Page" Document Type which is created at the root. Normally, only one of these is required. You can enforce that using the following Content Type Filter. + +The code below is querying the existing content available at the root. Normally you can create a "Home Page" here, but if one already exists that option is removed: + +```csharp +internal class OneHomePageOnlyContentTypeFilter : IContentTypeFilter +{ + private readonly IContentService _contentService; + + public OneHomePageOnlyContentTypeFilter(IContentService contentService) => _contentService = contentService; + + public Task> FilterAllowedAtRootAsync(IEnumerable contentTypes) + where TItem : IContentTypeComposition + { + var docTypeAliasesToExclude = new List(); + + const string HomePageDocTypeAlias = "homePage"; + var docTypeAliasesAtRoot = _contentService.GetRootContent() + .Select(x => x.ContentType.Alias) + .Distinct() + .ToList(); + if (docTypeAliasesAtRoot.Contains(HomePageDocTypeAlias)) + { + docTypeAliasesToExclude.Add(HomePageDocTypeAlias); + } + + return Task.FromResult(contentTypes + .Where(x => docTypeAliasesToExclude.Contains(x.Alias) is false)); + } + + public Task> FilterAllowedChildrenAsync(IEnumerable contentTypes, Guid parentKey) + => Task.FromResult(contentTypes); +} +``` + +Content Type Filters are registered as a collection, making it possible to have more than one in the solution or an installed package. + +The filters need to be registered in a composer: + +```csharp +public class MyComposer : IComposer +{ + public void Compose(IUmbracoBuilder builder) + { + builder.ContentTypeFilters() + .Append(); + } +} +``` diff --git a/16/umbraco-cms/reference/custom-swagger-api.md b/16/umbraco-cms/reference/custom-swagger-api.md new file mode 100644 index 00000000000..b7e7747b8d8 --- /dev/null +++ b/16/umbraco-cms/reference/custom-swagger-api.md @@ -0,0 +1,202 @@ +--- +description: Example of a Custom API with Authorization and Swagger +--- + +# Custom API with Authorization and Swagger + +This article covers how to create a Custom API controller protected by the backoffice authorization policies. It also shows how to enable the authorization UI in Swagger docs. + + +{% hint style="info" %} + +Before proceeding, make sure to read the [Management API](./management-api/README.md) article. It provides information about the Swagger documentation and Authorization used in this article. + +{% endhint %} + +This example can be a starting point for creating a secure custom API with automatic Swagger documentation. You can find other examples in the [API versioning and OpenAPI](./api-versioning-and-openapi.md) article. + +1. Create a new `.cs` file called `MyBackOfficeSecurityRequirementsOperationFilter` in your Umbraco project. + +2. Add the following code so that the new API shows in the Swagger documentation and Swagger UI: + +{% code title="MyBackOfficeSecurityRequirementsOperationFilter.cs" lineNumbers="true" %} + +```csharp + +using Microsoft.Extensions.Options; +using Microsoft.OpenApi.Models; +using Swashbuckle.AspNetCore.SwaggerGen; +using Umbraco.Cms.Api.Management.OpenApi; +using Umbraco.Cms.Core.Composing; + +namespace Umbraco.Cms.Web.UI.New.Custom; + +//Necessary code for the new API to show in the Swagger documentation and Swagger UI +public class MyBackOfficeSecurityRequirementsOperationFilter : BackOfficeSecurityRequirementsOperationFilterBase +{ + protected override string ApiName => "my-api-v1"; +} + +public class MyConfigureSwaggerGenOptions : IConfigureOptions +{ + public void Configure(SwaggerGenOptions options) + { + options.SwaggerDoc("my-api-v1", new OpenApiInfo { Title = "My API v1", Version = "1.0" }); + options.OperationFilter(); + } +} + +public class MyComposer : IComposer +{ + public void Compose(IUmbracoBuilder builder) + => builder.Services.ConfigureOptions(); +} + +``` + +{% endcode %} + +- Our filter inherits from `BackOfficeSecurityRequirementsOperationFilterBase`. This marks our API as supporting authorization via Swagger. +- `MyConfigureSwaggerGenOptions` configures our API swagger docs with our filter applied. +- `MyComposer ` makes sure the swagger generator knows about our API docs configuration at runtime. + +3. Add the ApiController to setup the logic behind the endpoint: + +{% code title="MyBackOfficeSecurityRequirementsOperationFilter.cs" lineNumbers="true" %} + +```csharp + +using Umbraco.Cms.Api.Common.Attributes; +using Umbraco.Cms.Api.Common.Filters; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Models.Membership; +using Umbraco.Cms.Core.Security; +using Umbraco.Cms.Web.Common.Authorization; +using Asp.Versioning; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +... + +//Creating the Controller +[ApiController] +[ApiVersion("1.0")] +[MapToApi("my-api-v1")] +[Authorize(Policy = AuthorizationPolicies.BackOfficeAccess)] +[JsonOptionsName(Constants.JsonOptionsNames.BackOffice)] +[Route("api/v{version:apiVersion}/my")] +public class MyApiController : Controller +{ + private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor; + + public MyApiController(IBackOfficeSecurityAccessor backOfficeSecurityAccessor) + => _backOfficeSecurityAccessor = backOfficeSecurityAccessor; + + [HttpGet("say-hello")] + [MapToApiVersion("1.0")] + [ProducesResponseType(typeof(string), StatusCodes.Status200OK)] + public IActionResult SayHello() + { + IUser currentUser = _backOfficeSecurityAccessor.BackOfficeSecurity?.CurrentUser + ?? throw new InvalidOperationException("No backoffice user found"); + return Ok($"Hello, {currentUser.Name}"); + } +} + +``` + +{% endcode %} + +
+ +See the entire file: MyBackOfficeSecurityRequirementsOperationFilter.cs + +{% code title="MyBackOfficeSecurityRequirementsOperationFilter.cs" lineNumbers="true" %} + +```csharp +using Asp.Versioning; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Options; +using Microsoft.OpenApi.Models; +using Swashbuckle.AspNetCore.SwaggerGen; +using Umbraco.Cms.Api.Common.Attributes; +using Umbraco.Cms.Api.Common.Filters; +using Umbraco.Cms.Api.Management.OpenApi; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Core.Models.Membership; +using Umbraco.Cms.Core.Security; +using Umbraco.Cms.Web.Common.Authorization; + +namespace Umbraco.Cms.Web.UI.New.Custom; + +//Necessary code for the new API to show in the Swagger documentation and Swagger UI +public class MyBackOfficeSecurityRequirementsOperationFilter : BackOfficeSecurityRequirementsOperationFilterBase +{ + protected override string ApiName => "my-api-v1"; +} + +public class MyConfigureSwaggerGenOptions : IConfigureOptions +{ + public void Configure(SwaggerGenOptions options) + { + options.SwaggerDoc("my-api-v1", new OpenApiInfo { Title = "My API v1", Version = "1.0" }); + options.OperationFilter(); + } +} + +public class MyComposer : IComposer +{ + public void Compose(IUmbracoBuilder builder) + => builder.Services.ConfigureOptions(); +} + +//Creating the Controller +[ApiController] +[ApiVersion("1.0")] +[MapToApi("my-api-v1")] +[Authorize(Policy = AuthorizationPolicies.BackOfficeAccess)] +[JsonOptionsName(Constants.JsonOptionsNames.BackOffice)] +[Route("api/v{version:apiVersion}/my")] +public class MyApiController : Controller +{ + private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor; + + public MyApiController(IBackOfficeSecurityAccessor backOfficeSecurityAccessor) + => _backOfficeSecurityAccessor = backOfficeSecurityAccessor; + + [HttpGet("say-hello")] + [MapToApiVersion("1.0")] + [ProducesResponseType(typeof(string), StatusCodes.Status200OK)] + public IActionResult SayHello() + { + IUser currentUser = _backOfficeSecurityAccessor.BackOfficeSecurity?.CurrentUser + ?? throw new InvalidOperationException("No backoffice user found"); + return Ok($"Hello, {currentUser.Name}"); + } +} +``` + +{% endcode %} + +
+ +4. Run the project and navigate to `{yourdomain}/umbraco/swagger`. +5. Choose the swagger documentation we created with the code above named **My API v1** from **Select a definition**. + +![Created Custom API in Swagger Documentation](./images/custom-api-swagger-example.png) + +Here, we can find the endpoint that we created: + +```http +GET /api/v1/my/say-hello +``` + +6. Click on the **Authorize** button to authenticate. + +7. Try out the endpoint using the **Try it out** button. +8. Click on **Execute**. + +![Trying out the endpoint](./images/custom-api-swagger-example-response.png) + +We now get the response we have setup using the code: `"Hello, {{userName}}"`. diff --git a/16/umbraco-cms/reference/debugging.md b/16/umbraco-cms/reference/debugging.md new file mode 100644 index 00000000000..a6af593be03 --- /dev/null +++ b/16/umbraco-cms/reference/debugging.md @@ -0,0 +1,79 @@ +--- +description: >- + Information on SourceLink and how to use it to debug the Umbraco CMS source + code +--- + +# Debugging with SourceLink + +Microsoft and Visual Studio have introduced a new debugging technology called 'SourceLink' that enables source code debugging of certain .NET assemblies from NuGet. This feature has been enabled to allow developers to step into the native Umbraco CMS source code. + +## Enabling SourceLink in Visual Studio 2017 & 2019 + +1. Navigate to **Tools** -> **Options** -> **Debugging** -> **General**. +2. In the **General** window, uncheck `Enable Just My Code` option and check `Enable Source Link support` option. +3. Click **OK** to save the changes. + + ![Visual Studio 2019 Debug Settings for SourceLink](../../../10/umbraco-cms/reference/images/VS19-enable-sourcelink.png) + +## What is SourceLink? + +To read about SourceLink, you can take a look at the following websites: + +* [Microsoft Docs](https://docs.microsoft.com/en-us/dotnet/standard/library-guidance/sourcelink) +* [Scott Hanselman blog post on Sourcelink](https://www.hanselman.com/blog/ExploringNETCoresSourceLinkSteppingIntoTheSourceCodeOfNuGetPackagesYouDontOwn.aspx) +* [SourceLink on GitHub](https://github.com/dotnet/sourcelink) + +## Working with SourceLink + +* Create a new `.NET 5.0` Framework blank/empty website. +* Install the latest Umbraco CMS 9.0+ Nuget Packages from Nuget.org +* Create an IComposer or similar code in your new site/SLN that you want to F11/Step Into. [Example Code Snippet to try with SourceLink](debugging.md#example-code-snippet-to-try-with-sourcelink) +* Prompt will appear and the original source code file is fetched directly from GitHub. ![Visual Studio 2019 SourceLink dialog](../../../10/umbraco-cms/reference/images/VS19-sourcelink-prompt.png) +* How far can you `F11`, also known as `Step Into`, and go down the rabbit hole of the Umbraco CMS source code? + +### Example Code Snippet to try with SourceLink + +```csharp +using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Core.DependencyInjection; +using Umbraco.Cms.Core.Services; +using Umbraco.Extensions; + +namespace WebApplication23; + +public class MyComposer : IComposer +{ + public void Compose(IUmbracoBuilder builder) + { + builder.Components().Append(); + } +} + +public class MyComponent : IComponent +{ + private IContentService _contentService; + + public MyComponent(IContentService contentService) + { + _contentService = contentService; + } + + public void Initialize() + { + // Add break point & F11 into me + var root = _contentService.GetRootContent(); + + foreach (var item in root) + { + // Add break point & F11 into me + var udi = item.GetUdi(); + var foo = 5; + } + } + + public void Terminate() + { + } +} +``` diff --git a/16/umbraco-cms/reference/distributed-locks.md b/16/umbraco-cms/reference/distributed-locks.md new file mode 100644 index 00000000000..5ba2e898cbf --- /dev/null +++ b/16/umbraco-cms/reference/distributed-locks.md @@ -0,0 +1,31 @@ +# Distributed Locks + +During save operations, Umbraco will generally take a database lock to avoid concurrency issues. + +Access to this feature is via the `IScope` interface, for example: + +```csharp + using (var scope = _scopeProvider.CreateScope()) +{ + scope.WriteLock(Constants.Locks.Domains); + + // Carry out save operation. + + scope.Complete(); +} +``` + +Each lockable entity is represented by an integer Id, stored along with the state of the lock in the `umbracoLock` database table. + +Packages or custom solutions working with custom data via the `IScope` interface can introduce their own records to this table. However it's important to not clash with either core identifiers or those introduced by other packages. + +A reference is maintained here of known identifiers: + +| Id | Name | Used By | +|--------------|----------------------|------------------| +| -1000 | MainDom | Umbraco CMS | +| -331 to -340 | Various | Umbraco CMS | +| -800 | DeployTransferQueue | Umbraco Deploy | + + + diff --git a/16/umbraco-cms/reference/dive-into-the-code.md b/16/umbraco-cms/reference/dive-into-the-code.md new file mode 100644 index 00000000000..74476325093 --- /dev/null +++ b/16/umbraco-cms/reference/dive-into-the-code.md @@ -0,0 +1,39 @@ +--- +description: >- + Learn more about what you can find in this section, which is referred to as + the "Developers Reference". +--- + +# Dive into the code + +The developers' Reference section primarily consists of API references of the different core Umbraco APIs. In many cases, the references come with code snippets with examples. + +The section also includes documentation about the different configuration options, security, advanced templating, and searching. + +
ConfigurationMost configuration in Umbraco CMS is handled via the appSettings.json file.configurationDocumentations Icons_Umbraco_CMS_Reference_Configuration.png
TemplatingWork with MVC views and Razor templates to build the frontend for your Umbraco CMS website.templatingDocumentations Icons_Umbraco_CMS_Reference_Templating.png
Querying & ModelsEverything you need to extend your templates with filters, models, and more advanced queries.queryingDocumentations Icons_Umbraco_CMS_Reference_Querying_and_Models.png
Routing and ControllersDive into working with routers and controllers for customizing your Umbraco CMS even further.routingDocumentations Icons_Umbraco_CMS_Reference_Routing_and_Controllers.png
SecurityFind everything you need related to keeping your Umbraco CMS website secure.securityDocumentations Icons_Umbraco_CMS_Reference_Security.png
SearchingLearn about Examine and Lucene which are tools utilized in Umbraco for the search functionality.searchingDocumentations Icons_Umbraco_CMS_Reference_Searching (1).png
NotificationsLearn how to use notifications to hook into the workflow processes for the backoffice.notification-handler.mdDocumentations Icons_Umbraco_CMS_Reference_Notifications.png
CachingLearn how to implement caching features in the Umbraco CMS application.cacheDocumentations Icons_Umbraco_CMS_Reference_Caching.png
API DocumentationDive into the deep end and start exploring the different sections of the Umbraco CMS API.api-documentation.mdDocumentations Icons_Umbraco_CMS_Reference_API_Documentation.png
+ +## Also in this section + +{% content-ref url="using-ioc.md" %} +[using-ioc.md](using-ioc.md) +{% endcontent-ref %} + +{% content-ref url="response-caching.md" %} +[response-caching.md](response-caching.md) +{% endcontent-ref %} + +{% content-ref url="plugins/" %} +[plugins](plugins/) +{% endcontent-ref %} + +{% content-ref url="common-pitfalls.md" %} +[common-pitfalls.md](common-pitfalls.md) +{% endcontent-ref %} + +{% content-ref url="debugging.md" %} +[debugging.md](debugging.md) +{% endcontent-ref %} + +{% content-ref url="language-variation.md" %} +[language-variation.md](language-variation.md) +{% endcontent-ref %} diff --git a/16/umbraco-cms/reference/images/VS19-enable-sourcelink.png b/16/umbraco-cms/reference/images/VS19-enable-sourcelink.png new file mode 100644 index 00000000000..9fb55917f99 Binary files /dev/null and b/16/umbraco-cms/reference/images/VS19-enable-sourcelink.png differ diff --git a/16/umbraco-cms/reference/images/VS19-sourcelink-prompt.png b/16/umbraco-cms/reference/images/VS19-sourcelink-prompt.png new file mode 100644 index 00000000000..724dfacb8fe Binary files /dev/null and b/16/umbraco-cms/reference/images/VS19-sourcelink-prompt.png differ diff --git a/16/umbraco-cms/reference/images/custom-api-swagger-example-response.png b/16/umbraco-cms/reference/images/custom-api-swagger-example-response.png new file mode 100644 index 00000000000..76664f2786f Binary files /dev/null and b/16/umbraco-cms/reference/images/custom-api-swagger-example-response.png differ diff --git a/16/umbraco-cms/reference/images/custom-api-swagger-example.png b/16/umbraco-cms/reference/images/custom-api-swagger-example.png new file mode 100644 index 00000000000..466ab1bb8c4 Binary files /dev/null and b/16/umbraco-cms/reference/images/custom-api-swagger-example.png differ diff --git a/16/umbraco-cms/reference/images/custom-middleware-cors-browser-example.png b/16/umbraco-cms/reference/images/custom-middleware-cors-browser-example.png new file mode 100644 index 00000000000..00a3e277451 Binary files /dev/null and b/16/umbraco-cms/reference/images/custom-middleware-cors-browser-example.png differ diff --git a/16/umbraco-cms/reference/images/management-api-execute-response.png b/16/umbraco-cms/reference/images/management-api-execute-response.png new file mode 100644 index 00000000000..fa8ae962a61 Binary files /dev/null and b/16/umbraco-cms/reference/images/management-api-execute-response.png differ diff --git a/16/umbraco-cms/reference/images/management-api-swagger-authenticated.png b/16/umbraco-cms/reference/images/management-api-swagger-authenticated.png new file mode 100644 index 00000000000..63ce196e450 Binary files /dev/null and b/16/umbraco-cms/reference/images/management-api-swagger-authenticated.png differ diff --git a/16/umbraco-cms/reference/images/management-api-swagger-authorize-button.png b/16/umbraco-cms/reference/images/management-api-swagger-authorize-button.png new file mode 100644 index 00000000000..a4486a55b2d Binary files /dev/null and b/16/umbraco-cms/reference/images/management-api-swagger-authorize-button.png differ diff --git a/16/umbraco-cms/reference/images/management-api-swagger-authorize-instructions.png b/16/umbraco-cms/reference/images/management-api-swagger-authorize-instructions.png new file mode 100644 index 00000000000..2912c91fc61 Binary files /dev/null and b/16/umbraco-cms/reference/images/management-api-swagger-authorize-instructions.png differ diff --git a/16/umbraco-cms/reference/images/management-api-swagger.png b/16/umbraco-cms/reference/images/management-api-swagger.png new file mode 100644 index 00000000000..20310b09421 Binary files /dev/null and b/16/umbraco-cms/reference/images/management-api-swagger.png differ diff --git a/16/umbraco-cms/reference/images/management-api-try-it-out.png b/16/umbraco-cms/reference/images/management-api-try-it-out.png new file mode 100644 index 00000000000..8a0ab27c219 Binary files /dev/null and b/16/umbraco-cms/reference/images/management-api-try-it-out.png differ diff --git a/16/umbraco-cms/reference/images/postman-setup-swagger-cog-wheel.png b/16/umbraco-cms/reference/images/postman-setup-swagger-cog-wheel.png new file mode 100644 index 00000000000..e15b2156bf8 Binary files /dev/null and b/16/umbraco-cms/reference/images/postman-setup-swagger-cog-wheel.png differ diff --git a/16/umbraco-cms/reference/images/postman-setup-swagger-import.png b/16/umbraco-cms/reference/images/postman-setup-swagger-import.png new file mode 100644 index 00000000000..376d417974b Binary files /dev/null and b/16/umbraco-cms/reference/images/postman-setup-swagger-import.png differ diff --git a/16/umbraco-cms/reference/images/postman-setup-swagger-json-file.png b/16/umbraco-cms/reference/images/postman-setup-swagger-json-file.png new file mode 100644 index 00000000000..463023ea8fe Binary files /dev/null and b/16/umbraco-cms/reference/images/postman-setup-swagger-json-file.png differ diff --git a/16/umbraco-cms/reference/language-variation.md b/16/umbraco-cms/reference/language-variation.md new file mode 100644 index 00000000000..97d34358419 --- /dev/null +++ b/16/umbraco-cms/reference/language-variation.md @@ -0,0 +1,111 @@ +--- +description: Language variants allow you to have different variations of content based on the language culture. Learn how to use them in this section. +--- + +# Language Variation + +Language Variation allows you to have different variations of content based on a language culture. In the documentation there are other useful articles about the feature: + +* [Getting started with Language Variants](../fundamentals/backoffice/variants.md) +* [Rendering variant values](../fundamentals/design/rendering-content.md) + +[`IPublishedContent`](querying/ipublishedcontent/) contains all language variations of a node, and when rendering it out it will then use the Culture you are currently on. This can then be overridden on an individual property level if you want, like this: + +```csharp +@Model.Value("pageTitle", "fr", fallback: Fallback.ToLanguage) +``` + +Here we would attempt to render the `pageTitle` property in the French variant. We want to fallback to the current culture language if it can't find it in French. + +The challenge arises when trying to display all values of an IPublishedContent model in a specific culture from a "current culture"-less context, like a [`SurfaceController`](routing/surface-controllers/). + +If you do something like this: + +```csharp +using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Core.Cache; +using Umbraco.Cms.Core.Logging; +using Umbraco.Cms.Core.Routing; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Web; +using Umbraco.Cms.Infrastructure.Persistence; +using Umbraco.Cms.Web.Website.Controllers; +using Umbraco.Extensions; + +namespace TestStuff; + +public class TestController : SurfaceController +{ + + public TestController( + IUmbracoContextAccessor umbracoContextAccessor, + IUmbracoDatabaseFactory databaseFactory, + ServiceContext services, + AppCaches appCaches, + IProfilingLogger profilingLogger, + IPublishedUrlProvider publishedUrlProvider) + : base(umbracoContextAccessor, databaseFactory, services, appCaches, profilingLogger, publishedUrlProvider) + { + } + + public IActionResult Index() + { + var culturedRootNode = CurrentPage.Root(); + TempData.Add("CulturedRootNode", culturedRootNode); + + return View(); + } +} +``` + +You will get the root node in the default culture. However you can set a new `VariationContext` like this: + +```csharp +using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Core.Cache; +using Umbraco.Cms.Core.Logging; +using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.Routing; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Web; +using Umbraco.Cms.Infrastructure.Persistence; +using Umbraco.Cms.Web.Website.Controllers; +using Umbraco.Extensions; + +namespace TestStuff; + +public class TestController : SurfaceController +{ + private readonly IVariationContextAccessor _variationContextAccessor; + + public TestController( + IUmbracoContextAccessor umbracoContextAccessor, + IUmbracoDatabaseFactory databaseFactory, + ServiceContext services, + AppCaches appCaches, + IProfilingLogger profilingLogger, + IPublishedUrlProvider publishedUrlProvider, + IVariationContextAccessor variationContextAccessor) + : base(umbracoContextAccessor, databaseFactory, services, appCaches, profilingLogger, publishedUrlProvider) + { + _variationContextAccessor = variationContextAccessor; + } + + public IActionResult Index() + { + const string culture = "af"; // Afrikaans + + // This is how the culture is set for the context we are in + _variationContextAccessor.VariationContext = new VariationContext(culture); + + var culturedRootNode = CurrentPage.Root(); + TempData.Add("CulturedRootNode", culturedRootNode); + + return View(); + } +} +``` + +So we access the `IVariationContextAccessor.VariationContext` and set it to a new one with our own specified culture (remember `using Umbraco.Core.Models.PublishedContent;` at the top to get access to it). + +The elements you get afterward will be in the culture you have specified. diff --git a/16/umbraco-cms/reference/management-api/README.md b/16/umbraco-cms/reference/management-api/README.md new file mode 100644 index 00000000000..458e04f29ce --- /dev/null +++ b/16/umbraco-cms/reference/management-api/README.md @@ -0,0 +1,70 @@ +--- +description: Get started with the Management API. +--- + +# Management API + +## Management API + +The Management API delivers headless management capabilities built directly into Umbraco. The Management API is used by the backoffice to communicate with the backend. + +The Management API can also be used for Custom apps or Workflows with OpenID Connect. + +{% hint style="info" %} +The Management API is a replacement for the backoffice controllers that lacked RESTful capabilities. +{% endhint %} + +### Swagger Documentation + +Umbraco ships with Swagger to document the Management API. Swagger and the Swagger UI are based on [Swashbuckle.AspNetCore](https://github.com/domaindrivendev/Swashbuckle.AspNetCore/) and is available at `{yourdomain}/umbraco/swagger`. For security reasons, both are disabled in production environments. + +The Swagger documentation allows you to select a definition and go to either Umbraco Management API or Content Delivery API. If you are extending the Management API with your own controllers, you can also create custom documentation for these in Swagger. See [Custom Swagger API](../custom-swagger-api.md) and [Creating a backoffice API](../../tutorials/creating-a-backoffice-api/) articles for details. + +![Umbraco Management API documentation in Swagger](../images/management-api-swagger.png) + +In the Swagger Umbraco Management API, you can find a collection of available endpoints in this version of Umbraco. + +### Authorization + +The Management API endpoints are protected by the backoffice authorization policies and need an authentication token to interact with them. + +To set the authorization, click on the **Authorize** button: + +![Umbraco Management API Authorize Button](../images/management-api-swagger-authorize-button.png) + +Then a popup will appear with some setup information and a login form for authorization: + +![Umbraco Management API Authorize Login](../images/management-api-swagger-authorize-instructions.png) + +The available integration for the authorization is done via a backoffice user with the integration of `OAuth2, authorizationCode with PKCE`. Swagger is only enabled in non-production environments, so if you need to access the Management API in production, you need a different client. + +{% hint style="info" %} +In production environment, only `umbraco-back-office` **client** is allowed to connect to the Management API. In non-production environments, the `umbraco-swagger` and `umbraco-postman` **clients** can be used. + +You can see an example of how to connect a backoffice user via OAuth2 in Postman in the [Swagger Setup in Postman](postman-setup-swagger.md) article. +{% endhint %} + +## Test an Endpoint + +To test a Management API endpoint, follow these steps: + +1. Authenticate via the **Authorize** button. Make sure you use `umbraco-swagger` as the `client_id`: + +![Umbraco Management API when Authenticated](../images/management-api-swagger-authenticated.png) + +{% hint style="info" %} +In non-production environments, you will always need only the `client_id` to authenticate. The `client_secret` should always be left blank, as the authentication flow does not use a client secret. +{% endhint %} + +2. Expand the first endpoint of **Audit Log** and click **Try it out** + +![Umbraco Management API Endpoint - Try it Out Button](../images/management-api-try-it-out.png) + +3. You now have the option to change the values of the **parameters**. In this case, let´s leave the default values as they are. +4. Click on **Execute** so that we can get some data for our **Audit Log** endpoint. + +![Umbraco Management API Endpoint - Execute - Response](../images/management-api-execute-response.png) + +In the **Response Body** we get the details of the **Audit Log** that we have requested. + +5. You can continue changing the default **parameters**, **clear** the query, or **cancel** the trial of the endpoint. diff --git a/16/umbraco-cms/reference/management-api/external-access.md b/16/umbraco-cms/reference/management-api/external-access.md new file mode 100644 index 00000000000..7c7aebc79e0 --- /dev/null +++ b/16/umbraco-cms/reference/management-api/external-access.md @@ -0,0 +1,95 @@ +--- +description: How external applications can consume the Management API. +--- + +# External access to the Management API + +The Management API can be used directly for integrations between Umbraco and external systems. + +When consuming the Management API from an external source, you must use the OpenId Connect Client Credentials flow for authorization. Refer to the [API Users](../../fundamentals/data/users/api-users.md) article for details on setting up Client Credentials. + +With a set of Client Credentials in place, you can obtain an access token from the Management API token endpoint: `/umbraco/management/api/v1/security/back-office/token`. + +The token endpoint response looks like this: + +```json +{ + "access_token": "ZnEAKg5YwDc7621y6xZlEdT9kwp_ULGQPc5mnY9cDw0", + "token_type": "Bearer", + "expires_in": 299 +} +``` + +As shown, the access token should be used as a Bearer token when consuming the Management API. + +Also, notice that access tokens have a fixed expiry. While you can keep issuing new tokens for the Client Credentials, please reuse tokens within their lifespan. This will be more performant and avoid flooding the Umbraco database with tokens. + +{% hint style="info" %} +The Management API does not support OpenID Connect Discovery. This is reserved for Members accessing protected content via the [Delivery API](../content-delivery-api/protected-content-in-the-delivery-api/README.md). +{% endhint %} + +The following code sample demonstrates how to consume the Management API by + +1. Obtaining an access token from the token endpoint, and +2. Fetching data from the "current user" endpoint. + +![The "current user" endpoint in Swagger UI](images/current-user-endpoint.png) + +{% hint style="info" %} +This sample requires the [`IdentityModel`](https://www.nuget.org/packages/IdentityModel) NuGet package to run. +{% endhint %} + +{% code title="Program.cs" lineNumbers="true" %} +```csharp +using System.Net.Http.Json; +using IdentityModel.Client; + +// the base URL of the Umbraco site - change this to fit your setup +const string host = "https://localhost:44391"; + +var client = new HttpClient(); + +// request a client credentials token from the Management API token endpoint +var tokenResponse = await client.RequestClientCredentialsTokenAsync( + new ClientCredentialsTokenRequest + { + Address = $"{host}/umbraco/management/api/v1/security/back-office/token", + ClientId = "umbraco-back-office-my-client", + ClientSecret = "my-client-secret" + } +); + +if (tokenResponse.IsError || tokenResponse.AccessToken is null) +{ + Console.WriteLine($"Error obtaining a token: {tokenResponse.ErrorDescription}"); + return; +} + +// use the access token as Bearer token +client.SetBearerToken(tokenResponse.AccessToken); + +// fetch user data from the "current user" Management API endpoint +var apiResponse = await client.GetAsync($"{host}/umbraco/management/api/v1/user/current"); +var apiUserResponse = await apiResponse + .EnsureSuccessStatusCode() + .Content + .ReadFromJsonAsync(); + +if (apiUserResponse is null) +{ + Console.WriteLine("Could not parse a user from the API response."); + return; +} + +Console.WriteLine($"Hello, {apiUserResponse.Name} ({apiUserResponse.Email})"); + +public class ApiUserResponse +{ + public required Guid Id { get; set; } + + public required string Name { get; set; } + + public required string Email { get; set; } +} +``` +{% endcode %} diff --git a/16/umbraco-cms/reference/management-api/images/current-user-endpoint.png b/16/umbraco-cms/reference/management-api/images/current-user-endpoint.png new file mode 100644 index 00000000000..fd7a06436c0 Binary files /dev/null and b/16/umbraco-cms/reference/management-api/images/current-user-endpoint.png differ diff --git a/16/umbraco-cms/reference/management-api/postman-setup-swagger.md b/16/umbraco-cms/reference/management-api/postman-setup-swagger.md new file mode 100644 index 00000000000..622079a8526 --- /dev/null +++ b/16/umbraco-cms/reference/management-api/postman-setup-swagger.md @@ -0,0 +1,133 @@ +--- +description: Setup OAuth authorization for swagger via Postman +--- + +# Overview + +{% hint style="info" %} + +This guide is created by a community member and is not managed by Umbraco HQ. Some attributes may change in the future because of the integration with Postman (third-party tool). + +{% endhint %} + +This guide covers how to set up OAuth authorization for the Management API using Postman. + +Before proceeding, make sure to read the [Management API](./README.md) article. It provides information about Authorization and why it is needed in this article. + +This guide covers the following: + +1. [Importing the collection](#importing-the-collection) +2. [Setup authorization](#setup-authorization) +3. [Get a token for a new user](#get-a-token-for-a-new-user) +4. [Common pitfalls and errors](#common-pitfalls-and-errors) + +# Importing the collection + +1. Open the swagger UI at `{yourdomain}/umbraco/swagger`. +2. Choose **Umbraco Management API** from **Select a definition**. +3. Open the JSON file, which you can find right underneath the **Title**: + +![JSON file location](../images/postman-setup-swagger-json-file.png) + +4. Save the JSON file to disk. The name of the file will be saved by default with the name of `swagger.json`. +5. Click to [create a new collection](https://learning.postman.com/docs/collections/using-collections/#creating-collections) in Postman. +6. Import the `swagger.json` file. +7. Choose **Postman Collection** when prompted. + +![Postman import JSON file as collection](../images/postman-setup-swagger-import.png) + +Once imported, you will see a new collection called **Umbraco Management API**. + +# Setup Authorization + +## Setup Variables Values + +1. Click on **Variables** tab in the **Umbraco Management API** collection. +2. Add a new variable called `baseUrl` and in the **Initial** and **Current** values add your URL, which in this example we use the `localhost URL` (without trailing slashes): + +```http +https://localhost:44331 +``` + +{% hint style="info" %} + +The localhost URL might vary from this example. Make sure to change the URL to the current localhost URL your project is running on. + +{% endhint %} + +3. Save the changes. + +## Setup Authorization Values + +To set up authorization values, follow these steps: +1. Click on **Authorization** tab in the **Umbraco Management API** collection. + +2. Choose `OAuth 2.0` from **Type** +3. Check if these attributes are set: + +* **Add auth data** is set to `Request Headers` +* **Auto-refresh token** is `Disabled` + +### Configure Token + +Now let's setup a new token: + +1. Add a **Token name** called `BackofficeSwagger` under **Configure New Token**. The token name can be anything. +2. Choose `Authorization Code (With PKCE)` from **Grant Type**. +3. Click to enable `Authorize using browser` on **Callback URL**. +4. Add the following on **Auth URL**: + +```http +{{baseUrl}}/umbraco/management/api/v1/security/back-office/authorize +``` + +5. Add the following on **Access Token URL**: + +```http +{{baseUrl}}/umbraco/management/api/v1/security/back-office/token +``` + +6. Add `umbraco-postman` on **Client ID**. +7. Choose `SHA-256` from **Code Challenge Method** . +8. Choose `Send Client credentials in body` from **Client Authentication**. +9. Any other field should either be empty or auto-filled by default. +10. Click **Save**. + +11. Click on **Get New Access Token**. +A window appears to authenticate into the Backoffice. Follow the given instruction to **Open in Postman**. +* You will see a new **Manage access tokens** window in Postman. +12. Click **Use Token**. + +# Get a token for a new user + +1. Click on **Authorization** tab in the **Umbraco Management API** collection . +2. Click on `Clear Cookies` at the bottom of the page above the **Get New Access Token**. +3. Open your localhost instance of Umbraco in the browser. Example: `https://localhost:44331`. +4. Inspect the page, go to **Application** tab and clear the `UmbracoBackOffice` cookie. +5. Click on **Get New Access Token** in Postman and +6. Click on **Use Token** after authentication. + +# Common pitfalls and errors + +## Missing agent + +When trying to obtain a token you might run into an error. If you see the message `Error: localhost request not supported` in the Postman console, it means the Postman agent is missing. To resolve this issue, you can download the Postman agent from the Postman website [Postman website](https://www.postman.com/downloads/postman-agent/) and try again. + +## SSL Certificate verification + +When requesting a token, you might get an error that reads `Error: unable to verify the first certificate` in the console. +To resolve this: +1. Click on the **Settings** cog wheel in the top right corner next to the **Invite** button. + +![Postman Cog Wheel Location](../images/postman-setup-swagger-cog-wheel.png) + +2. Click on **Settings** and disable `SSL certificate verification`. + +## Making a request + +When making a request for the first time, follow these steps: + +1. Click on the **Authorization** tab in the **Umbraco Management API** collection. +2. Choose `Inherit auth from parent` from **Type**. +3. Disable any parameters you are not using as Postman sets their value to default sometimes. +4. Click **Save** diff --git a/16/umbraco-cms/reference/management/README.md b/16/umbraco-cms/reference/management/README.md new file mode 100644 index 00000000000..2074d0254c4 --- /dev/null +++ b/16/umbraco-cms/reference/management/README.md @@ -0,0 +1,25 @@ +--- +description: >- + Details of CRUD operations within Umbraco and how to interact with the data + persisted in the database +--- + +# Management + +The intended audience for these reference pages is .NET developers. It is assumed the reader already knows the basics of Umbraco and knows .NET & C#. + +{% hint style="warning" %} +Since the release of Umbraco 14, the documentation for Models and Services has been removed from the documentation. +{% endhint %} + +## [Models (external)](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Models.html) + +Find references for the models in the public API. The models include Content, ContentType, Language, Media, and Template classes. + +## [Services (external)](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Services.html) + +Find references for the services available for performing Create, Read, Update, and Delete (CRUD) operations for the models. + +### [Learn how to use the Umbraco Services](using-services/) + +Find a handful of resources for using some of the services available with Umbraco CMS. diff --git a/16/umbraco-cms/reference/management/using-services/README.md b/16/umbraco-cms/reference/management/using-services/README.md new file mode 100644 index 00000000000..46ef6ee8094 --- /dev/null +++ b/16/umbraco-cms/reference/management/using-services/README.md @@ -0,0 +1,51 @@ +--- +description: List of service references along with instructions on how to use them, as well as some examples for better understanding. +--- + +# Using Umbraco services + +In this article you can learn how to use and work with some of the services provided with Umbraco CMS. + +You can find a list of all supported services in the [API Documentation](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Services.html). + +## Getting a Service + +All services can be accessed with the following using statement: + +```csharp +using Umbraco.Cms.Core.Services; +``` + +In some cases, you can use [Dependency Injection](https://learn.microsoft.com/en-us/dotnet/core/extensions/dependency-injection). For example, if you have registered your class in Umbraco's dependency injection, you can specify the service interface in your constructor. + +To use the `NotificationService` you can use Dependency Injection via the `INotificationService` interface like this: + +```csharp +public class MyClass +{ + private INotificationService _notificationService; + + public MyClass(INotificationService notificationService) + { + _notificationService = notificationService; + } +} +``` + +In Razor views, you can access the Notification Service through the `@inject` directive: + +```csharp +@inject INotificationService NotificationService +``` + +Use the above example for other services by replacing the interface and the name of the service. + +## Examples on using services + +* [Consent Service](./consentservice.md) +* [User Service](./userservice.md) +* [Content Service](./contentservice.md) +* [Media Service](./mediaservice.md) +* [Relation Service](./relationservice.md) +* [Content Type Service](./contenttypeservice.md) +* [Localization Service](./localizationservice.md) diff --git a/16/umbraco-cms/reference/management/using-services/consentservice.md b/16/umbraco-cms/reference/management/using-services/consentservice.md new file mode 100644 index 00000000000..d69f8a4d09b --- /dev/null +++ b/16/umbraco-cms/reference/management/using-services/consentservice.md @@ -0,0 +1,43 @@ +# Consent Service + +A service for handling lawful data processing requirements. + +## What is a Consent + +A consent is fully identified by a source (whoever is consenting), a context (for example, an application), and an action (whatever is consented). A consent state registers the state of the consent (granted, revoked...). + +## Register a new consent + +Consent can be given or revoked or changed via the `RegisterConsent` method, which creates a new `Consent` entity to track the consent. + +## Get the current state + +Getter methods of this service return the current state of a consent, that is the latest [IConsent](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Models.IConsent.html) entity that was created. + +## Revoking a consent + +Revoking a consent is performed by registering a revoked consent. + +A consent _cannot be deleted_. It can only be revoked by registering a "revoked consent". + +## Examples + +```csharp +// store a new consent +var newConsent = _consentService.RegisterConsent("userId", "Our.Custom.Umbraco.Plugin", "AllowedToEmail", ConsentState.Granted, "some comments"); + +// lookup a consent +var consents = _consentService.LookupConsent("userId", "Our.Custom.Umbraco.Plugin", "AllowedToEmail", sourceStartsWith : true); +if (consents != null && consents.Any()) +{ + var currentConsent = consents.First(c => c.Current == true); + if(currentConsent.State == ConsentState.Granted) + { + // Do what you need + } + else + { + // the state is None, Pending or Revoked + } +} +``` diff --git a/16/umbraco-cms/reference/management/using-services/contentservice.md b/16/umbraco-cms/reference/management/using-services/contentservice.md new file mode 100644 index 00000000000..986c38de8b5 --- /dev/null +++ b/16/umbraco-cms/reference/management/using-services/contentservice.md @@ -0,0 +1,87 @@ +--- +description: Example on how to create and publish content programmatically using the ContentService. +--- + +# Content Service + +Learn how to use the Content Service. + +## Creating content programmatically + +In the example below, a new page is programmatically created using the content service. It is assumed that there are two document types, namely Catalogue and Product. In this case, a new Product is added underneath the Catalogue page. Add the below code in the Catalogue template. + +```csharp +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Services; + +namespace Umbraco.Cms.Web.UI.Custom; + +public class PublishContentDemo +{ + private readonly IContentService _contentService; + + public PublishContentDemo(IContentService contentService) => _contentService = contentService; + + public void Create() + { + // Create a variable for the GUID of the parent page - Catalogue, where you want to add a child item. + var parentId = Guid.Parse("b6fbbb31-a77f-4f9c-85f7-2dc4835c7f31"); + + // Create a new child item of type 'Product' + var demoProduct = ContentService.Create("Microphone", parentId, "product"); + + // Set the value of the property with alias 'category' + demoProduct.SetValue("category" , "audio"); + + // Set the value of the property with alias 'price' + demoProduct.SetValue("price", "1500"); + + // Save and publish the child item + _contentService.SaveAndPublish(demoProduct); + } +} +``` + +In a multi-language setup, it is necessary to set the name of the content item for a specified culture: + +```csharp +demoProduct.SetCultureName("Microphone", "en-us"); // this will set the english name +demoProduct.SetCultureName("Mikrofon", "da"); // this will set the danish name +``` + +For information on how to retrieve multilingual languages, see the [Retrieving languages](localizationservice.md) article. + +## Publishing content programmatically + +The ContentService is also used for publishing operations. + +The following example shows a page being published with all descendants. + +```csharp +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Services; + +namespace Umbraco.Cms.Web.UI.Custom; + +public class PublishContentDemo +{ + private readonly IContentService _contentService; + + public PublishContentDemo(IContentService contentService) => _contentService = contentService; + + public void Publish(Guid key) + { + IContent? content = _contentService.GetById(key) + ?? throw new InvalidOperationException($"Could not find content with key: {key}."); + + _contentService.SaveAndPublishBranch(content, PublishBranchFilter.Default); + } +} +``` + +The `PublishBranchFilter` option can include one or more of the following flags: + +- `Default` - publishes existing published content with pending changes. +- `IncludeUnpublished` - publishes unpublished content and existing published with pending changes. +- `ForceRepublish` - publishes existing published content with or without pending changes. +- `All` - combines `IncludeUnpublished` and `ForceRepublish`. diff --git a/16/umbraco-cms/reference/management/using-services/contenttypeservice.md b/16/umbraco-cms/reference/management/using-services/contenttypeservice.md new file mode 100644 index 00000000000..cb31d19f5f2 --- /dev/null +++ b/16/umbraco-cms/reference/management/using-services/contenttypeservice.md @@ -0,0 +1,126 @@ +--- +description: Examples on how to retrieve content types and content type containers using the ContentTypeService. +--- + +# Content Type Service + +Learn how to work with Content Types through the Content Type Service. + +## Getting a single content type + +A given content type has a few different unique identifier that we can use to look it up via the content type service. For instance, if we know the GUID of the content type, we can look it up like this: + +```csharp +// Declare the GUID ID +Guid guid = new Guid("796a8d5c-b7bb-46d9-bc57-ab834d0d1248"); + +// Get a reference to the content type by its GUID ID +IContentType contentType = _contentTypeService.Get(guid); +``` + +Although the use of a GUID is preferable, you can also use it's numeric ID: + +```csharp +// Get a reference to the content type by its numeric ID +IContentType contentType = _contentTypeService.Get(1234); +``` + +Finally, you can also look up a content type by its alias: + +```csharp +// Get a reference to the content type by its alias +IContentType contentType = _contentTypeService.Get("home"); +``` + +## Getting a list of content types + +As content types are stored in a hierarchical list with folders (containers), there is also multiple ways to can get content types. If you are looking for a flat list of all content types, you can use the `GetAll` method: + +```csharp +// Get a collection of all content types +IEnumerable contentTypes = _contentTypeService.GetAll(); +``` + +The service also have `GetMany`-methods to get a collection of content types by their GUIDs IDs or numeric IDs: +```csharp +// Get a collection of two specific content types by their GUIDs IDs +IEnumerable contentTypes = _contentTypeService.GetMany(new[] { + new Guid("2b54088e-d355-4b9e-aa4b-5aec4b3f87eb"), + new Guid("859c5916-19d8-4a72-9bd0-5641ad503aa9") +}); +``` + +```csharp +// Get a collection of two specific content types by their numeric IDs +IEnumerable contentTypes = _contentTypeService.GetMany(1234, 1235); +``` + +To get a list of all Content Types of another content type, you can use the `GetChildren` method. This can be done by specifying the numeric ID or the GUID: + +```csharp +// Get a collection of content types of a specific content type +IEnumerable contentTypes = _contentTypeService.GetChildren(1232); +``` + +```csharp +IEnumerable contentTypes = _contentTypeService.GetChildren(Guid.Parse("4f89dd28-d038-4209-aaa1-06109b7946a7")); +``` + +## Check whether a content type has children + +In some cases it can be useful to check if a content type has children. The `HasChildren` method can be used to check whether a content type has children. + +```csharp +// Check if there are children +bool hasChildren = _contentTypeService.HasChildren(Guid.Parse("2b54088e-d355-4b9e-aa4b-5aec4b3f87eb")); +``` + +Although the use of a GUID is preferable, you can also use it's numeric ID: + +```csharp +// Check if there are children +bool hasChildren = _contentTypeService.HasChildren(1234); +``` + +## Retrieving content type container + +### Getting a single content type container + +You can add content types in three different ways. At the root level, under another content type, or under a _container_ (which is a folder). To obtain a single container, the process is similar to obtaining a single content type. This means that you can search for a container either by its GUID: + +```csharp +// Declare the GUID ID +Guid guid = new Guid("d3b9cc9a-d471-4465-a89a-112c6bc1e5b4"); + +// Get a container by its GUID ID +EntityContainer container = _contentTypeService.GetContainer(guid); +``` + +or its numeric counterpart: + +```csharp +// Get a container by its numeric ID +EntityContainer container = _contentTypeService.GetContainer(1090); +``` + +### Getting a list of content type containers + +In the same way as you can get the content types of a container, you can get the child containers of another container. This is done by calling the `GetContainers` method with an array of numeric IDs: + +```csharp +// Declare the array of IDs to lookup +int[] ids = new[] {1090}; + +// Get the child containers via the content type service +IEnumerable containers = _contentTypeService.GetContainers(ids); +``` + +Also, if the array is empty, all containers will be returned: + +```csharp +// Declare the array of IDs to lookup +int[] ids = new int[0]; + +// Get all content type containers +IEnumerable containers = _contentTypeService.GetContainers(ids); +``` diff --git a/16/umbraco-cms/reference/management/using-services/images/relations-api.PNG b/16/umbraco-cms/reference/management/using-services/images/relations-api.PNG new file mode 100644 index 00000000000..a9a39ed6b55 Binary files /dev/null and b/16/umbraco-cms/reference/management/using-services/images/relations-api.PNG differ diff --git a/16/umbraco-cms/reference/management/using-services/images/relations.PNG b/16/umbraco-cms/reference/management/using-services/images/relations.PNG new file mode 100644 index 00000000000..e446f442f5e Binary files /dev/null and b/16/umbraco-cms/reference/management/using-services/images/relations.PNG differ diff --git a/16/umbraco-cms/reference/management/using-services/localizationservice.md b/16/umbraco-cms/reference/management/using-services/localizationservice.md new file mode 100644 index 00000000000..0440a602b45 --- /dev/null +++ b/16/umbraco-cms/reference/management/using-services/localizationservice.md @@ -0,0 +1,98 @@ +--- +description: Example on how to retrieve languages using the LocalizationService. +--- + +# Localization Service + +Learn how to use the Localization service to retrieve languages. + +## Getting a single language + +The localization service contains a number of methods for looking up languages. If you already know the ID of a specific language (eg. the default language has ID `1`), you can use the `GetLanguageById` method to get the reference to that language: + +```csharp +// Get a reference to the language by its ID +ILanguage language1 = _localizationService.GetLanguageById(1); +``` + +As an alternative, you can look up a language by its ISO code via the `GetLanguageByIsoCode` method: + +```csharp +// Get a reference to the language by its ISO code +ILanguage language2 = _localizationService.GetLanguageByIsoCode("en-US"); +``` + +The ISO code is a combination of the two-letter ISO 639-1 language code (lowercase) and two-letter ISO-3166 country code (uppercase). Eg. `en-US` for English in the United States, `en-GB` for English in the United Kingdom and `da-DK` for Danish in Denmark. + +Both methods will return an instance of the [ILanguage](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Models.ILanguage.html) interface, which has traditional properties like `Id` and `Key`, but also properties specific to the language like `CultureName`, `CultureInfo` and `IsoCode`. You can see the API reference for further information on the properties of the interface. + +## Getting all languages + +If you need instead need a list of all installed languages, you can use the `GetAllLanguages` method. It takes no parameters, and as such a returns a collection of all languages (with no pagination like some of the other services): + +```csharp +// Get a collection of all languages +IEnumerable languages = _localizationService.GetAllLanguages(); + +// Iterate over the collection +foreach (ILanguage language in languages) +{ + // Get the .NET culture info + CultureInfo cultureInfo = language.CultureInfo; + +
ID: @language.Id
+
Key: @language.Key
+
Name: @language.CultureName
+
ISO: @language.IsoCode
+
Culture info: @cultureInfo
+
+} +``` + +As shown in the example above, you can get the `System.Globalization.CultureInfo` instance of each language. The CultureInfo determines how numbers, dates and similar should be either parsed or formatted in .NET. + +## Full example + +Below you can see a full example of the examples shown above - including the necessary imports: + +```csharp +@using System.Globalization +@using Umbraco.Cms.Core.Models +@using Umbraco.Cms.Core.Services +@inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage +@inject ILocalizationService LocalizationService + +@{ + // Get a collection of all languages + IEnumerable languages = LocalizationService.GetAllLanguages(); + + // Iterate over the collection + foreach (ILanguage language in languages) + { + + // Get the .NET culture info + CultureInfo cultureInfo = language.CultureInfo; + +
ID: @language.Id
+
Key: @language.Key
+
Name: @language.CultureName
+
ISO: @language.IsoCode
+
Culture info: @cultureInfo
+
+ + } + + // Get a reference to the language by its ID + ILanguage language1 = LocalizationService.GetLanguageById(1); + + // Get a reference to the language by its ISO code + ILanguage language2 = LocalizationService.GetLanguageByIsoCode("en-US"); + +
@language1
+
@language2
+} +``` + +{% hint style="warning" %} +The above example is using `ILocalizationService` which is currently obselete and will be removed in v15. Use `ILanguageService` or `IDictionaryItemService` (for dictionary item operations) instead. +{% endhint %} diff --git a/16/umbraco-cms/reference/management/using-services/mediaservice.md b/16/umbraco-cms/reference/management/using-services/mediaservice.md new file mode 100644 index 00000000000..8b8ead0007d --- /dev/null +++ b/16/umbraco-cms/reference/management/using-services/mediaservice.md @@ -0,0 +1,83 @@ +--- +description: Examples on how to create a new folder and a new media item from a stream by using the MediaService. +--- + +# Media Service + +In this article, you can find some examples on how to create a new media folder and a media item from a stream programmatically. + +Samples in this document will require the following using statements: + +```csharp +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.IO; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Strings; +using Umbraco.Extensions; +``` + +## Creating a new folder + +To create a new folder at the root of the media archive, your code could look like the following: + +```csharp +// Initialize a new media at the root of the media archive +IMedia folder = _mediaService.CreateMedia("Samples Media Item Folder", Constants.System.Root, Constants.Conventions.MediaTypes.Folder); + +// Save the folder +var result = _mediaService.Save(folder); +``` + +Alternatively, you can replace the Constants in the above sample with hardcoded values. + +```csharp +// Initialize a new media at the root of the media archive +IMedia folder = _mediaService.CreateMedia("Samples Media Item Folder", -1, "Folder"); + +// Save the folder +var result = _mediaService.Save(folder); +``` + +For the `CreateMedia` method, the first parameter is the name of the folder to be created. + +The second parameter is the ID of the parent media item. `Constants.System.Root` is a constant defined in Umbraco with the value of `-1`, which is used for indicating the root of the media archive. Instead of specifying the numeric ID of the parent, you may instead specify either a `Guid` ID or an `IMedia` instance representing the parent media. + +The third parameter is the alias of the Media Type. As Umbraco comes with a Folder Type by default, we can use the `Constants.Conventions.MediaTypes.Folder` constant to specify that the alias of the Media Type is `Folder`. + +Besides the three mandatory parameters, you can specify a user's numeric ID for media creation attribution. Unspecified cases default to the "Administrator" user with ID `-1`. + +## Creating a new media item from a stream + +You can specify a `Stream` for the contents of the file that should be created. + +As an example, if you have an image on disk named `unicorn.jpg` in the images folder of `wwwroot`. You can open a new stream for a file on the disk, and then create a new media item for that file in Umbraco: + +Please be aware that you will need to inject the following services: + +* `MediaFileManager _mediaFileManager` +* `IShortStringHelper _shortStringHelper` +* `IContentTypeBaseServiceProvider _contentTypeBaseServiceProvider` +* `MediaUrlGeneratorCollection _mediaUrlGeneratorCollection` +* `IMediaService _mediaService` +* `IWebHostEnvironment _webHostEnvironment` + +```csharp +string webRootPath = _webHostEnvironment.WebRootPath; +var path = Path.Combine(webRootPath, "images", "unicorn.jpg"); + +// Open a new stream to the file +using (Stream stream = System.IO.File.OpenRead(path)) +{ + // Initialize a new image at the root of the media archive + IMedia media = _mediaService.CreateMedia("Unicorn", Constants.System.Root, Constants.Conventions.MediaTypes.Image); + // Set the property value (Umbraco will handle the underlying magic) + media.SetValue(_mediaFileManager, _mediaUrlGeneratorCollection, _shortStringHelper, _contentTypeBaseServiceProvider, Constants.Conventions.Media.File, "unicorn.jpg", stream); + + // Save the media + var result = _mediaService.Save(media); +} +``` + +Again Umbraco will make sure the necessary properties are updated. diff --git a/16/umbraco-cms/reference/management/using-services/relationservice.md b/16/umbraco-cms/reference/management/using-services/relationservice.md new file mode 100644 index 00000000000..bed26552c64 --- /dev/null +++ b/16/umbraco-cms/reference/management/using-services/relationservice.md @@ -0,0 +1,135 @@ +# Relation Service + +The `RelationService` allows you to create relations between objects that would otherwise have no obvious connection. + +Below you will find examples using `RelationService`. + +## Automatically relate to the root node + +To perform the said task we need a Notification Handler: + +[You can read more about composing Umbraco here](../../../implementation/composing.md) + +```csharp +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; + +namespace Doccers.Core.Components; + +public class ContentPublishedNotificationHandler(IContentService contentService, IRelationService relationService) : INotificationHandler +{ + public void Handle(ContentPublishedNotification notification) + { + var home = contentService.GetRootContent().FirstOrDefault(); + if (home == null) return; + + // Get the relation type by alias + var relationType = relationService.GetRelationTypeByAlias("homesick"); + + if (relationType == null) return; + + foreach (var entity in notification.PublishedEntities + .Where(x => x.Id != home.Id)) + { + // Check if they are already related + if (!relationService.AreRelated(home.Id, entity.Id)) + { + // If not then let us relate the currenty entity to home + relationService.Relate(home.Id, entity.Id, relationType); + } + } + } +} +``` + +To have Umbraco recognize our Notification Handler we need to register it in a composer: + +```csharp +using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Core.Notifications; + +namespace Doccers.Core.Composers; + +public class RelationComposer : IComposer +{ + public void Compose(IUmbracoBuilder builder) + { + builder.AddNotificationHandler(); + } +} +``` + +If I now `Save and Publish` my `Products` node I get the following result: + +![Relations](../../../../../10/umbraco-cms/reference/management/services/images/relations.PNG) + +Now let us try and fetch the data from an API. + +```csharp +using Microsoft.AspNetCore.Mvc; +using System.Runtime.Serialization; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Web.Common; + +namespace Doccers.Core.Controllers.Http; + +[ApiController] +[Route("/umbraco/api/relations")] +public class RelationsController : Controller +{ + private readonly IRelationService _relationService; + private readonly UmbracoHelper _umbracoHelper; + + public RelationsController(IRelationService relationService, UmbracoHelper umbracoHelper) + { + // Alternatively you could also access + // the service via the service context: + // _relationService = Services.RelationService; + _relationService = relationService; + _umbracoHelper = umbracoHelper; + } + + [HttpGet("getbyrelationtypealias")] + public IActionResult GetByRelationTypeAlias(string alias) + { + var relationType = _relationService.GetRelationTypeByAlias(alias); + if (relationType == null) + return BadRequest("Invalid relation type alias"); + + var relations = _relationService.GetAllRelationsByRelationType(relationType.Id); + var content = relations.Select(x => _umbracoHelper.Content(x.ChildId)) + .Select(x => new Relation() + { + Name = x.Name, + UpdateDate = x.UpdateDate + }); + + return Ok(content); + } +} +``` + +Notice the `x => new Relation()`? We need to make sure what we are returning can be serialized. Therefore the `Relation` class is: + +```csharp +[DataContract(Name = "relation")] +public class Relation +{ + [DataMember(Name = "name")] + public string Name { get; set; } + + [DataMember(Name = "updateDate")] + public DateTime UpdateDate { get; set; } +} +``` + +Browsing `/umbraco/api/relations/getbyrelationtypealias?alias=homesick` now returns the following: + +![Relations](../../../../../10/umbraco-cms/reference/management/services/images/relations-api.PNG) + +{% hint style="info" %} + +If you want to do something similar it is recommended that you wrap a caching layer around it, as the RelationService queries the database directly. + +{% endhint %} diff --git a/16/umbraco-cms/reference/management/using-services/userservice.md b/16/umbraco-cms/reference/management/using-services/userservice.md new file mode 100644 index 00000000000..a207e94a38d --- /dev/null +++ b/16/umbraco-cms/reference/management/using-services/userservice.md @@ -0,0 +1,119 @@ +--- +description: This will show you how to perform various User management using the Umbraco service layer. +--- + +# User Service + +Learn how to use the User Service to manage the users on your Umbraco project. + +## Assigning a User to a User Group + +To assign a User to a User Group, we need both the `IUserService` and `IUserGroupService`. As with all Umbraco services, these are obtained using dependency injection. + +1. Start by defining an interface for our implementation: + +{% code title="ISampleUserHandler.cs" %} +```csharp +namespace UmbracoDocs.Samples; + +public interface ISampleUserHandler +{ + Task AssignUserToAdminGroup(string email, Guid performingUserKey); +} +``` +{% endcode %} + +2. Next we implement the interface. This implementation holds the dependency to the Umbraco services: + +{% code title="SampleUserHandler.cs" %} +```csharp +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.Membership; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Services.OperationStatus; + +namespace UmbracoDocs.Samples; + +public class SampleUserHandler : ISampleUserHandler +{ + private readonly IUserService _userService; + private readonly IUserGroupService _userGroupService; + + public SampleUserHandler(IUserService userService, IUserGroupService userGroupService) + { + _userService = userService; + _userGroupService = userGroupService; + } + + public async Task AssignUserToAdminGroup(string email, Guid performingUserKey) + { + IUser? user = _userService.GetByEmail(email); + if (user is null) + { + return false; + } + + Attempt result = await _userGroupService.AddUsersToUserGroupAsync( + new UsersToUserGroupManipulationModel(Constants.Security.AdminGroupKey, [user.Key]), + performingUserKey + ); + + return result.Success; + } +} +``` +{% endcode %} + +3. Register the implementation in a Composer: + +{% code title="SampleUserHandlerComposer.cs" %} +```csharp +using Umbraco.Cms.Core.Composing; + +namespace UmbracoDocs.Samples; + +public class SampleUserHandlerComposer : IComposer +{ + public void Compose(IUmbracoBuilder builder) + => builder.Services.AddSingleton(); +} +``` +{% endcode %} + +4. Lastly, we need to put our implementation to use. This could be done in a Management API controller: + +{% code title="SampleUserController.cs" %} +```csharp +using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Api.Management.Controllers; +using Umbraco.Cms.Api.Management.Routing; +using Umbraco.Cms.Core.Security; + +namespace UmbracoDocs.Samples; + +[ApiExplorerSettings(GroupName = "Sample user handler")] +[VersionedApiBackOfficeRoute("sample/user-handler")] +public class SampleUserController : ManagementApiControllerBase +{ + private readonly ISampleUserHandler _sampleUserHandler; + private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor; + + public SampleUserController( + ISampleUserHandler sampleUserHandler, + IBackOfficeSecurityAccessor backOfficeSecurityAccessor) + { + _sampleUserHandler = sampleUserHandler; + _backOfficeSecurityAccessor = backOfficeSecurityAccessor; + } + + [HttpPut] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + public async Task AssignUserToAdminGroup(string email) + => await _sampleUserHandler.AssignUserToAdminGroup(email, CurrentUserKey(_backOfficeSecurityAccessor)) + ? Ok() + : BadRequest(); +} +``` +{% endcode %} diff --git a/16/umbraco-cms/reference/mapping.md b/16/umbraco-cms/reference/mapping.md new file mode 100644 index 00000000000..f421d938f61 --- /dev/null +++ b/16/umbraco-cms/reference/mapping.md @@ -0,0 +1,301 @@ +# UmbracoMapper + +Often in code there is a need to 'map' one object's properties to another type of object. The 'type of objects' are not related by inheritance or interface. (Think database layer object, passing information to a presentation layer ViewModel etc). In these circumstances, it can save time and provide consistency to consolidate the logic to map between the options into one set of 'Mapping' rules. + +{% hint style="info" %} +UmbracoMapper replaced AutoMapper which was an external dependency. AutoMapper builds the mapping code dynamically, based upon mapping profiles, which are defined as C# expressions. UmbracoMapper relies on static code, that is, mappings need to be hand-written. + +If you need to map `IPublishedContent`, you might need a **custom implementation** or a **third-party solution like** [**Andy Butland’s Umbraco Mapper**](https://our.umbraco.com/packages/developer-tools/umbraco-mapper) **(**which has been renamed to [Anaximapper](https://www.andybutland.dev/2022/08/a-quick-post-on-view-model-mapping.html)) rather than relying on Umbraco's `IUmbracoMapper.` +{% endhint %} + +## Accessing the IUmbracoMapper + +The IUmbracoMapper is registered with Dependency Injection (DI). It can therefore be injected into constructors of controllers, custom classes etc, wherever DI is used. + +## Mapping + +Mapping with the UmbracoMapper works in ways similar to AutoMapper: + +```csharp +// assuming source is ISource, create a new target instance +var target = umbracoMapper.Map(source); + +// assuming both source and target already exists +target = umbracoMapper.Map(source, target); +``` + +The UmbracoMapper class also defines explicit methods to map enumerables: + +```csharp +// assuming sources is IEnumerable, map to IEnumerable +var targets = umbracoMapper.MapEnumerable(sources); +``` + +Explicit mapping of enumerables enumerates the source items, and map each item individually. + +It can also implicitly map enumerables. The following code is also valid: + +```csharp +// assuming sources is IEnumerable, map to IEnumerable +var targets = umbracoMapper.Map>(sources); +``` + +If a mapping has been defined from `IEnumerable` to `IEnumerable`, then it will be used. Otherwise, the UmbracoMapper will look for a mapping from the source type to the target type, pretty much like the explicit method. + +## Defining mappings + +Mappings are defined in `IMapDefinition` instances. This interface defines one method: + +```csharp +void DefineMaps(IUmbracoMapper mapper); +``` + +Mappings are registered (and must be registered) via a [collection builder](../implementation/composing.md#collections): + +```csharp +builder.WithCollectionBuilder() + .Add(); +``` + +A definition provides a constructor, and a map: + +```csharp +public void DefineMaps(IUmbracoMapper mapper) +{ + mapper.Define( + (source, context) => { ... }, // constructor + (source, target, context) => { .... } // map + ); +} +``` + +The constructor function is used to create an instance of the target class. The most basic implementation would be: + +```csharp +(source, context) => new TargetClass(), +``` + +The mapping action is used to map an instance of the source class, to an instance of the target class. The most basic implementation would be: + +```csharp +(source, target, context) => +{ + target.MyProperty1 = source.MyProperty1; + target.MyProperty2 = source.MyProperty2; + ... +} +``` + +The constructor function is used whenever the mapper is asked to create a target instance. Then, the mapping action is used. + +In other words, `umbracoMapper.Map(source)` will first run the construction function, and then the mapping action. On the other hand, `umbracoMapper.Map(source, target)` where target already exists, would only run the mapping action. + +The UmbracoMapper class provides multiple overloads of the Define method: + +* An overload accepting a constructor function and a mapping action, as presented above. +* An overload accepting a mapping action only, which tells the mapper how to map to an existing target (but the mapper will not be able to create new target instances). +* An overload accepting a construction function, which tells the mapper how to create new target instances (but the mapper will not perform any additional mapping). +* A parameter-less overload, which defines a "no-operation" mapping (the mapper cannot create new target instance, and mapping does nothing). + +## Context + +Both constructor functions and map actions presented above expose a context parameter which is an instance of MapperContext and provides two types of services: + +* An `Items` dictionary which can store any type of object, using string keys, and can be used to carry some context along mappings; +* Some Map and MapEnumerable functions that can be used in mapping functions, to recursively map nested elements, while propagating the context. + +{% hint style="info" %} +The context provides a `HasItem` property. To check whether the context has items, without allocating an extra empty dictionary, use this property. +{% endhint %} + +The context is used, for instance, to carry the culture when mapping content items with variants. See the `MapperContextExtensions` class, which contains methods such as: + +```csharp +public static void SetCulture(this MapperContext context, string culture) +{ + context.Items[CultureKey] = culture; +} +``` + +And + +```csharp +public static string GetCulture(this MapperContext context) +{ + return context.HasItems && + context.Items.TryGetValue(CultureKey, out var obj) && + obj is string s + ? s + : null; +} +``` + +Every `Map` and `MapEnumerable` method exposed by the UmbracoMapper have overloads that can manipulate the context before executing the mapping. For instance, + +```csharp +var target = umbracoMapper.Map(source, context => + { + context.SetCulture(cultureName); + }); +``` + +## Umbraco.Code + +Umbraco.Code is an assembly which should contain coding utilities for Umbraco. At the moment, it contains only one Roslyn analyzer, the `MapAllAnalyzer`, which is used to help writing mapping methods. + +The code lives in the [Umbraco.Code repository](https://github.com/umbraco/Umbraco-Code) and the tool is available via [Nuget](https://www.nuget.org/packages/Umbraco.Code/). It is included as a development dependency in Umbraco. + +The analyzer examines every method mapping from a source to a target, and being marked with the `// Umbraco.Code.MapAll` comment block: + +```csharp +mapper.Define( + (source, context) => new Target(), // constructor + Map // map + ); + +// Umbraco.Code.MapAll +private static void Map(ISource source, + ITarget target, + MapperContext context) +{ + target.Property1 = source.Property1; + target.Property2 = source.Property2; +} +``` + +The analyzer verifies that every publicly settable property of target is assigned a value. If a property is not assigned a value, the tool raises a build error (ie. the code will not compile). + +Since, contrary to AutoMapper, mapping is not implicit nor automatic, this ensures that an error would be raised. Should a new property be added to ISource, the corresponding mappings must be updated. + +It is possible to exclude some properties from the check: + +```csharp +// Umbraco.Code.MapAll -Property2 +``` + +And the comment can be repeated if the list of excluded properties is long: + +```csharp +// Umbraco.Code.MapAll -Property2 -Property3 -Property4 +// Umbraco.Code.MapAll -Property5 -Property6 -Property7 +``` + +The analyzer follows the standard analyzer development patterns, and building the code in Release mode produces the appropriate NuGet package. + +## Full example + +Below you will find a full example showing you how to map a collection of type Product to a collection of type ProductDto. + +```csharp +#region Models + +public class Product +{ + public string Name { get; set; } + public string SuperSecretThingNotForPublicDisplay { get; set; } +} + +[DataContract(Name = "product")] +public class ProductDto +{ + [DataMember(Name = "name")] + public string Name { get; set; } +} + +#endregion + +#region Mapping + +public class ProductMappingDefinition : IMapDefinition +{ + public void DefineMaps(IUmbracoMapper mapper) + { + mapper.Define((source, context) => new ProductDto(), Map); + } + + private void Map(Product source, ProductDto target, MapperContext context) + { + target.Name = source.Name; + } +} + +#endregion + +#region Composing + +public class ProductComposer : IComposer +{ + public void Compose(IUmbracoBuilder builder) + { + builder.WithCollectionBuilder() + .Add(); + } +} + +#endregion + +[ApiController] +[Route("/umbraco/api/products")] +public class ProductsController : Controller +{ + private readonly IUmbracoMapper _mapper; + + public ProductsController(IUmbracoMapper mapper) => _mapper = mapper; + + [HttpGet("getall")] + public IActionResult GetAll() + { + var products = FakeServiceCall(); + var mapped = _mapper.MapEnumerable(products); + + return Ok(mapped); + } + + [HttpGet("getfirstproduct")] + public IActionResult GetFirstProduct() + { + var product = FakeServiceCall().First(); + var mapped = _mapper.Map(product); + + return Ok(mapped); + } + + private IEnumerable FakeServiceCall() + { + return new List() + { + new Product() + { + Name = "Umbraco Cloud", + SuperSecretThingNotForPublicDisplay = "Secret" + }, + new Product() + { + Name = "Umbraco Forms", + SuperSecretThingNotForPublicDisplay = "Also secret" + } + }; + } +} +``` + +Result from `/umbraco/api/products/getall`: + +```json +[ + { + "name": "Umbraco Cloud" + }, + { + "name": "Umbraco Forms" + } +] +``` + +Result from `/umbraco/api/products/getfirstproduct`: + +```json +{ + "name": "Umbraco Cloud" +} +``` diff --git a/16/umbraco-cms/reference/notifications/README.md b/16/umbraco-cms/reference/notifications/README.md new file mode 100644 index 00000000000..b551f10a97d --- /dev/null +++ b/16/umbraco-cms/reference/notifications/README.md @@ -0,0 +1,289 @@ +--- +description: Get started with Notifications. +--- + +# Using Notifications + +Umbraco uses Notifications (similar to the Observer pattern) to allow you to hook into the workflow process for the backoffice. For example, notifications allow you to execute some code every time a page is published. + +## Notifications + +All notifications reside in the `Umbraco.Cms.Core.Notifications` namespace and are postfixed with `Notification`. + +Available notifications typically exist in pairs, with "before" and "after" notifications. For example, the ContentService class has the concept of **publishing** and **published** notifications. So, there is both a `ContentPublishingNotification` and a `ContentPublishedNotification` notification. + +The notification to use depends on what you want to achieve. If you want to be able to cancel the action, you would use the `CancelOperation` method on the "before" notification. See the sample in [ContentService Notifications](contentservice-notifications.md). If you want to execute some code after the publishing has succeeded, then you would use the "after" notification. + +## Registering Notifications + +Check the [Notification Handler](notification-handler.md) article to learn more about notification handlers lifetime, async notification handler and how to register the notification handlers. + +## List of Notifications + +Below you can find a list of most used object notifications. + +You can find a list of all supported notifications in the [API Documentation](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Notifications.html). + +### Content, Media, and Member notifications + +
+ +ContentService Notifications + +The ContentService class is the most commonly used type when extending Umbraco using notifications. ContentService implements IContentService. It provides access to operations involving IContent. + +Below you can find a list of the most common ContentService object notifications. + +* [ContentSavingNotification](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Notifications.ContentSavingNotification.html) +* [ContentSavedNotification](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Notifications.ContentSavedNotification.html) +* [ContentPublishingNotification](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Notifications.ContentPublishingNotification.html) +* [ContentPublishedNotification](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Notifications.ContentPublishedNotification.html) +* [ContentUnpublishingNotification](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Notifications.ContentUnpublishingNotification.html) +* [ContentUnpublishedNotification](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Notifications.ContentUnpublishedNotification.html) +* [ContentCopyingNotification](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Notifications.ContentCopyingNotification.html) +* [ContentCopiedNotification](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Notifications.ContentCopiedNotification.html) +* [ContentMovingNotification](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Notifications.ContentMovingNotification.html) +* [ContentMovedNotification](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Notifications.ContentMovedNotification.html) +* [ContentMovingToRecycleBinNotification](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Notifications.ContentMovingToRecycleBinNotification.html) +* [ContentMovedToRecycleBinNotification](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Notifications.ContentMovedToRecycleBinNotification.html) +* [ContentDeletingNotification](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Notifications.ContentDeletingNotification.html) +* [ContentDeletedNotification](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Notifications.ContentDeletedNotification.html) +* [ContentDeletingVersionsNotification](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Notifications.ContentDeletingVersionsNotification.html) +* [ContentDeletedVersionsNotification](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Notifications.ContentDeletedVersionsNotification.html) +* [ContentRollingBackNotification](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Notifications.ContentRollingBackNotification.html) +* [ContentRolledBackNotification](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Notifications.ContentRolledBackNotification.html) +* [ContentSendingToPublishNotification](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Notifications.ContentSendingToPublishNotification.html) +* [ContentSentToPublishNotification](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Notifications.ContentSentToPublishNotification.html) +* [ContentEmptyingRecycleBinNotification](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Notifications.ContentEmptyingRecycleBinNotification.html) +* [ContentEmptiedRecycleBinNotification](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Notifications.ContentEmptiedRecycleBinNotification.html) +* [ContentSavedBlueprintNotification](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Notifications.ContentSavedBlueprintNotification.html) +* [ContentDeletedBlueprintNotification](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Notifications.ContentDeletedBlueprintNotification.html) + +
+ +
+ +MediaServiceNotifications + +Below you can find a list of the most common MediaService object notifications. + +* [MediaSavingNotification](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Notifications.MediaSavingNotification.html) +* [MediaSavedNotification](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Notifications.MediaSavedNotification.html) +* [MediaMovingNotification](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Notifications.MediaMovingNotification.html) +* [MediaMovedNotification](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Notifications.MediaMovedNotification.html) +* [MediaMovingToRecycleBinNotification](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Notifications.MediaMovingToRecycleBinNotification.html) +* [MediaMovedToRecycleBinNotification](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Notifications.MediaMovedToRecycleBinNotification.html) +* [MediaDeletingNotification](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Notifications.MediaDeletingNotification.html) +* [MediaDeletedNotification](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Notifications.MediaDeletedNotification.html) +* [MediaDeletingVersionsNotification](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Notifications.MediaDeletingVersionsNotification.html) +* [MediaDeletedVersionsNotification](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Notifications.MediaDeletedVersionsNotification.html) + +
+ +
+ +MemberService Notifications + +The MemberService implements IMemberService and provides access to operations involving IMember. + +Below you can find a list of the most common MemberService object notifications. + +* [MemberSavingNotification](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Notifications.MemberSavingNotification.html) +* [MemberSavedNotification](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Notifications.MemberSavedNotification.html) +* [MemberDeletingNotification](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Notifications.MemberDeletingNotification.html) +* [MemberDeletedNotification](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Notifications.MemberDeletedNotification.html) +* [AssignedMemberRolesNotification](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Notifications.AssignedMemberRolesNotification.html) +* [RemovedMemberRolesNotification](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Notifications.RemovedMemberRolesNotification.html) + +
+ +### Other notifications + +
+ +ContentTypeService Notifications + +The ContentTypeService class implements IContentTypeService. It provides access to operations involving IContentType. + +Below you can find a list of the most common ContentTypeService object notifications. + +* [ContentTypeSavingNotification](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Notifications.ContentTypeSavingNotification.html) +* [ContentTypeSavedNotification](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Notifications.ContentTypeSavedNotification.html) +* [ContentTypeDeletingNotification](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Notifications.ContentTypeDeletingNotification.html) +* [ContentTypeDeletedNotification](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Notifications.ContentTypeDeletedNotification.html) +* [ContentTypeMovingNotification](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Notifications.ContentTypeMovingNotification.html) +* [ContentTypeMovedNotification](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Notifications.ContentTypeMovedNotification.html) +* [ContentTypeChangedNotification](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Notifications.ContentTypeChangedNotification.html) + +
+ +
+ +MediaTypeService Notifications - object list + +The MediaTypeService class implements IMediaTypeService. It provides access to operations involving IMediaType. + +Below you can find a list of the most common MediaTypeService object notifications. + +* [MediaTypeSavingNotification](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Notifications.MediaTypeSavingNotification.html) +* [MediaTypeSavedNotification](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Notifications.MediaTypeSavedNotification.html) +* [MediaTypeDeletingNotification](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Notifications.MediaTypeDeletingNotification.html) +* [MediaTypeDeletedNotification](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Notifications.MediaTypeDeletedNotification.html) +* [MediaTypeMovingNotification](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Notifications.MediaTypeMovingNotification.html) +* [MediaTypeMovedNotification](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Notifications.MediaTypeMovedNotification.html) +* [MediaTypeChangedNotification](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Notifications.MediaTypeChangedNotification.html) + +
+ +
+ +MemberTypeService Notifications + +The MemberTypeService class implements IMemberTypeService. It provides access to operations involving IMemberType + +Below you can find a list of the most common MemberTypeService object notifications. + +* [MemberTypeSavingNotification](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Notifications.MemberTypeSavingNotification.html) +* [MemberTypeSavedNotification](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Notifications.MemberTypeSavedNotification.html) +* [MemberTypeDeletingNotification](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Notifications.MemberTypeDeletingNotification.html) +* [MemberTypeDeletedNotification](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Notifications.MemberTypeDeletedNotification.html) +* [MemberTypeMovingNotification](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Notifications.MemberTypeMovingNotification.html) +* [MemberTypeMovedNotification](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Notifications.MemberTypeMovedNotification.html) +* [MemberTypeChangedNotification](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Notifications.MemberTypeChangedNotification.html) + +
+ +
+ +DataTypeService Notifications + +The DataTypeService class implements IDataTypeService. It provides access to operations involving IDataType. + +Below you can find a list of the most common DataTypeService object notifications. + +* [DataTypeSavingNotification](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Notifications.DataTypeSavingNotification.html) +* [DataTypeSavedNotification](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Notifications.DataTypeSavedNotification.html) +* [DataTypeDeletingNotification](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Notifications.DataTypeDeletingNotification.html) +* [DataTypeDeletedNotification](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Notifications.DataTypeDeletedNotification.html) +* [DataTypeMovingNotification](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Notifications.DataTypeMovingNotification.html) +* [DataTypeMovedNotification](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Notifications.DataTypeMovedNotification.html) + +
+ +
+ +FileService Notifications + +The FileService class implements IFileService. It provides access to operations involving IFile objects like scripts, stylesheets and templates. + +Below you can find a list of the most common FileService object notifications. + +* [TemplateSavingNotification](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Notifications.TemplateSavingNotification.html) +* [TemplateSavedNotification](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Notifications.TemplateSavedNotification.html) +* [ScriptSavingNotification](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Notifications.ScriptSavingNotification.html) +* [ScriptSavedNotification](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Notifications.ScriptSavedNotification.html) +* [StylesheetSavingNotification](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Notifications.StylesheetSavingNotification.html) +* [StylesheetSavedNotification](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Notifications.StylesheetSavedNotification.html) +* [TemplateDeletingNotification](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Notifications.TemplateDeletingNotification.html) +* [TemplateDeletedNotification](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Notifications.TemplateDeletedNotification.html) +* [ScriptDeletingNotification](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Notifications.ScriptDeletingNotification.html) +* [ScriptDeletedNotification](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Notifications.ScriptDeletedNotification.html) +* [StylesheetDeletingNotification](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Notifications.StylesheetDeletingNotification.html) +* [StylesheetDeletedNotification](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Notifications.StylesheetDeletedNotification.html) + +
+ +
+ +LocalizationService Notifications + +The LocalizationService class implements ILocalizationService. It provides access to operations involving Language and DictionaryItem. + +Below you can find a list of the most common LocalizationService object notifications. + +* [LanguageSavingNotification](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Notifications.LanguageSavingNotification.html) +* [LanguageSavedNotification](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Notifications.LanguageSavedNotification.html) +* [DictionaryItemSavingNotification](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Notifications.DictionaryItemSavingNotification.html) +* [DictionaryItemSavedNotification](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Notifications.DictionaryItemSavedNotification.html) +* [LanguageDeletingNotification](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Notifications.LanguageDeletingNotification.html) +* [LanguageDeletedNotification](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Notifications.LanguageDeletedNotification.html) +* [DictionaryItemDeletingNotification](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Notifications.DictionaryItemDeletingNotification.html) +* [DictionaryItemDeletedNotification](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Notifications.DictionaryItemDeletedNotification.html) + +
+ +
+ +CacheRefresher Notifications + +Below you can find a list of the most common CacheRefresher object notifications. + +* [ContentCacheRefresherNotification](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Notifications.ContentCacheRefresherNotification.html) +* [MediaCacheRefresherNotification](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Notifications.MediaCacheRefresherNotification.html) +* [MemberCacheRefresherNotification](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Notifications.MemberCacheRefresherNotification.html) +* [UserCacheRefresherNotification](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Notifications.UserCacheRefresherNotification.html) + +
+ +
+ +RelationService Notifications + +Below you can find a list of the most common RelationService object notifications. + +The RelationService provides access to operations involving IRelation and IRelationType, and publishes the following relation notifications: + +* [RelationSavingNotification](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Notifications.RelationSavingNotification.html) +* [RelationSavedNotification](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Notifications.RelationSavedNotification.html) +* [RelationDeletingNotification](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Notifications.RelationDeletingNotification.html) +* [RelationDeletedNotification](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Notifications.RelationDeletedNotification.html) +* [RelationTypeSavingNotification](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Notifications.RelationTypeSavingNotification.html) +* [RelationTypeSavedNotification](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Notifications.RelationTypeSavedNotification.html) +* [RelationTypeDeletingNotification](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Notifications.RelationTypeDeletingNotification.html) +* [RelationTypeDeletedNotification](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Notifications.RelationTypeDeletedNotification.html) + +
+ +
+ +UmbracoApplicationLifetime Notifications + +Represents an Umbraco application lifetime (starting, started, stopping, stopped) notification. + +Below you can find a list of the most common UmbracoApplicationLifetime object notifications. + +* [UmbracoApplicationStartingNotification](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Notifications.UmbracoApplicationStartingNotification.html) +* [UmbracoApplicationStartedNotification](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Notifications.UmbracoApplicationStartedNotification.html) +* [UmbracoApplicationStoppingNotification](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Notifications.UmbracoApplicationStoppingNotification.html) +* [UmbracoApplicationStoppedNotification](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Notifications.UmbracoApplicationStoppedNotification.html) + +
+ +### Tree notifications + +Learn more about these under [Tree Change Notifications](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Notifications.TreeChangeNotification-1.html) in the CMS API Docs. + +### Editor Model Notifications + +See [EditorModel Notifications](editormodel-notifications/) for a list of the EditorModel events. + +{% hint style="info" %} +Useful for manipulating the model before it is sent to an editor in the backoffice. It could be used to set a default value of a property on a new document. +{% endhint %} + +## Creating and publishing your own custom notifications + +Umbraco uses notifications to allow people to hook into different workflow processes. This notification pattern is extensible, allowing you to create and publish custom notifications, and other people to observe and hook into your custom processes. This approach can be useful when creating Umbraco packages. For more information on how you create and publish your own notifications, see the [creating and publishing notifications](creating-and-publishing-notifications.md) article. + +## Samples + +Below you can find some articles with some examples using Notifications. + +* [CacheRefresher Notification](cacherefresher-notifications.md) +* [ContentService Notifications](contentservice-notifications.md) +* [Determining if an entity is new](determining-new-entity.md) +* [Hot vs. cold restarts](hot-vs-cold-restarts.md) +* [MediaService Notifications](mediaservice-notifications.md) +* [MemberService Notifications](memberservice-notifications.md) +* [Sending Allowed Children Notification](sendingallowedchildrennotifications.md) +* [Umbraco Application Lifetime Notifications](umbracoapplicationlifetime-notifications.md) diff --git a/16/umbraco-cms/reference/notifications/cacherefresher-notifications.md b/16/umbraco-cms/reference/notifications/cacherefresher-notifications.md new file mode 100644 index 00000000000..da54f65f7a6 --- /dev/null +++ b/16/umbraco-cms/reference/notifications/cacherefresher-notifications.md @@ -0,0 +1,111 @@ +--- +description: Example of how to use a CacheRefresher Notification +--- + +# CacheRefresher Notifications + +Before starting with cache refresher notifications it's a good idea to ensure you need to use them. If you want to react to changes in content, for instance, there's no real reason to use these notifications. This is due to the [content service notifications](contentservice-notifications.md) being easier to work with. If you need to react to changes in the cache, then these are the notifications for you. + +Cache refresher notifications are sent when the cache has refreshed. There are multiple different types of cache refresher notifications. These types are based on what type has been updated in the cache, for instance, content or media. All these notifications inherit from the same base notification: `CacheRefresherNotification`. + +The base notification is implemented in the following way: + +```csharp +public abstract class CacheRefresherNotification : INotification +{ + public CacheRefresherNotification(object messageObject, MessageType messageType) + { + MessageObject = messageObject ?? throw new ArgumentNullException(nameof(messageObject)); + MessageType = messageType; + } + + public object MessageObject { get; } + + public MessageType MessageType { get; } +} +``` + +As you can see this notification contains two properties, a `MessageObject` and a `MessageType`. The `MessageType` specifies what kind of cache operation was performed, for example `RemoveById`. The possible message types is as follows: + +```csharp +public enum MessageType +{ + RefreshAll, + RefreshById, + RefreshByJson, + RemoveById, + RefreshByInstance, + RemoveByInstance, + RefreshByPayload, +} +``` + +The other parameter `MessageObject` will depend on what type of cache refresher notification you're handling. If you for instance handle the `ContentCacheNotification`, the message object will be `ContentCacheRefresher.JsonPayload[]`. + +This object contains the Id and key of the item being updated, as well as an enum specifying how the tree is updated: + +```csharp +[Flags] +public enum TreeChangeTypes : byte +{ + None = 0, + + // all items have been refreshed + RefreshAll = 1, + + // an item node has been refreshed + // with only local impact + RefreshNode = 2, + + // an item node has been refreshed + // with branch impact + RefreshBranch = 4, + + // an item node has been removed + // never to return + Remove = 8, +} + +``` + +An example of working with the `ContentCacheNotification` can be seen here: + +```csharp +using Umbraco.Cms.Core.Cache; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Services.Changes; + +namespace Umbraco.Cms.Web.UI; + +public class ContentCacheRefresherExample : INotificationHandler +{ + private readonly IContentService _contentService; + + public ContentCacheRefresherExample(IContentService contentService) + { + _contentService = contentService; + } + + public void Handle(ContentCacheRefresherNotification notification) + { + if (notification.MessageObject is not ContentCacheRefresher.JsonPayload[] payloads) + { + return; + } + + foreach (ContentCacheRefresher.JsonPayload payload in payloads) + { + if (payload.ChangeTypes is not TreeChangeTypes.RefreshNode or TreeChangeTypes.RefreshBranch) + { + return; + } + + // You can do stuff with the ID of the refreshed content, for instance getting it from the content service. + var refeshedContent = _contentService.GetById(payload.Id); + } + } +} + +``` diff --git a/16/umbraco-cms/reference/notifications/contentservice-notifications.md b/16/umbraco-cms/reference/notifications/contentservice-notifications.md new file mode 100644 index 00000000000..b96e433fe7f --- /dev/null +++ b/16/umbraco-cms/reference/notifications/contentservice-notifications.md @@ -0,0 +1,224 @@ +--- +description: Find out more about ContentService Notifications and explore some example of how to use it +--- + +# ContentService Notifications + +The ContentService class is the most commonly used type when extending Umbraco using notifications. ContentService implements IContentService. It provides access to operations involving IContent. + +## Usage + +Example usage of the ContentPublishingNotification: + +```csharp +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Notifications; + +namespace Umbraco.Docs.Samples.Web.Notifications; + +public class DontShout : INotificationHandler +{ + public void Handle(ContentPublishingNotification notification) + { + foreach (var node in notification.PublishedEntities) + { + if (node.ContentType.Alias.Equals("announcement")) + { + var newsArticleTitle = node.GetValue("title"); + if (!string.IsNullOrWhiteSpace(newsArticleTitle) && newsArticleTitle.Equals(newsArticleTitle.ToUpper())) + { + notification.CancelOperation(new EventMessage("Corporate style guideline infringement", + "Don't put the announcement title in upper case, no need to shout!", + EventMessageType.Error)); + } + } + } + } +} +``` + +### Variants and Notifications + +Umbraco V8 introduced the concept of Variants for Document Types, initially to allow different language variants of particular properties within a Document Type to be edited/translated based on the languages configured in your instance of Umbraco. + +These variants can be saved, published, and unpublished independently of each other. (Unpublishing a 'mandatory language' variant of a content item - will trigger all culture variants to be unpublished). + +This poses a problem when handling notifications from the ContentService - for example which culture got published? Do I want to run my 'custom' code that fires on save if it's only the Spanish version that's been published? Also, if only the Spanish variant is 'unpublished' - that feels like a different situation than if 'all the variants' have been 'unpublished'. Depending on which event you are handling there are helper methods you can call to find out. + +#### Saving + +When handling the ContentSavingNotification which will be published whenever a variant is saved. You can tell 'which' variant has triggered the save using an extension method on the ContentSavingNotification called 'IsSavingCulture' + +```csharp +public bool IsSavingCulture(IContent content, string culture); +``` + +As an example, you could check which cultures are being saved (it could be multiple if multiple checkboxes are checked) + +```csharp +public void Handle(ContentSavingNotification notification) +{ + foreach (var entity in notification.SavedEntities) + { + // Cultures being saved + var savingCultures = entity.AvailableCultures + .Where(culture => notification.IsSavingCulture(entity, culture)).ToList(); + // or + if (notification.IsSavingCulture(entity, "en-GB")) + { + // Do things differently if the UK version of the page is being saved. + } + } +} +``` + +#### Saved + +With the Saved notification you can similarly use the 'HasSavedCulture' method of the 'ContentSavedNotification' to detect which culture caused the Save. + +```csharp +public bool HasSavedCulture(IContent content, string culture); +``` + +#### Unpublishing + +When handling the Unpublishing notification, it might not work how you would expect. If 'all the variants' are being unpublished at the same time (or the mandatory language is being unpublished, which forces this to occur) then the Unpublishing notification will be published as expected. + +```csharp +public void Handle(ContentUnpublishingNotification notification) +{ + foreach (var unPublishedEntity in notification.UnpublishedEntities) + { + // complete unpublishing of entity, all cultures + } +} +``` + +However, if only one variant is being unpublished, the Unpublishing event will not be triggered. This is because the content item itself is not fully 'unpublished' by the action. Instead, what occurs is a 'publish' action 'without' the unpublished variant. + +You can therefore detect the Unpublishing of a variant in the publishing notification - using the IsUnpublishingCulture extension method of the `ContentPublishingNotification` + +```csharp +public void Handle(ContentPublishingNotification notification) +{ + foreach (var node in notification.PublishedEntities) + { + if (notification.IsUnpublishingCulture(node, "da-DK")) + { + // Bye bye DK! + } + } +} +``` + +#### Unpublished + +Again, the Unpublished notification does not get published when a single variant is Unpublished, instead, the Published notification can be used, and the 'HasUnpublishedCulture' extension method of the ContentPublishedNotification can determine which variant being unpublished triggered the publish. + +```csharp +public bool HasUnpublishedCulture(IContent content, string culture); +``` + +#### Publishing + +When handling the ContentPublishingNotification which will be triggered whenever a variant is published (or unpublished - see note in the Unpublishing section above). + +You can tell 'which' variant has triggered the publish using a helper method on the ContentPublishingNotification called IsPublishingCulture. + +```csharp +public bool IsPublishingCulture(IContent content, string culture); +``` + +For example, you could check which cultures are being published and act accordingly (it could be multiple if multiple checkboxes are checked). + +```csharp +public void Handle(ContentPublishingNotification notification) +{ + foreach (var node in notification.PublishedEntities) + { + var publishingCultures = node.AvailableCultures + .Where(culture => notification.IsPublishingCulture(node, culture)).ToList(); + + var unPublishingCultures = node.AvailableCultures + .Where(culture => notification.IsUnpublishingCulture(node, culture)).ToList(); + // or + if (notification.IsPublishingCulture(node, "da-DK")) + { + // Welcome back DK! + } + } +} +``` + +#### Published + +In the Published notification you can similarly use the HasPublishedCulture and HasUnpublishedCulture methods of the 'ContentPublishedEventArgs' to detect which culture caused the Publish or the UnPublish if it was only a single non-mandatory variant that was unpublished. + +```csharp +public bool HasPublishedCulture(IContent content, string culture); +public bool HasUnpublishedCulture(ICotnent content, string culture); +``` + +#### IContent Helpers + +In each of these notifications, the entities being Saved, Published, and Unpublished are `IContent` entities. There are some useful helper methods on IContent to discover the status of the content item's variant cultures: + +```csharp +bool IsCultureAvailable(string culture); +bool IsCultureEdited(string culture); +bool IsCulturePublished(string culture); +``` + +
+ +What happened to Creating and Created events? + +Both the ContentService.Creating and ContentService.Created events were removed, and therefore never moved to notifications. Why? Because these events were not guaranteed to trigger and therefore should not be used. This is because these events would only trigger when the ContentService.CreateContent method was used which is an entirely optional way to create content entities. It is also possible to construct a new content item - which is generally the preferred and consistent way - and therefore the Creating/Created events would not execute when constructing content that way. + +Furthermore, there was no reason to listen to the Creating/Created events. They were misleading since they didn't trigger before and after the entity persisted. They are triggered inside the CreateContent method which never persists the entity, it constructs a new content object. + +**What do we use instead?** + +The ContentSavingNotification and ContentSavedNotification will always be published before and after an entity has been persisted. You can determine if an entity is brand new in either of those notifications. In the Saving notification - before the entity is persisted - you can check the entity's HasIdentity property which will be 'false' if it is brand new. In the Saved notification you can [check to see if the entity 'remembers being dirty'](determining-new-entity.md) + +
+ +
+ +What happened to raiseEventmethod parameters? + +RaiseEvent method service parameters have been removed from v9 and to name some reasons why: + +- Because it's entirely inconsistent, not all services have this as method parameters and maintaining that consistency is impossible especially if 3rd party libraries support events/notifications. +- It's hacky. There's no good way to suppress events/notifications this way at a higher (scoped) level. +- There's also hard-coded logic to ignore these parameters sometimes which makes it even more inconsistent. +- There are events below services at the repository level that cannot be controlled by this flag. + +**What do we use instead?** + +We can suppress notifications at the scope level which makes things consistent and will work for all services that use a Scope. Also, there's no required maintenance to make sure that new service methods will also work. + +**How to use scopes**: + +- Create an explicit scope and call scope.Notifications.Suppress(). +- The result of Suppress() is IDisposable, so until it is disposed, notifications will not be added to the queue. + +[Example](https://github.com/umbraco/Umbraco-CMS/blob/b69afe81f3f6fcd37480b3b0295a62af44ede245/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Scoping/SupressNotificationsTests.cs#L35): + +```csharp +using (IScope scope = ScopeProvider.CreateScope(autoComplete: true)) +using (IDisposable _ = scope.Notifications.Suppress()) +{ + // TODO: Calls to service methods here will not have notifications +} +``` + +Child scope will inherit the parent Scope's notification object which means if a parent scope has notifications suppressed, then so does the child scope. You cannot call Suppress() more than once for the same outer scope instance else an exception will be thrown. This ensures that you cannot un-suppress notifications at a child level for an outer scope. It also ensures that suppressing events is an explicit thing to do. + +**Why would one want to suppress events?** + +The main reason for ever doing this would be performance for bulk operations. The callers hould be aware that suppressing events will lead to an inconsistent content cache state (if notifications are suppressed for content or media services). This is because notifications are used by the Published Content Cache to populate the `cmsContentNu` table and populate the content caches. They are also used to populate the Examine indexes. + +So if you did suppress events, it will require you to rebuild the Published Content Cache and examine data manually. + +
diff --git a/16/umbraco-cms/reference/notifications/creating-and-publishing-notifications.md b/16/umbraco-cms/reference/notifications/creating-and-publishing-notifications.md new file mode 100644 index 00000000000..746ccd28b17 --- /dev/null +++ b/16/umbraco-cms/reference/notifications/creating-and-publishing-notifications.md @@ -0,0 +1,106 @@ +--- +description: How to create and publish your own custom notifications +--- + +# Creating And Publishing Notifications + +## Creating And Publishing Custom Notifications + +There may be many reasons why you would like to create your own custom notifications, in this article we'll use the CleanUpYourRoom [recurring hosted service](../scheduling.md) as an example, which empties the recycle bin every 5 minutes. You might want to publish a notification once the task has started, and maybe once the task has successfully cleared the recycle bin. + +For a notification to be publishable there's only one requirement, it must implement the empty marker interface `INotification`, the rest is up to you. For instance, we might want to create a notification that just signals that the clean your room task has started and nothing else, in this case, we'll create an empty class implementing `INotification` + +```csharp +using Umbraco.Cms.Core.Notifications; + +namespace Umbraco.Docs.Samples.Web.RecurringBackgroundJobs; + +public class CleanYourRoomStartedNotification : INotification +{ + +} +``` + +This notification can now be published, and we can create a notification handler to receive it with, see [MediaService-Notifications](mediaservice-notifications.md) for an example of how to implement a notification handler. But this notification alone might not be super helpful, we might want to be able to send some additional information with the notification, however, since this is, in essence, just a normal class, we can include whatever information we want. Let's try and create a `RoomCleanedNotification` which contains the number of nodes removed from the recycle bin: + +```csharp +using Umbraco.Cms.Core.Notifications; + +namespace Umbraco.Docs.Samples.Web.RecurringBackgroundJobs; + +public class RoomCleanedNotification : INotification +{ + public int ItemsDeleted { get; } + + public RoomCleanedNotification(int itemsDeleted) + { + ItemsDeleted = itemsDeleted; + } +} +``` + +Now you can create a handler that receives the amount of items deleted through the notification. + +## Sending notifications + +Just creating the notification classes is not enough, we also want to be able to publish them. There's two ways of publishing notifications: + +- `IEventAggregator` - Notifications published with `IEventAggregator` will always be published immediately. +- `IScope.Notifications` - Notifications published with a scope will only be published once the scope has been completed and disposed. + +The method you use to publish notifications depends on what your needs are, the benefits of publishing notifications with a scope is that the notification will only be published if you complete the scope, and then only once the scope is disposed of. This can be useful if you access the database, or do some other operation that might fail causing you to do a rollback, disposing of the scope without completing it, in this case, you might not want to publish a notification that signals that the operation was a success, using scopes will handle this for you. On the other hand, you might want to publish the notification immediately no matter what, for instance with the `CleanYourRoomStartedNotification`, for this, the `IEventAggregator` is the right choice. + +### Example + +```csharp +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Scoping; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Infrastructure.BackgroundJobs; + +namespace Umbraco.Docs.Samples.Web.RecurringBackgroundJobs; + +public class CleanUpYourRoom : IRecurringBackgroundJob +{ + public TimeSpan Period { get; } = TimeSpan.FromMinutes(5); + public event EventHandler? PeriodChanged { add {} remove {} } + + private readonly IContentService _contentService; + private readonly ICoreScopeProvider _coreScopeProvider; + private readonly IEventAggregator _eventAggregator; + + public CleanUpYourRoom( + IContentService contentService, + ICoreScopeProvider coreScopeProvider, + IEventAggregator eventAggregator, + ILogger logger) + { + _contentService = contentService; + _coreScopeProvider = coreScopeProvider; + _eventAggregator = eventAggregator; + } + + public Task RunJobAsync() + { + // This will be published immediately + _eventAggregator.Publish(new CleanYourRoomStartedNotification()); + + using ICoreScope scope = _coreScopeProvider.CreateCoreScope(); + int numberOfThingsInBin = _contentService.CountChildren(Constants.System.RecycleBinContent); + + if (_contentService.RecycleBinSmells()) + { + _contentService.EmptyRecycleBin(userId: -1); + // This will only be published when the scope is completed and disposed. + scope.Notifications.Publish(new RoomCleanedNotification(numberOfThingsInBin)); + } + + // Remember to complete the scope when done. + scope.Complete(); + return Task.CompletedTask; + } +} +``` + +In this case, the `CleanYourRoomStartedNotification` will always be published immediately, however, `RoomCleanedNotification` will only be published once the operation is done, and if you remove the `scope.Complete();` line it will never be published, the recycle bin won't be emptied either. diff --git a/16/umbraco-cms/reference/notifications/determining-new-entity.md b/16/umbraco-cms/reference/notifications/determining-new-entity.md new file mode 100644 index 00000000000..8ed1b295756 --- /dev/null +++ b/16/umbraco-cms/reference/notifications/determining-new-entity.md @@ -0,0 +1,30 @@ +--- +description: Example of how to determine if an entity is new +--- + +# Determining if an entity is new + +Many of the Umbraco services publishes a 'Saved' notification (or similar). In some cases, it is beneficial to know if this entity is a brand new entity that has been persisted in the database. This is how you can determine this. + +## Checking if it's new + +We know that if an entity is new and hasn't been persisted that it will not have an ID. Therefore we know if an entity has been newly persisted to the database by checking if its ID was changed before being persisted. + +Here's the snippet of code that does that: + +```csharp +var dirty = (IRememberBeingDirty)entity; +var isNew = dirty.WasPropertyDirty("Id"); +``` + +To check if an entity is new in the ContentSavingNotification use the following: + +```csharp +var isNew = entity.HasIdentity is false; +``` + +Since the IContent has not been saved yet, it's not necessary to cast it to `IRememberBeingDirty`. It won't have an identity if it's new, since it hasn't been committed yet. + +## How it works + +This is all possible because of the `IRememberBeingDirty` interface. Indeed the name of this interface is hilarious but it describes exactly what it does. All entities implement this interface which is really handy. It tracks not only the property data that has changed because it inherits from yet another hilarious interface called `ICanBeDirty`. It also tracks the property data that was changed before it was committed. diff --git a/16/umbraco-cms/reference/notifications/editormodel-notifications/README.md b/16/umbraco-cms/reference/notifications/editormodel-notifications/README.md new file mode 100644 index 00000000000..23dc06c26aa --- /dev/null +++ b/16/umbraco-cms/reference/notifications/editormodel-notifications/README.md @@ -0,0 +1,168 @@ +# EditorModel Notifications + +{% hint style="warning" %} +This page is a work in progress and may undergo further revisions, updates, or amendments. The information contained herein is subject to change without notice. + +EditorModel notifications are no longer handled on the server-side, but on the client-side. As the documentation effort progresses, the samples on this page will be updated accordingly. +{% endhint %} + +EditorModel notifications enable you to manipulate the model used by the backoffice before it is loaded into an editor. For example the `SendingContentNotification` is published right before a content item is loaded into the backoffice for editing. It is therefore the perfect notification to use to set a default value for a particular property, or perhaps to hide a property/tab/Content App from a certain editor. + +## Usage + +Example usage of the `SendingContentNotification` - e.g. set the default PublishDate for a new NewsArticle to be today's Date: + +{% code overflow="wrap" lineNumbers="true" fullWidth="false" %} +```csharp +using System; +using System.Linq; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models.ContentEditing; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Extensions; + +namespace Umbraco.Docs.Samples.Web.Notifications; + +public class EditorSendingContentNotificationHandler : INotificationHandler +{ + public void Handle(SendingContentNotification notification) + { + if (notification.Content.ContentTypeAlias.Equals("blogpost")) + { + // Access the property you want to pre-populate + // each content item can have 'variations' - each variation is represented by the `ContentVariantDisplay` class. + // if your site uses variants, then you need to decide whether to set the default value for all variants or a specific variant + // eg. set by variant name: + // var variant = notification.Content.Variants.FirstOrDefault(f => f.Name == "specificVariantName"); + // OR loop through all the variants: + foreach (var variant in notification.Content.Variants) + { + // Check if variant is a 'new variant' + // we only want to set the default value when the content item is first created + if (variant.State == ContentSavedState.NotCreated) + { + // each variant has an IEnumerable of 'Tabs' (property groupings) + // and each of these contain an IEnumerable of `ContentPropertyDisplay` properties + // find the first property with alias 'publishDate' + var pubDateProperty = variant.Tabs.SelectMany(f => f.Properties) + .FirstOrDefault(f => f.Alias.InvariantEquals("publishDate")); + if (pubDateProperty is not null) + { + // set default value of the publish date property if it exists + pubDateProperty.Value = DateTime.UtcNow; + } + } + } + } + } +} +``` +{% endcode %} + +Another example could be to set the default Member Group for a specific Member Type using `SendingMemberNotification`: + +```csharp +using System.Collections.Generic; +using System.Linq; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; + +namespace Umbraco.Docs.Samples.Web.Notifications; + +public class EditorSendingMemberNotificationHandler : INotificationHandler +{ + private readonly IMemberGroupService _memberGroupService; + + public EditorSendingMemberNotificationHandler(IMemberGroupService memberGroupService) + { + _memberGroupService = memberGroupService; + } + + public void Handle(SendingMemberNotification notification) + { + var isNew = !int.TryParse(notification.Member.Id?.ToString(), out int id) || id == 0; + + // We only want to set the default member group when the member is initially created, eg doesn't have an Id yet + if (isNew is false) + { + return; + } + + // Set a default value member group for the member type `Member` + if (notification.Member.ContentTypeAlias.Equals("Member")) + { + var memberGroup = _memberGroupService.GetByName("Customer"); + if (memberGroup is null) + { + return; + } + + // Find member group property on member model + var property = notification.Member.MembershipProperties.FirstOrDefault(x => + x.Alias.Equals($"{Constants.PropertyEditors.InternalGenericPropertiesPrefix}membergroup")); + + if (property is not null) + { + // Assign a default value for member group property + property.Value = new Dictionary + { + {memberGroup.Name, true} + }; + } + } + } +} +``` + +## Notifications + +| Notification | Members | Description | +| ---------------------------------- | -------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| SendingContentNotification |
  • ContentItemDisplay Content
  • IUmbracoContext UmbracoContext
|

Published right before the editor model is sent for editing in the content section.
NOTE: Content is a Umbraco.Cms.Core.Models.ContentEditing.ContentItemDisplay type which contains the tabs and properties of the elements about to be loaded for editing.

| +| SendingMediaNotification |
  • MediaItemDisplay Media
  • IUmbracoContext UmbracoContext
|

Published right before the editor model is sent for editing in the media section
NOTE: Media is a Umbraco.Cms.Core.Models.ContentEditing.MediaItemDisplay type which in turn contains the tabs and properties of the elements about to be loaded for editing.

| +| SendingMemberNotification |
  • MemberDisplay Member
  • IUmbracoContext UmbracoContext
|

Published right before the editor model is sent for editing in the member section.
NOTE: Member is a Umbraco.Cms.Core.Models.ContentEditing.MemberDisplay type which in turn contains the tabs and properties of the elements about to be loaded for editing.

| +| SendingUserNotification |
  • UserDisplay User
  • IUmbracoContext UmbracoContext
|

Published right before the editor model is sent for editing in the user section.
NOTE: User is a Umbraco.Cms.Core.Models.ContentEditing.UserDisplay type which in turn contains the tabs and properties of the elements about to be loaded for editing.

| +| SendingDashboardsNotification |
  • IEnumerable<Tab<IDashboardSlim>> Dashboards
  • IUmbracoContext UmbracoContext
|

Published right before the a dashboard is retrieved in a section.
NOTE: Dashboards is a collection of IDashboardSlim, each object gives you access to Label, Alias, Properties, whether it's expanded, and whether it IsActive.

| +| SendingAllowedChildrenNotification |
  • IEnumerable<ContentTypeBasic> Children
  • IUmbracoContext UmbracoContext
|

Published right before the allowed children of the selected Content Type are sent back during content creation in the Content Section.
NOTE: Children is a collection of ContentTypeBasic, each object gives you access to Alias, Description, Thumbnail and more. You can remove or add new children to the list in the notification.

| + +### Display models + +#### ContentItemDisplay + +A model representing a content item to be displayed in the backoffice + +* TemplateAlias +* Urls +* AllowPreview - Determines whether previewing is allowed for this node, By default this is true but by using notifications developers can toggle this off for certain documents if there is nothing to preview +* AllowedActions - The allowed 'actions' based on the user's permissions - Create, Update, Publish, Send to publish +* IsBlueprint +* Tabs - Defines the tabs containing display properties +* Properties - properties based on the properties in the tabs collection +* And more... + +#### MediaItemDisplay + +A model representing a media item to be displayed in the backoffice + +* Alias +* Tabs - Defines the tabs containing display properties +* Properties - properties based on the properties in the tabs collection +* And more... + +#### MemberDisplay + +A model representing a member to be displayed in the backoffice + +* Username +* Email +* Tabs - Defines the tabs containing display properties +* Properties - properties based on the properties in the tabs collection +* And more... + +## Samples + +The EditorModel notifications gives you a lot of options to customize the backoffice experience. You can find inspiration from the various samples provided below: + +* [Customizing the "Links" box](customizing-the-links-box.md) diff --git a/16/umbraco-cms/reference/notifications/editormodel-notifications/customizing-the-links-box.md b/16/umbraco-cms/reference/notifications/editormodel-notifications/customizing-the-links-box.md new file mode 100644 index 00000000000..359db99af67 --- /dev/null +++ b/16/umbraco-cms/reference/notifications/editormodel-notifications/customizing-the-links-box.md @@ -0,0 +1,39 @@ +# Customizing the "Links" box + +For a content item, Umbraco will show a **Links** box within the **Info** content app. By default, this box will show one or more links to content item. + +![Links Box](images/properties-info-app-v14.png) + +With the `SendingContentNotification` event, we can manipulate the links in the `Urls` property. This could be by replacing it with custom links although a URL provider would be more suitable: + +```csharp +public void Handle(SendingContentNotification notification) +{ + notification.Content.Urls = new[] + { + new UrlInfo($"/products/?id={notification.Content.Id}", true, null) + }; +} +``` + +If the content item has multiple cultures, we can specify the link culture like this: + +```csharp +public void Handle(SendingContentNotification notification) +{ + notification.Content.Urls = new[] + { + new UrlInfo($"https://mysite.com/products/?id={notification.Content.Id}", true, "en-US"), + new UrlInfo($"https://mysite.dk/produkter/?id={notification.Content.Id}", true, "da-DK") + }; +} +``` + +or remove the box entirely by providing an empty list of links: + +```csharp +public void Handle(SendingContentNotification notification) +{ + notification.Content.Urls = null; +} +``` diff --git a/16/umbraco-cms/reference/notifications/editormodel-notifications/images/properties-info-app-v14.png b/16/umbraco-cms/reference/notifications/editormodel-notifications/images/properties-info-app-v14.png new file mode 100644 index 00000000000..e9525c9a306 Binary files /dev/null and b/16/umbraco-cms/reference/notifications/editormodel-notifications/images/properties-info-app-v14.png differ diff --git a/16/umbraco-cms/reference/notifications/editormodel-notifications/images/properties-info-app.png b/16/umbraco-cms/reference/notifications/editormodel-notifications/images/properties-info-app.png new file mode 100644 index 00000000000..3af83b41b26 Binary files /dev/null and b/16/umbraco-cms/reference/notifications/editormodel-notifications/images/properties-info-app.png differ diff --git a/16/umbraco-cms/reference/notifications/hot-vs-cold-restarts.md b/16/umbraco-cms/reference/notifications/hot-vs-cold-restarts.md new file mode 100644 index 00000000000..583f11e2bda --- /dev/null +++ b/16/umbraco-cms/reference/notifications/hot-vs-cold-restarts.md @@ -0,0 +1,42 @@ +--- +description: When rebooting an Umbraco CMS website it is common to distinguish between hot and cold restarts depending on your setup. +--- + +# Hot vs. cold restarts + +The load time of your site is dependent on a few different things. When talking about hot vs. cold restarts of your Umbraco CMS website it comes down to whether the search indexes need to be rebuilt. + +* **Cold restart**: When the search indexes need to rebuild. +* **Hot start**: When the search indexes do not need to rebuild. + +## Hot start + +When no cache needs to be rebuild, the restart of your site is generally faster. This is why it is referred to as a **hot restart**. + +## Cold start + +The **cold restart** expression is generally used for when the search indexes need to rebuild. This will resolve in a slower startup time, depending on the amount of content on the site. Generally speaking, the more content you have the longer a cold boot will take. + +## Troubleshooting slow startup + +Different things could be in play when your site is slow to startup after a reboot/restart. + +Below is a list of some of the more common reasons: + +* The search indexes need to rebuild (cold restart). +* Examine indexes need to rebuild - for large sites, this can take some time. +* The custom code on the website is not optimized and does not live up to .NET standards. + +## In Memory Auto + +Another factor that can slow down time to first page load is the [In Memory Auto models builder](../templating/modelsbuilder/builder-modes.md#in-memory) setting. Having this setting enabled will result in the first page load being slower. The reason for this is that when the first page is requested, the strongly typed models needs to be compiled and loaded in. + +This is, however, less noticeable on consecutive restarts, a bit like hot and cold restarts. Since the compiled models will be cached on disk, they don't need to be recompiled until the models change again. + +## Legacy Umbraco + +In earlier versions of Umbraco, the difference between hot and cold restarts was more distinct as more components were involved with this process. + +[Learn more about the different restart processes in the legacy documentation](https://our.umbraco.com/documentation/Reference/Events/Hot-vs-Cold-restarts). + +The process for [troubleshooting slow startup times is also slightly different for earlier version of Umbraco](https://our.umbraco.com/documentation/Reference/Events/Troubleshooting-Slow-Startup). diff --git a/16/umbraco-cms/reference/notifications/mediaservice-notifications.md b/16/umbraco-cms/reference/notifications/mediaservice-notifications.md new file mode 100644 index 00000000000..81678ccd649 --- /dev/null +++ b/16/umbraco-cms/reference/notifications/mediaservice-notifications.md @@ -0,0 +1,135 @@ +--- +description: Example of how to use a MediaService Notification +--- + +# MediaService Notifications + +The MediaService class implements IMediaService. It provides access to operations involving IMedia. + +## Usage + +Example usage of the MediaService notifications: + +```csharp +using Microsoft.Extensions.Logging; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Notifications; + +namespace MySite; + +public class MediaNotificationHandler : INotificationHandler +{ + private readonly ILogger _logger; + + public MediaNotificationHandler(ILogger logger) + { + _logger = logger; + } + + public void Handle(MediaSavedNotification notification) + { + foreach (var mediaItem in notification.SavedEntities) + { + if (mediaItem.ContentType.Alias.Equals("Image")) + { + // Do something with the image, maybe send to Azure for AI analysis of image contents or something. + _logger.LogDebug($"Sending {mediaItem.Name} to analysis"); + SendToAzure(mediaItem); + } + } + } +} +``` + +## Returning messages to the user + +You can return a custom message to the user. Use this to show information, a warning or maybe an error. +This is achieved using the ```Messages``` property of the notification and a composer. + +### Example + +This example returns an informational message to the user when a Media item is saved. + +```csharp +using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Notifications; + +namespace MyProject +{ + public class CustomComposer : IComposer + { + public void Compose(IUmbracoBuilder builder) + { + builder.AddNotificationHandler(); + } + } + + public class MediaNotificationTest : INotificationHandler + { + public void Handle(MediaSavedNotification notification) + { + notification.Messages.Add(new EventMessage( + "Notification", + "You can return a message to the user, using the messages property on the notification.", + EventMessageType.Info)); + } + } +} +``` + +![image](https://github.com/umbraco/UmbracoDocs/assets/6904597/67696298-2710-4aeb-bd0a-33c6d8414216) + +
+ +What happened to Creating and Created events? + +Both the MediaService.Creating and MediaService.Created events have been obsoleted. Because of this, these were not moved over to notifications, and no longer exist. Why? Because these events were not guaranteed to trigger and therefore should not have been used. This is because these events *only* triggered when the MediaService.CreateMedia method was used which is an entirely optional way to create media entities. It is also possible to construct a new media item - which is generally the preferred and consistent way - and therefore the Creating/Created events would not execute when constructing media that way. + +Furthermore, there was no reason to listen for the Creating/Created events because they were misleading. They didn't trigger before and after the entity had been persisted. Instead they triggered inside the CreateMedia method which never persists the entity. It constructs a new media object. + +**What do we use instead?** + +The MediaSavingNotification and MediaSavedNotification will always be published before and after an entity has been persisted. You can determine if an entity is brand new with either of those notifications. With the Saving notification - before the entity is persisted - you can check the entity's HasIdentity property which will be 'false' if it is brand new. In the Saved event you can [check to see if the entity 'remembers being dirty'](determining-new-entity.md) + +
+ +
+ +What happened to raiseEventmethod parameters? + +RaiseEvent method service parameters have been removed from v9 and to name some reasons why: + +- Because it's entirely inconsistent, not all services have this as method parameters and maintaining that consistency is impossible especially if 3rd party libraries support events/notifications. +- It's hacky. There's no good way to suppress events/notifications this way at a higher (scoped) level. +- There's also hard-coded logic to ignore these parameters sometimes which makes it even more inconsistent. +- There are events below services at the repository level that cannot be controlled by this flag. + +**What do we use instead?** + +We can suppress notifications at the scope level which makes things consistent and will work for all services that use a Scope. Also, there's no required maintenance to make sure that new service methods will also work. + +**How to use scopes**: + +- Create an explicit scope and call scope.Notifications.Suppress(). +- The result of Suppress() is IDisposable, so until it is disposed, notifications will not be added to the queue. + +[Example](https://github.com/umbraco/Umbraco-CMS/blob/b69afe81f3f6fcd37480b3b0295a62af44ede245/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Scoping/SupressNotificationsTests.cs#L35): + +```csharp +using (IScope scope = ScopeProvider.CreateScope(autoComplete: true)) +using (IDisposable _ = scope.Notifications.Suppress()) +{ + // TODO: Calls to service methods here will not have notifications +} +``` + +Child scope will inherit the parent Scope's notification object which means if a parent scope has notifications suppressed, then so does the child scope. You cannot call Suppress() more than once for the same outer scope instance else an exception will be thrown. This ensures that you cannot un-suppress notifications at a child level for an outer scope. It also ensures that suppressing events is an explicit thing to do. + +**Why would one want to suppress events?** + +The main reason for ever doing this would be performance for bulk operations. The callers hould be aware that suppressing events will lead to an inconsistent content cache state (if notifications are suppressed for content or media services). This is because notifications are used by the Published Content Cache to populate the `cmsContentNu` table and populate the content caches. They are also used to populate the Examine indexes. + +So if you did suppress events, it will require you to rebuild the Published Content Cache and examine data manually. + +
diff --git a/16/umbraco-cms/reference/notifications/memberservice-notifications.md b/16/umbraco-cms/reference/notifications/memberservice-notifications.md new file mode 100644 index 00000000000..a1f5f1a87ec --- /dev/null +++ b/16/umbraco-cms/reference/notifications/memberservice-notifications.md @@ -0,0 +1,76 @@ +--- +description: Example of how to use a MemberService Notification +--- + +# MemberService Notifications + +The MemberService implements IMemberService and provides access to operations involving IMember. + +## Usage + +```csharp +using Microsoft.Extensions.Logging; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Notifications; + +namespace MySite; + +public class MemberNotificationHandler : INotificationHandler +{ + private readonly ILogger _logger; + + public MemberNotificationHandler(ILogger logger) + { + _logger = logger; + } + + public void Handle(MemberSavedNotification notification) + { + foreach (var member in notification.SavedEntities) + { + // Write to the logs every time a member is saved. + _logger.LogInformation("Member {member} has been saved and notification published!", member.Name); + } + } +} +``` + +
+ +What happened to raiseEventmethod parameters? + +RaiseEvent method service parameters have been removed from v9 and to name some reasons why: + +- Because it's entirely inconsistent, not all services have this as method parameters and maintaining that consistency is impossible especially if 3rd party libraries support events/notifications. +- It's hacky. There's no good way to suppress events/notifications this way at a higher (scoped) level. +- There's also hard-coded logic to ignore these parameters sometimes which makes it even more inconsistent. +- There are events below services at the repository level that cannot be controlled by this flag. + +**What do we use instead?** + +We can suppress notifications at the scope level which makes things consistent and will work for all services that use a Scope. Also, there's no required maintenance to make sure that new service methods will also work. + +**How to use scopes**: + +- Create an explicit scope and call scope.Notifications.Suppress(). +- The result of Suppress() is IDisposable, so until it is disposed, notifications will not be added to the queue. + +[Example](https://github.com/umbraco/Umbraco-CMS/blob/b69afe81f3f6fcd37480b3b0295a62af44ede245/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Scoping/SupressNotificationsTests.cs#L35): + +```csharp +using (IScope scope = ScopeProvider.CreateScope(autoComplete: true)) +using (IDisposable _ = scope.Notifications.Suppress()) +{ + // TODO: Calls to service methods here will not have notifications +} +``` + +Child scope will inherit the parent Scope's notification object which means if a parent scope has notifications suppressed, then so does the child scope. You cannot call Suppress() more than once for the same outer scope instance else an exception will be thrown. This ensures that you cannot un-suppress notifications at a child level for an outer scope. It also ensures that suppressing events is an explicit thing to do. + +**Why would one want to suppress events?** + +The main reason for ever doing this would be performance for bulk operations. The callers hould be aware that suppressing events will lead to an inconsistent content cache state (if notifications are suppressed for content or media services). This is because notifications are used by the Published Content Cache to populate the `cmsContentNu` table and populate the content caches. They are also used to populate the Examine indexes. + +So if you did suppress events, it will require you to rebuild the Published Content Cache and examine data manually. + +
diff --git a/16/umbraco-cms/reference/notifications/notification-handler.md b/16/umbraco-cms/reference/notifications/notification-handler.md new file mode 100644 index 00000000000..8877fe1ce06 --- /dev/null +++ b/16/umbraco-cms/reference/notifications/notification-handler.md @@ -0,0 +1,131 @@ +--- +description: Learn about notification handlers lifetime, async notification handler and how to register the notification handlers. +--- + +# Notification handlers lifetime + +It's important to know that the handlers you create and register to receive notifications will be **transient**. This means that they will be initialized every time they receive a notification. You can therefore not rely on them having a specific state based on previous notifications. + +As an example, you cannot do the following: + +1. Create a list in a handler. +2. Add something when a notification is received. +3. Check if that list contains what you added in an earlier notification. + +When following the steps above, the list will always be empty because the object has only been initialized. + +If you need persistence between notifications, we recommend you move that functionality into a service or similar. You can then register it with the DI container, and inject it into your handler. + +As previously mentioned, many notifications exist in pairs, with a "before" and "after" notification. There may be cases where you want to add some information to the "before" notification, which will then be available to your "after" notification handler. In order to support this, the notification "pairs" are **stateful**. This means the notifications contain a dictionary that is shared between the "before" and "after" notifications. You can add values to the dictionary, and later retrieve them like this: + +```csharp +public void Handle(TemplateSavingNotification notification) +{ + notification.State["SomeKey"] = "Some Value Relevant to the \"after\" notification handler"; +} + + +public void Handle(TemplateSavedNotification notification) +{ + var valueFromSaving = notification.State["SomeKey"]; +} +``` + +## Registering notification handlers + +Once you have made your notification handlers, you need to register them with the `AddNotificationHandler` extension method on the `IUmbracoBuilder`. This enables them to run whenever a notification they subscribe to is published. There are two ways to do this: + +1. In the **Program** class, if you're making handlers for your site +2. In a **composer**, if you're a package developer subscribing to notifications + +{% hint style="info" %} +Learn more about the different ways of registering your handlers and other extensions in the [Dependency Injection](../using-ioc.md) article. +{% endhint %} + +### Registering notification handlers in the program class + +In the `Program.cs` file, register your notification in the `CreateUmbracoBuilder()` builder chain: + +```csharp +builder.CreateUmbracoBuilder() + .AddBackOffice() + .AddWebsite() + .AddDeliveryApi() + .AddComposers() + // Add your extension methods here, before Build(). + .AddNotificationHandler() + .Build(); +``` + +The extension method takes two generic type parameters. The first, `ContentPublishingNotification`, is the notification you wish to subscribe to. The second, `DontShout`, is the class that handles the notification. This class must implement `INotificationHandler<>` with the type of notification it handles as the generic type parameter. In this case, the `DontShout` class definition looks like this: + +```csharp +public class DontShout : INotificationHandler +``` + +For the full handler implementation, see [ContentService Notifications](contentservice-notifications.md). + +### Registering notification handlers in a composer + +If you are writing a package for Umbraco you will not have access to the Program class. You can instead use a composer, which gives you access to the `IUmbracoBuilder`. + +```csharp +public class DontShoutComposer : IComposer +{ + public void Compose(IUmbracoBuilder builder) + { + builder.AddNotificationHandler(); + } +} +``` + +## Async Notification Handler + +If you need to do anything asynchronously when handling a notification, you can achieve this using the `INotificationAsyncHandler`. + +### Notification handler + +Create an asynchronous handler by implementing the `INotificationAsyncHandler`: + +```csharp +public class ContentDeletedHandler : INotificationAsyncHandler +{ + public async Task HandleAsync(ContentDeletedNotification notification, CancellationToken cancellationToken) + { + // await anything + await Task.Delay(1000); + } +} +``` + +### Notification registration + +When using the `INotificationAsyncHandler`, register it using the `IUmbracoBuilder` and the `AddNotificationAsyncHandler` extension method. This can be done in the Program class or with a composer. + +{% code title="Program.cs" %} + +```csharp +builder.CreateUmbracoBuilder() + .AddBackOffice() + .AddWebsite() + .AddDeliveryApi() + .AddComposers() + .AddNotificationAsyncHandler() + .Build(); +``` + +{% endcode %} + +{% code title="NotificationHandlersComposer.cs" %} + +```csharp +public class NotificationHandlersComposer : IComposer +{ + public void Compose(IUmbracoBuilder builder) + { + builder.AddNotificationAsyncHandler(); + } +} +``` + +{% endcode %} diff --git a/16/umbraco-cms/reference/notifications/sendingallowedchildrennotifications.md b/16/umbraco-cms/reference/notifications/sendingallowedchildrennotifications.md new file mode 100644 index 00000000000..8603360e63a --- /dev/null +++ b/16/umbraco-cms/reference/notifications/sendingallowedchildrennotifications.md @@ -0,0 +1,9 @@ +--- +description: Example of how to use a SendingAllowedChildren Notification +--- + +# Sending Allowed Children Notification + +The `SendingAllowedChildrenNotification` is no longer available in Umbraco 15. + +Please see [content type filters](../content-type-filters.md) as the supported alternative for use cases that previously relied on this notification. \ No newline at end of file diff --git a/16/umbraco-cms/reference/notifications/umbracoapplicationlifetime-notifications.md b/16/umbraco-cms/reference/notifications/umbracoapplicationlifetime-notifications.md new file mode 100644 index 00000000000..c1b26bcff5f --- /dev/null +++ b/16/umbraco-cms/reference/notifications/umbracoapplicationlifetime-notifications.md @@ -0,0 +1,51 @@ +--- +description: >- + Represents an Umbraco application lifetime (starting, started, stopping, + stopped) notification +--- + +# Umbraco Application Lifetime Notifications + +Umbraco application lifetime notifications are published for the starting, started, stopping, and stopped events of the Umbraco runtime. These events implement the `IUmbracoApplicationLifetimeNotification` interface that contains a single `IsRestarting` property. + +An Umbraco application is restarted after an install or upgrade has been completed. You can use this property to prevent running code twice: on initial boot and restart. To prevent running code when the application is in the install or upgrade state, inject an `IRuntimeState` instance in your notification and inspect the `Level` property instead. + +## Usage + +Example usage of the UmbracoApplicationLifetime notifications: + +```csharp +using Microsoft.Extensions.Logging; +using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.DependencyInjection; +using Umbraco.Cms.Core.Notifications; + +public class UmbracoApplicationNotificationComposer : IComposer +{ + public void Compose(IUmbracoBuilder builder) + { + builder.AddNotificationHandler(); + builder.AddNotificationHandler(); + builder.AddNotificationHandler(); + builder.AddNotificationHandler(); + } +} + +public class UmbracoApplicationNotificationHandler : INotificationHandler, INotificationHandler, INotificationHandler, INotificationHandler +{ + private readonly ILogger _logger; + + public UmbracoApplicationNotificationHandler(ILogger logger) => _logger = logger; + + public void Handle(UmbracoApplicationStartingNotification notification) => Log(notification, notification.IsRestarting); + + public void Handle(UmbracoApplicationStartedNotification notification) => Log(notification, notification.IsRestarting); + + public void Handle(UmbracoApplicationStoppingNotification notification) => Log(notification, notification.IsRestarting); + + public void Handle(UmbracoApplicationStoppedNotification notification) => Log(notification, notification.IsRestarting); + + private void Log(INotification notification, bool isRestarting) => _logger.LogInformation("{Type} - {IsRestarting}", notification.GetType().Name, isRestarting); +} +``` diff --git a/16/umbraco-cms/reference/plugins/README.md b/16/umbraco-cms/reference/plugins/README.md new file mode 100644 index 00000000000..08391ab5433 --- /dev/null +++ b/16/umbraco-cms/reference/plugins/README.md @@ -0,0 +1,13 @@ +# Plugins + +_The term 'Plugins' is referring to any types in Umbraco that are found in assemblies that are used to extend and/or enhance the Umbraco application. Plugins can also be added directly registered to their specific 'Resolver' if the plugin type is not public or if the Resolver type doesn't support finding types in assemblies._ + +What is a Resolver and what kinds of Resolvers are there? + +## [Creating a Resolver for a Plugin](creating-resolvers.md) + +Creating a single object and multiple object Resolver. + +## [Finding types](finding-types.md) + +Using the PluginManager to lookup types in assemblies to register in Resolvers. diff --git a/16/umbraco-cms/reference/plugins/creating-resolvers.md b/16/umbraco-cms/reference/plugins/creating-resolvers.md new file mode 100644 index 00000000000..382d6470f6d --- /dev/null +++ b/16/umbraco-cms/reference/plugins/creating-resolvers.md @@ -0,0 +1,91 @@ +# Creating Resolvers + +_A Resolver should be created for any plugin type. Resolvers are the standard way to retrieve/create/register plugin types._ + +## Creating a single object resolver + +As an example, we'll create a resolver to resolve an application error logger: + +```csharp +/// +/// An object resolver to return the IErrorLogger +/// +public class ErrorLoggerResolver : SingleObjectResolverBase +{ + internal ContentStoreResolver(IErrorLogger errorLogger) + : base(errorLogger) + { + } + + /// + /// Can be used by developers at runtime to set their IErrorLogger at app startup + /// + /// + public void SetErrorLogger(IErrorLogger errorLogger) + { + Value = errorLogger; + } + + /// + /// Returns the IErrorLogger + /// + public IErrorLogger ErrorLogger + { + get { return Value; } + } +} +``` + +All you need to do is inherit from `Umbraco.Core.ObjectResolution.SingleObjectResolverBase` and then add whatever constructors, properties and methods you would like to expose. + +In the example above we have a constructor that accepts a default `IErrorLogger`. Normally in Umbraco this resolver will be constructed in a `IBootManager` with a default object. The we expose a method to allow developers to change to a custom `IErrorLogger` at runtime called `SetErrorLogger`. Then we create a property to expose the `IErrorLogger` called ErrorLogger. + +Example: + +```csharp +// get the error logger +IErrorLogger logger = ErrorLoggerResolver.Current.ErrorLogger; + +// set the error logger (can only be done during application startup) +ErrorLoggerResolver.Current.SetErrorLogger(new MyCustomErrorLogger("../my-file-path")); +``` + +## Creating a multiple object resolver + +Creating a multiple object resolver is similar. As an example we'll create a LanguageConvertersResolver. + +{% hint style="info" %} +The naming convention for multiple objects resolvers are plural: We've named this LanguageConverter**s**Resolver with a pluralized 'Converters' to denote that this resolver returns multiple objects +{% endhint %} + +```csharp +public sealed class LanguageConvertersResolver : ManyObjectsResolverBase +{ + /// + /// Constructor + /// + /// + internal LanguageConvertersResolver(IEnumerable converters) + : base(converters) + { + } + + /// + /// Return the converters + /// + public IEnumerable Converters + { + get { return Values; } + } + +} +``` + +When creating a multiple object resolver you need to decide what lifetime scope the objects created and returned will have which is defined in the constructor created. The default constructor of the `ManyObjectsResolverBase` specifies that the objects created will have an Application based lifetime scope which means the objects will be singletons only one instance of each one will exist for the lifetime of the application. There are 3 lifetime scopes that can be specified: + +* ObjectLifetimeScope.Application + * One instance of each object will be created for the entire lifetime of the application (singleton) +* ObjectLifetimeScope.Transient + * A new instance of each object will be created each time the 'Values' collection is accessed +* ObjectLifetimeScope.HttpRequest + * One instance of each object will be created for the lifetime of the current http request diff --git a/16/umbraco-cms/reference/plugins/finding-types.md b/16/umbraco-cms/reference/plugins/finding-types.md new file mode 100644 index 00000000000..3e060b2a8ca --- /dev/null +++ b/16/umbraco-cms/reference/plugins/finding-types.md @@ -0,0 +1,35 @@ +# Finding types + +_Whenever types need to be found in assemblies in order to add them to resolvers, the PluginManager should be used. The TypeFinder should never be used directly in any code except for in PluginManager extension methods or in the PluginManager itself._ + +## The Plugin Manager + +The `Umbraco.Core.PluginManager` class is responsible for finding and caching all plugin types. It is also responsible for instantiating these types. It contains 4 important methods: + +* `IEnumerable ResolveTypes()` + * Generic method to find the specified type and cache the result +* `IEnumerable ResolveTypesWithAttribute()` + * Generic method to find the specified type that has an attribute and cache the result +* `IEnumerable ResolveAttributedTypes()` + * Generic method to find any type that has the specified attribute and cache the result +* `T CreateInstance(Type type, bool throwException = false)` + * Used to create an instance of the specified type based on the resolved/cached plugin types + +## Retrieving instances + +It is definitely possible to use the methods above to find types in your code but this is not recommended practice. It is recommended to create extension methods for the PluginManager named accordingly to find specific types. For example: + +```csharp +PluginManager.Current.ResolveTrees(); +``` + +The code for this method is as follows: + +```csharp +internal static IEnumerable ResolveTrees(this PluginManager resolver) +{ + return resolver.ResolveTypes(); +} +``` + +The code calls the PluginManager's ResolveTypes method but this method is human readable and distinguishable. diff --git a/16/umbraco-cms/reference/querying/README.md b/16/umbraco-cms/reference/querying/README.md new file mode 100644 index 00000000000..e82476b45be --- /dev/null +++ b/16/umbraco-cms/reference/querying/README.md @@ -0,0 +1,31 @@ +# Querying & Models + +_Overview of multiple ways of querying, filtering, and searching published content for use on your website._ + +## [UDI identifiers](udi-identifiers.md) + +Umbraco stores identifiers in UDI format for most Umbraco object types. This identifier stores all of the metadata required to retrieve an Umbraco object and is parseable within text. Example: `umb://document/4fed18d8c5e34d5e88cfff3a5b457bf2`. UDI's can be used in many of the querying APIs. + +## [IPublishedContent](ipublishedcontent/) + +`IPublishedContent` is a strongly typed model for content, media, and members that is used to render content in your views for your website. + +## [UmbracoHelper](umbracohelper.md) + +UmbracoHelper is the unified way to work with published content/media on your website. Whether you are using MVC or WebForms you will be able to use UmbracoHelper to query/traverse Umbraco published data. + +## [IMemberManager](imembermanager.md) + +`IMemberManager` is a user manager interface for accessing member data in the form of `MemberIdentityUser` and converting it to `IPublishedContent`. + +## [IPublishedContentQuery](ipublishedcontentquery.md) + +The `IPublishedContentQuery` interface contains query methods for accessing strongly typed content in services etc. + +## [ITagQuery](itagquery.md) + +The `ITagQuery` interface allows to work with tags in Umbraco. + +## [UmbracoContext](umbraco-context.md) + +The UmbracoContext is a simplified way to work with the current request on your website. diff --git a/16/umbraco-cms/reference/querying/imembermanager.md b/16/umbraco-cms/reference/querying/imembermanager.md new file mode 100644 index 00000000000..87e384d581b --- /dev/null +++ b/16/umbraco-cms/reference/querying/imembermanager.md @@ -0,0 +1,202 @@ +--- +description: "Using the IMemberManager" +--- + +# IMemberManager + +`IMemberManager` has a variety of methods that are useful for managing members in controllers and views. In this article, we'll have a look at how some of these can be used. + +{% hint style="info" %} +For the full list of methods, see the [IMemberManager Interface API Documentation](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.Security.IMemberManager.html#methods). +{% endhint %} + +## How to reference IMemberManager + +There are different ways to reference `IMemberManager`: + +### Dependency Injection + +The recommended way is to create a [Controller](../../implementation/controllers.md) or Service and inject `IMemberManager` in the constructor: + +{% code title="MemberAuthenticationController.cs" %} +```csharp +using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Core.Security; + +namespace UmbracoDocs.Samples; + +public class MemberAuthenticationController : Controller +{ + private readonly IMemberManager _memberManager; + + public MemberAuthenticationController(IMemberManager memberManager) + => _memberManager = memberManager; +} +``` +{% endcode %} + +### Views + +Alternatively, `IMemberManager` can be injected directly into a template: + +{% code title="MemberAuthenticationView.cshtml" %} +```cshtml +@using Umbraco.Cms.Core.Security; +@inject IMemberManager _memberManager; +@inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage + +@if (_memberManager.IsLoggedIn()) +{ + @* Do something when a member is logged in *@ +} +``` +{% endcode %} + +{% hint style="info" %} +It is advisable to implement Controllers to manage this kind of view logic. +{% endhint %} + +## Examples + +### Finding members + +`IMemberManager` has multiple ways to find members. + +#### FindByIdAsync(string) + +Finds a member by their ID + +```csharp +var member = await _memberManager.FindByIdAsync("1234"); +// Do stuff with the member, for instance checking if email is confirmed +var emailConfirmed = member is not null && member.EmailConfirmed; +``` + +If we want to find a member by `Udi` or `Guid` we need to inject `IIdKeyMap` service: + +#### Find member by `Udi` + +```csharp +var memberUdiAttempt = _idKeyMap.GetIdForUdi(memberUdi); +if (memberUdiAttempt.Success) +{ + var memberId = memberUdiAttempt.Result; + var member = await _memberManager.FindByIdAsync(memberId.ToString()); +} +``` + +#### Find member by `Guid` + +```csharp +var memberKeyAttempt = _idKeyMap.GetIdForKey(memberKey, UmbracoObjectTypes.Member); +if (memberKeyAttempt.Success) +{ + var memberId = memberKeyAttempt.Result; + var member = await _memberManager.FindByIdAsync(memberId.ToString()); +} +``` + +#### FindByEmailAsync(string) + +Finds a member by their email. + +```csharp +var member = await _memberManager.FindByEmailAsync("test@member.com"); +// Do stuff with the member, for instance checking if email is confirmed +var emailConfirmed = member is not null && member.EmailConfirmed; +``` + +#### FindByNameAsync(string) + +Finds a member by their login name. + +```csharp +var member = await _memberManager.FindByNameAsync("TestLoginName"); +// Do stuff with the member, for instance checking if email is confirmed +var emailConfirmed = member is not null && member.EmailConfirmed; +``` + +### AsPublishedMember(MemberIdentityUser) + +The `IMemberManager` methods returns members as `MemberIdentityUser`. + +Since Members Types are defined like Content Types in Umbraco, members can hold any number of properties. To access these properties, it can be beneficial to convert the member into an `IPublishedContent` instance. + +This is done using `AsPublishedMember(MemberIdentityUser)`:: + +```csharp +MemberIdentityUser? member = await _memberManager.FindByEmailAsync("test@member.com"); +if (member is not null) +{ + IPublishedContent? memberAsContent = _memberManager.AsPublishedMember(member); + // Access member content properties +} +``` + +### GetCurrentMemberAsync() + +Returns the currently logged in member if there is one, else returns null value. + +```csharp +var currentMember = await _memberManager.GetCurrentMemberAsync(); +var currentMemberName = currentMember?.Name; +``` + +### GetUserIdAsync() + +Returns the ID of a member. + +```csharp +public async Task GetMemberId(MemberIdentityUser member) + => await _memberManager.GetUserIdAsync(member); +``` + +### IsLoggedIn() + +Checks if the current request contains a logged-in member. + +```csharp +public async Task GetMemberId(MemberIdentityUser member) + => await _memberManager.GetUserIdAsync(member); +``` + +### IsMemberAuthorizedAsync(IEnumerable memberTypes, IEnumerable memberGroups, IEnumerable memberIds) + +Checks if the current member is authorized as specific member types, member groups or concrete members. + +For instance, you can use this method to verify if the current logged in member is part of a specific group: + +```csharp +var memberIsVIP = await _memberManager.IsMemberAuthorizedAsync(allowGroups: new []{"VIP"}); +``` + +### IsProtectedAsync() + +Returns a `Task` specifying if the content with a given [Umbraco path](ipublishedcontent/properties.md#path) has public access restrictions set. + +```csharp +public async Task GetContentName(IPublishedContent content) + => await _memberManager.IsProtectedAsync(content.Path) + ? $"{content.Name} - Members only!" + : $"{content.Name} - Access for everyone!"; +``` + +### MemberHasAccessAsync(string) + +Returns a `Task` specifying if the currently logged in member has access to the content given its [Umbraco path](ipublishedcontent/properties.md#path). + +```csharp +public async Task GetContentName(IPublishedContent content) + => await _memberManager.MemberHasAccessAsync(content.Path) + ? content.Name + : "Members only"; +``` + +### ValidateCredentialsAsync(string username, string password) + +Validates that specific member credentials are correct (without performing a log-in). + +```csharp +public async Task IsValidCredentials(string userName, string password) + => await _memberManager.ValidateCredentialsAsync(userName, password); +``` diff --git a/16/umbraco-cms/reference/querying/ipublishedcontent/README.md b/16/umbraco-cms/reference/querying/ipublishedcontent/README.md new file mode 100644 index 00000000000..ff3f89581fe --- /dev/null +++ b/16/umbraco-cms/reference/querying/ipublishedcontent/README.md @@ -0,0 +1,28 @@ +# IPublishedContent + +_`IPublishedContent` is a strongly typed model for content, media and members and is used to render content in your views for your website._ + +## Get started + +To access the current page in your templates, copy-paste the below Razor code. + +```csharp +@{ + var pageName = Model.Name; + var childPages = Model.Children(); +} + +

@pageName

+``` + +## [Properties & Extension Methods](properties.md) + +Listing and explanation of IPublishedContent properties and standard helpers for Content and Media. + +## [Collections & Filtering](collections.md) + +Methods for IPublishedContent collections and filtering. + +## [IsHelpers](ishelpers.md) + +A library of extension methods to simplify working with IPublishedContent in collections to modify your HTML output. Examples of using `IsHelpers` could be injecting CSS classes for alternating rows or to modify margins. diff --git a/16/umbraco-cms/reference/querying/ipublishedcontent/collections.md b/16/umbraco-cms/reference/querying/ipublishedcontent/collections.md new file mode 100644 index 00000000000..ad64579bb6f --- /dev/null +++ b/16/umbraco-cms/reference/querying/ipublishedcontent/collections.md @@ -0,0 +1,218 @@ +# IPublishedContent Collections + +All collections of `IPublishedContent` are `IEnumerable`. This means that all C# LINQ statements can be used to filter and query the collections. + +## Collections + +### .Children + +Returns a collection of child items available in the current culture, below the current content item. + +```csharp +
    + @foreach(var item in Model.Children) + { +
  • @item.Name
  • + } +
+``` + +### .ChildrenForAllCultures + +Returns a collection of child items for all cultures, below the current content item, regardless of whether they are available for the current culture. + +```csharp +
    + @foreach(var item in Model.ChildrenForAllCultures) + { +
  • @item.Name
  • + } +
+``` + +### .Children(string culture = null) + +Returns a collection of child items available in the specified culture with a default of the current one, below the current content item. + +```csharp +
    + @foreach(var item in Model.Children("dk-dk")) + { +
  • @item.Name
  • + } +
+``` + +### .Ancestors() + +Returns all ancestors of the current page (parent page, grandparent and so on) + +```csharp +
    + @*Order items by their Level*@ + @foreach(var item in Model.Ancestors().OrderBy(x => x.Level)) + { +
  • @item.Name
  • + } +
+``` + +### .Ancestor() + +Returns the first ancestor of the current page + +```csharp +@* return the first ancestor item from the current page *@ +var nodes = Model.Ancestor(); + +@* return the first item, of a specific type, from the current page *@ +var nodes = Model.Ancestor(); +``` + +### .AncestorsOrSelf() + +Returns a collection of all ancestors of the current page (parent page, grandparent and so on), and the current page itself + +```csharp +@* Get the top item in the content tree, this will always be the Last ancestor found *@ +var websiteRoot = Model.AncestorsOrSelf().Last(); +``` + +### .Descendants() + +Returns all descendants of the current page (children, grandchildren etc) + +```csharp +
    + @* Filter collection by content that has a template assigned *@ + @foreach(var item in Model.Descendants().Where(x => x.TemplateId > 0)) + { +
  • @item.Name
  • + } +
+``` + +### .DescendantsOrSelf() + +Returns all descendants of the current page (children, grandchildren etc), and the current page itself + +```csharp +
    + @* Filter collection by content that has a template assigned *@ + @foreach(var item in Model.DescendantsOrSelf().Where(x => x.TemplateId > 0)) + { +
  • @item.Name
  • + } +
+``` + +### .OfTypes + +Filters a collection of content by content type alias + +```csharp +
    + @* Filter collection by content type alias (you can pass in any number of aliases) *@ + @foreach(var item in Model.DescendantsOrSelf().OfTypes("widget1", "widget2")) + { +
  • @item.Name
  • + } +
+``` + +*** + +## Filtering, Ordering & Extensions + +Filtering and Ordering are done with LINQ. + +Some examples: + +### .Where + +```csharp +@* Returns all items in the collection that have a template assigned and have a name starting with 'S' *@ +var nodes = Model.Descendants().Where(x => x.TemplateId > 0 && x.Name.StartsWith("S")) +``` + +### .OrderBy + +```csharp +@* Orders a collection by the property name "title" *@ +var nodes = Model.Children.OrderBy(x => x.GetProperty("title")) +``` + +### .GroupBy + +Groups collection by content type alias + +```csharp +@{ + var groupedItems = Model.Descendants().GroupBy(x => x.ContentType); + foreach (var group in groupedItems) + { +

@group.Key.Alias

+ foreach(var item in group) + { +

@item.Name

+ } + } +} +``` + +### .Take(int) + +Return only the number of items for a collection specified by the integer value. + +```csharp +@* return the first 3 items from the child collection *@ +var nodes = Model.Children.Take(3); +``` + +### .Skip(int) + +Return items from the collection after skipping the specified number of items. + +```csharp +@* Skip the first 3 items in the collection and return the rest *@ +var nodes = Model.Children.Skip(3); +``` + +{% hint style="info" %} +You can combine Skip and Take when using for paging operations +{% endhint %} + +```csharp +@* using skip and take together you can perform paging operations *@ +var nodes = Model.Children.Skip(10).Take(10); +``` + +### .Count() + +Returns the number of items in the collection + +```csharp +int numberOfChildren = Model.Children.Count(); +``` + +### .Any() + +Returns a boolean True/False value determined by whether there are any items in the collection + +```csharp +bool hasChildren = Model.Children.Any(); +``` + +## Filtering Conventions + +Some filtering and routing behaviour is dependent upon a set of special naming conventions for certain properties. [See also: Routing Property Conventions](../../routing/routing-properties.md) + +### .IsVisible() + +If you create a checkbox property on a document type with an alias `umbracoNaviHide` then the value of this property is used by the `IsVisible()` extension method when filtering. + +```csharp +IEnumerable sectionPages = Model.Children.Where(x => x.IsVisible()); +``` + +Use case: When displaying a navigation menu for a section of the site, following this convention gives editors the option to 'hide' certain pages from appearing in the section navigation. (hence the unusual _umbracoNaviHide_ property alias!) diff --git a/16/umbraco-cms/reference/querying/ipublishedcontent/ishelpers.md b/16/umbraco-cms/reference/querying/ipublishedcontent/ishelpers.md new file mode 100644 index 00000000000..8e49a63e552 --- /dev/null +++ b/16/umbraco-cms/reference/querying/ipublishedcontent/ishelpers.md @@ -0,0 +1,71 @@ +# IPublishedContent IsHelpers + +The IsHelper methods are a set of extension methods for IPublishedContent to help perform quick conditional queries against IPublishedContent nodes in a collection. + +IsHelper methods are ternary operators, however they work a little nicer since they can be embedded in properties. They are also quicker to write because fewer brackets are needed for Razor to understand them. + +*** + +## How to use + +An IsHelper can be invoked as a method of an `IPublishedContent`. + +```csharp +@{ +if(item.IsVisible()) +{ +@item.Name +} +} +``` + +*** + +## IsHelper Methods + +### IsComposedOf(string alias) + +Test whether the content is of a content type composed of the given alias. + +### IsAllowedTemplate(int templateId) + +Test whether the specified `templateId` is an allowed template for the current node. + +### IsAllowedTemplate(string templateAlias) + +Test whether the specified `templateAlias` is an allowed template for the current node. + +{% hint style="info" %} +By default the above template methods are disabled. To enable them, make sure to modify your appsettings.json to include the following JSON config keys inside Umbraco.CMS section: + +```json +"WebRouting": { + "ValidateAlternativeTemplates": true, + "DisableAlternativeTemplates": false + } +``` +{% endhint %} + +### .IsEqual(IPublishedContent otherNode\[,string valueIfTrue]\[,string valueIfFalse]) + +Test if the current node is equal (by Id) to another node. + +### .IsNotEqual(IPublishedContent otherNode\[,string valueIfTrue]\[,string valueIfFalse]) + +Test if the current node is not equal (by Id) to another node. + +### .IsDescendant(IPublishedContent otherNode\[,string valueIfTrue]\[,string valueIfFalse]) + +Test if the current node is a descendant of another node. + +### .IsDescendantOrSelf(IPublishedContent otherNode\[,string valueIfTrue]\[,string valueIfFalse]) + +Test if the current node is the same as or a descendant of another node. + +### .IsAncestor(IPublishedContent otherNode\[,string valueIfTrue]\[,string valueIfFalse]) + +Test if the current node is an ancestor of another node. + +### .IsAncestorOrSelf(IPublishedContent otherNode\[,string valueIfTrue]\[,string valueIfFalse]) + +Test if the current node is the same as or an ancestor of another node. diff --git a/16/umbraco-cms/reference/querying/ipublishedcontent/properties.md b/16/umbraco-cms/reference/querying/ipublishedcontent/properties.md new file mode 100644 index 00000000000..ddec4a93523 --- /dev/null +++ b/16/umbraco-cms/reference/querying/ipublishedcontent/properties.md @@ -0,0 +1,295 @@ +# IPublishedContent Property Access & Extension Methods + +## Umbraco Properties + +Built-in properties, which exists on all content objects by default + +Common Examples + +```csharp +@* gets the current page Url *@ +@Model.Url(PublishedUrlProvider) + +@* gets the Creation date, and formats it to a short date *@ +@Model.CreateDate.ToString("D") + +@* Outputs the name of the parent if it exists *@ +@if(Model.Parent != null){ +

@Model.Parent.Name

+} +``` + +### .Id + +Returns the unique Id for the current content item + +```csharp +@Model.Id +``` + +### .Name + +Returns the Name of the current content item in the current culture + +```csharp +@Model.Name +``` + +### .Name(IVariationContextAccessor, string culture = null) + +Returns the Name of the current content item in the specified culture, null falls back to the current culture + +```csharp +@Model.Name(VariationContextAccessor, "dk-dk") +``` + +### .ContentType + +Returns a strongly typed 'PublishedContentType' object representing the content type the IPublishedContent item is based on, that gives access to the alias + +```csharp +@Model.ContentType +@Model.ContentType.Alias +``` + +### .GetCultureFromDomains(IUmbracoContextAccessor, ISiteDomainHelper, Uri current = null) + +Returns a culture from a configured domain in the content tree. + +```csharp +@Model.GetCultureFromDomains(ContextAccessor, DomainHelper) +``` + +### .Parent + +Returns the parent content item + +```csharp +@Model.Parent +@Model.Parent.Name +``` + +### .Path + +Returns a comma delimited string of Node Ids that represent the path of content items back to root. + +```csharp +@Model.Path +``` + +### .Level + +Returns the Level (depth) this content item is in its tree path + +```csharp +@Model.Level +``` + +### .TemplateId + +Returns the id of the default Template object used with this content item. + +```csharp +@Model.TemplateId +``` + +There are extension methods to retrieve template alias (Model.GetTemplateAlias()) + +### .SortOrder + +Returns the index the page is on, compared to its siblings + +```csharp +@Model.SortOrder +``` + +### .Url(PublishedUrlProvider, culture = null, UrlMode mode = UrlMode.Default) - (Extension method) + +Returns the Url to the page. + +```csharp +@Model.Url(PublishedUrlProvider) +``` + +**Example:** Getting a Danish Url for a site where a Danish language has been set up. + +```csharp +@Model.Url(PublishedUrlProvider, "dk") +``` + +**Example:** Getting an Absolute Danish Url for a site where a Danish language has been set up. + +```csharp +@Model.Url(PublishedUrlProvider, "dk", UrlMode.Absolute) +``` + +### .UrlSegment + +Returns the Url encoded name of the page (slug) of the current culture + +```csharp +@Model.UrlSegment +``` + +### .UrlSegment(IVariationContextAccessor, string culture = null) + +Returns the Url encoded name of the page (slug) of the specified culture + +```csharp +@Model.UrlSegment(VariationContextAccessor) +``` + +### .WriterId + +Returns the id of the Umbraco backoffice user that performed the last update operation on the content item. + +```csharp +@Model.WriterId +``` + +### .WriterName(IUserService) + +Returns the name of the Umbraco backoffice user that initially created the content item. + +```csharp +@Model.WriterName(UserService) +``` + +### .CreatorId + +Returns the id of the Umbraco backoffice user that initially created the content item + +```csharp +@Model.CreatorId +``` + +### .CreatorName(IUserService) + +Returns the name of the Umbraco backoffice user that initially created the content item. + +```csharp +@Model.CreatorName(UserService) +``` + +### .CreateDate + +Returns the DateTime the page was created + +```csharp +@Model.CreateDate +@* gets the Creation date, and formats it to a short date *@ +@Model.CreateDate.ToString("D") +``` + +### .UpdateDate + +Returns the DateTime the page was modified + +```csharp +@Model.UpdateDate +@* gets the Update/Modified date, and formats it to a short date *@ +@Model.UpdateDate.ToString("D") +``` + +*** + +## Custom properties + +All content and media items contain a reference to all the data defined by their Document Type. Custom property access is achieved using variations of the method: `Value` + +### Model.Value(IPublishedValueFallback, string) + +Returns the property value for the specified property alias + +```csharp +@*Get the property with alias: "siteName" from the current page *@ +@Model.Value(PublishedValueFallback, "siteName") +``` + +The type returned of this property value is `object`. This is fine in most cases since when using the above syntax, Razor will automatically execute a `ToString()` on the result value. + +See `Model.Value(string)` for how to return a strongly typed object for the property + +### Model.Value\(string) + +Returns the property value for the specified property alias converted to 'T' - the requested output type of the property value. + +For example, to return the `string` result of "siteName": + +```csharp +@(Model.Value(PublishedValueFallback, "siteName")) +``` + +```csharp +var mediaItems = Model.Value>(PublishedValueFallback, "mediaIds"); +``` + +## Fallbacks + +If the current content item doesn't have the requested value, use an alternative 'fallback' value in its place. + +Each of the examples below make use of an injected PublishedValueFallback. This is achieved by adding the following at the top of your Razor file: + +```csharp +@inject IPublishedValueFallback PublishedValueFallback +``` + +This parameter is optional, but can make unit testing easier. + +### Fallback to Default Value + +If a content page has a 'title' property, to fallback to use the 'Name' of the content item if the 'title' is not populated. Set the Fallback type to be Fallback.ToDefaultValue, and set the DefaultValue accordingly: + +```csharp +@(Model.Value(PublishedValueFallback, "title", fallback: Fallback.ToDefaultValue, defaultValue: Model.Name)); +``` + +or to a specific value + +```csharp +@(Model.Value(PublishedValueFallback, "author", fallback: Fallback.ToDefaultValue, defaultValue: "Team Reporter")); +``` + +### Fallback to Ancestors + +Look for a property value on the current page. If it doesn't exist look for the property value on the parent page. Then the parent's parent page and so on. This approach allows specifying 'global property values' all the way up the content tree. These values can be overridden in different sections or individual pages. + +```csharp +@(Model.Value(PublishedValueFallback, "propertyAlias", fallback: Fallback.ToAncestors)) +``` + +### Fallback to Language + +If working with variants - fallback to a different language value - if perhaps the value hasn't been populated yet for the current language: + +```csharp +@(Model.Value(PublishedValueFallback, "pageTitle", "fr", fallback: Fallback.ToLanguage)) +``` + +### Combining the Fallback options + +Use Fallback.To() to 'combine' Fallback options. + +The following would first look for a 'title' property on all ancestors, before defaulting to the current page's name: + +```csharp +@Model.Value(PublishedValueFallback, "title", fallback: Fallback.To(Fallback.Ancestors, Fallback.DefaultValue), defaultValue: Model.Name) +``` + +## Property Methods + +**There are a few helpful methods to help check if a property exists, has a value or is null.** + +### .HasProperty(string propertyAlias) + +Returns a boolean value representing if the IPublishedContent has a property with the specified alias. + +### .HasValue(string propertyAlias) + +Returns a boolean value representing if the IPublishedContent property has had a value set. + +It's possible to use 'Fallbacks' with HasValue: + +```csharp +bool hasPageTitleSetSomewhere = Model.HasValue(PublishedValueFallback, "pageTitle", fallback: Fallback.ToAncestors); +``` diff --git a/16/umbraco-cms/reference/querying/ipublishedcontentquery.md b/16/umbraco-cms/reference/querying/ipublishedcontentquery.md new file mode 100644 index 00000000000..d8acd00a8a9 --- /dev/null +++ b/16/umbraco-cms/reference/querying/ipublishedcontentquery.md @@ -0,0 +1,103 @@ +--- +description: Querying in views with IPublishedContentQuery in Umbraco +--- + +# IPublishedContentQuery + +The `IPublishedContentQuery` interface contains different query methods for accessing strongly typed content in services etc. + +## How to inject IPublishedContentQuery + +In order to inject the `IPublishedContentQuery` into your services, you must add a using statement for `Umbraco.Cms.Core` and inject the service using the constructor. + +```csharp +using Umbraco.Cms.Core; + +namespace Umbraco.Docs.Samples.Web.Services; + +public class SearchService +{ + private readonly IPublishedContentQuery _publishedContentQuery; + + public SearchService(IPublishedContentQuery publishedContentQuery) + { + _publishedContentQuery = publishedContentQuery; + } +} +``` + +Now you can access the `IPublishedContentQuery` through `_publishedContentQuery` + +## Examples + +### .Search(string term) + +By default, `IPublishedContentQuery` will search on Umbraco's 'External' search index for any published content matching the provided search term. + +```csharp +public IEnumerable Search(string searchTerm) +{ + foreach (var result in _publishedContentQuery.Search(searchTerm)) + { + yield return result; + } +} +``` + +### .Search(string term, int skip, int take, out long totalRecords) + +Specifying the number of records 'to skip' and the number of records 'to take' improves performance with many matching search results. This approach is beneficial when there is a requirement to implement paging. + +```csharp +public IEnumerable Search(string searchTerm, int skip = 5, int take = 10) +{ + foreach (var result in _publishedContentQuery.Search(searchTerm, skip, take, out long totalRecords)) + { + yield return result; + } +} +``` + +### .Search(IQueryExecutor queryExecutor) + +For more complex searching you can construct an Examine QueryExecutor. In the example the search will execute against content of type "blogPost" only. [Further information on using Examine](../searching/examine/quick-start.md#different-ways-to-query) + +```csharp +using System; +using System.Collections.Generic; +using Examine; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Infrastructure.Examine; +using Umbraco.Extensions; + +namespace Umbraco.Docs.Samples.Web.Services; + +public class SearchService +{ + private readonly IPublishedContentQuery _publishedContentQuery; + private readonly IExamineManager _examineManager; + + public SearchService(IPublishedContentQuery publishedContentQuery, IExamineManager examineManager) + { + _publishedContentQuery = publishedContentQuery; + _examineManager = examineManager; + } + + public IEnumerable Search(string searchTerm) + { + if (!_examineManager.TryGetIndex(Constants.UmbracoIndexes.ExternalIndexName, out IIndex index)) + { + throw new InvalidOperationException($"No index found by name{Constants.UmbracoIndexes.ExternalIndexName}"); + } + + var query = index.Searcher.CreateQuery(IndexTypes.Content); + var queryExecutor = query.NodeTypeAlias("blogPost").And().ManagedQuery(searchTerm); + + foreach (var result in _publishedContentQuery.Search(queryExecutor)) + { + yield return result; + } + } +} +``` diff --git a/16/umbraco-cms/reference/querying/itagquery.md b/16/umbraco-cms/reference/querying/itagquery.md new file mode 100644 index 00000000000..44944146f14 --- /dev/null +++ b/16/umbraco-cms/reference/querying/itagquery.md @@ -0,0 +1,158 @@ +--- +description: Working with tags in Umbraco +--- + +# ITagQuery + +The `ITagQuery` interface is your primary way to work with tags in Umbraco. This interface allows you to get different tags, such as content tags and media tags. It also lets you retrieve content by tag, for instance, getting all content nodes with the "Umbraco" tag. + +## How to reference ITagQuery + +If you're using it in Views or Partial views you can inject `ITagQuery` using the `@inject` keyword, for example + +``` +@inject ITagQuery _tagQuery; +``` + +After this you can use `_tagQuery` to access the `ITagQuery`. + +If you're using it in controllers, you can inject it into the constructor like so: + +```csharp +using System.Collections.Generic; +using System.Linq; +using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Core.PublishedCache; + +namespace UmbracoHelperDocs.Controllers; + +[ApiController] +[Route("/umbraco/api/tags")] +public class TagApiController : Controller +{ + private readonly ITagQuery _tagQuery; + + public TagApiController(ITagQuery tagQuery) + { + _tagQuery = tagQuery; + } + + [HttpGet("getmediatags")] + public ActionResult> GetMediaTags() + { + return _tagQuery.GetAllMediaTags().Select(tag => tag.Text).ToList(); + } +} +``` + +{% hint style="warning" %} +`ITagQuery` is a scoped service, meaning that it should only be injected into scoped or transient services. For more information see the official [Microsoft Documentation](https://docs.microsoft.com/en-us/dotnet/core/extensions/dependency-injection#scoped) +{% endhint %} + +## Examples + +All examples are from a view using the injection shown above, but working with tags in controllers will be the same. + +### GetAllContentTags(\[string tagGroup]) + +Get a collection of tags used by content items on the site. Optionally, you can pass in a group name to only list tags belonging to a specific tag group + +```csharp +@{ + var allContentTags = _tagQuery.GetAllContentTags(); + var newsContentTags = _tagQuery.GetAllContentTags("news"); +} +``` + +### GetAllMediaTags(\[string tagGroup]) + +Get a collection of tags used by media items on the site. Optionally, you can pass in a group name to only list tags belonging to a specific tag group + +```csharp +@{ + var allMediaTags = _tagQuery.GetAllMediaTags(); + var newsMediaTags = _tagQuery.GetAllMediaTags("news"); +} +``` + +### GetAllMemberTags(\[string tagGroup]) + +Get a collection of tags used by members on the site. Optionally, you can pass in a group name to only list tags belonging to a specific tag group + +```csharp +@{ + var allMemberTags = _tagQuery.GetAllMemberTags(); + var newsMemberTags = _tagQuery.GetAllMemberTags("news"); +} +``` + +### GetAllTags(\[string tagGroup]) + +Get a collection of tags used on the site. Optionally, you can pass in a group name to only list tags belonging to a specific tag group + +```csharp +@{ + var allTags = _tagQuery.GetAllTags(); + var allNewsTags = _tagQuery.GetAllTags("news"); +} +``` + +### GetContentByTag(string tag, \[string tagGroup]) + +Get a collection of IPublishedContent by tag, and you can optionally filter by tag group as well + +```csharp +@{ + var taggedContent = _tagQuery.GetContentByTag("News"); +} +``` + +### GetContentByTagGroup(string tagGroup) + +Get a collection of IPublishedContent by tag group + +```csharp +@{ + var taggedContent = _tagQuery.GetContentByTagGroup("BlogTags"); +} +``` + +### GetMediaByTag(string tag, \[string tagGroup]) + +Get a collection of Media by tag, and you can optionally filter by tag group as well + +```csharp +@{ + var taggedMedia = _tagQuery.GetMediaByTag("BlogTag"); +} +``` + +### GetMediaByTagGroup(string tag, \[string tagGroup]) + +Get a collection of Media by tag group + +```csharp +@{ + var mediaByTagGroup = _tagQuery.GetMediaByTagGroup("BlogTags"); +} +``` + +### GetTagsForEntity(int contentId, \[string tagGroup]) + +Get a collection of tags by entity id (queries content, media and members), and you can optionally filter by tag group as well + +```csharp +@{ + var tagsForEntity = _tagQuery.GetTagsForEntity(1234); +} +``` + +### GetTagsForProperty(int contentId, string propertyTypeAlias, \[string tagGroup]) + +Get a collection of tags assigned to a property of an entity (queries content, media and members). Optionally, you can filter by tag group as well + +```csharp +@{ + var propertyTags = _tagQuery.GetTagsForProperty(1234, "propertyTypeAlias"); +} +``` diff --git a/16/umbraco-cms/reference/querying/udi-identifiers.md b/16/umbraco-cms/reference/querying/udi-identifiers.md new file mode 100644 index 00000000000..16f17b82de4 --- /dev/null +++ b/16/umbraco-cms/reference/querying/udi-identifiers.md @@ -0,0 +1,80 @@ +# UDI Identifiers + +Umbraco uses Unique Document Identifiers (UDIs) to reference most object types, such as content, media, and members. A UDI contains all the metadata needed to retrieve an Umbraco object and is readable within text. + +Example: + +```text +umb://document/4fed18d8c5e34d5e88cfff3a5b457bf2. +``` + +UDIs are commonly used in Umbraco’s querying and management APIs. + +## Format + +A UDI consists of three parts: + +1. Scheme: `umb://` – Identifies as an Umbraco UDI. +2. Type: `document`– Specifies the object type (for example, media, member, Data Type, and so on). +3. GUID Identifier: `4fed18d8c5e34d5e88cfff3a5b457bf2` – A unique identifier for the object (a GUID without dashes). + +## Usage + +UDIs are useful for retrieving content, media, or other Umbraco objects through the API. Below are examples of how to use a UDI in C# to get content or media. + +### Retrieving Content by UDI + +You can retrieve a content item using `IContentService`: + +```cs +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Models + +@inject IContentService contentService + +@{ + // Define the UDI string here + var udiString = "umb://document/334cadfa62dd49049aad86b6e4c02aac"; // Example UDI string + + if (udiString.StartsWith("umb://document/")) + { + // Extract the GUID from the UDI string + var guidString = udiString.Replace("umb://document/", ""); + if (Guid.TryParse(guidString, out var guid)) + { + // Retrieve the content by GUID + var content = contentService.GetById(guid); + if (content != null) + { + // Access the body text field + var bodyText = content.GetValue("bodyText"); // Replace 'bodyText' with the alias of your body text field +

@bodyText

// Output the body text + } + else + { +

Content not found.

+ } + } + else + { +

Invalid GUID in the UDI string.

+ } + } +} +``` + +## UDI Types + +There are two types of UDIs in Umbraco: + +### GUID UDI + +Used for objects that have a GUID identifier, such as content and media. + +* [API Reference: GuidUdi](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.GuidUdi.html) + +### String UDI + +Used for objects that are not GUID-based, such as dictionary items. + +* [API Reference: StringUdi](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Core.StringUdi.html) diff --git a/16/umbraco-cms/reference/querying/umbraco-context.md b/16/umbraco-cms/reference/querying/umbraco-context.md new file mode 100644 index 00000000000..739344f4dab --- /dev/null +++ b/16/umbraco-cms/reference/querying/umbraco-context.md @@ -0,0 +1,71 @@ +--- +description: >- + The UmbracoContext is a helpful service provided on each request to the + website +--- + +# UmbracoContext helper + +The UmbracoContext is the simplified way to work with the current request on your website. + +You can use UmbracoContext to access the content and media cache. Other useful properties are the original and cleaned URLs of the current request. You can also check if the current request is running in "preview" mode. + +## How to reference UmbracoContext + +If you are using Views you can reference the UmbracoContext with the syntax: `@UmbracoContext` + +If you need an `UmbracoContext` in your own controllers, you need to inject an `IUmbracoContextAccessor`. + +The following is an example of how to get access to the `UmbracoContext` in a controller: + +``` csharp +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Core.Web; +using Umbraco.Cms.Web.Common.PublishedModels; +using Umbraco.Extensions; + +namespace Umbraco.Docs.Samples.Web.Controllers.Api; + +[ApiController] +[Route("/umbraco/api/people")] +public class PeopleController : Controller +{ + private readonly IUmbracoContextAccessor _umbracoContextAccessor; + + public PeopleController(IUmbracoContextAccessor umbracoContextAccessor) + { + _umbracoContextAccessor = umbracoContextAccessor; + } + + [HttpGet("getall")] + public ActionResult> GetAll() + { + if (_umbracoContextAccessor.TryGetUmbracoContext(out IUmbracoContext? context) == false) + { + return this.Problem("unable to get content"); + } + + if (context.Content == null) + { + return this.Problem("Content Cache is null"); + } + + var personContentType = context.Content.GetContentType(Person.ModelTypeAlias); + Debug.Assert(context.Content.HasContent()); + var personNodes = (context.Content.GetAtRoot() + .First() + .FirstChild() + .Children() ?? Array.Empty()) + .Select(p => p.Name); + return personContentType == null + ? this.Problem("Person Content Type is null") + : Ok(personNodes); + } +} +``` + +UmbracoContext is registered with a scoped lifetime. See the [Microsoft documentation](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-5.0#lifetime-and-registration-options) for more information. A service scope is created for each request, which means you can resolve an instance directly in a controller. diff --git a/16/umbraco-cms/reference/querying/umbracohelper.md b/16/umbraco-cms/reference/querying/umbracohelper.md new file mode 100644 index 00000000000..877c126f8cc --- /dev/null +++ b/16/umbraco-cms/reference/querying/umbracohelper.md @@ -0,0 +1,143 @@ +--- +description: Using the Umbraco Helper +--- + +# UmbracoHelper + +UmbracoHelper is the unified way to work with published content/media on your website. You can use the UmbracoHelper to query/traverse Umbraco published data. + +UmbracoHelper also has a variety of helper methods that are useful when working in your views and controllers. + +## How to reference UmbracoHelper + +If you are using Views you can reference UmbracoHelper with the syntax: `@Umbraco` + +If you need an `UmbracoHelper` in your own controllers, you need to inject an instance. + +Example of getting `UmbracoHelper` in a controller: + +```csharp +using System.Linq; +using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Web.Common; +using Umbraco.Cms.Web.Common.Controllers; + +namespace UmbracoHelperDocs.Controllers; + +[Route("customcontent/[action]")] +public class CustomContentController : Controller +{ + private readonly UmbracoHelper _umbracoHelper; + + public CustomContentController(UmbracoHelper umbracoHelper) + => _umbracoHelper = umbracoHelper; + + public IActionResult GetHomeNodeName() + { + IPublishedContent rootNode = _umbracoHelper + .ContentAtRoot() + .FirstOrDefault(); + + if (rootNode is null) + { + return NotFound(); + } + + return Ok(rootNode.Name); + } +} +``` + +UmbracoHelper is registered with a scoped lifetime (see [Microsoft documentation](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-5.0#lifetime-and-registration-options) for more information), as a service scope is created for each request you can resolve an instance directly in a controller. + +If you need to use an UmbracoHelper in a service with a singleton lifetime you would instead need to make use of the IUmbracoHelperAccessor interface to obtain a temporary reference to an instance. + +## IPublishedContent + +UmbracoHelper will expose all content in the form of `IPublishedContent`. To get a reference to the currently executing content item from the UmbracoHelper, use `UmbracoHelper.AssignedContentItem`. + +The samples below demonstrate using `UmbracoHelper` in Razor. Working with the `UmbracoHelper` will be the same in controllers, except for the fact that you must resolve it with `IUmbracoHelperAccessor` like shown above. + +## Working with Content + +### .Content(Guid id) + +Given a node ID, returns a `IPublishedContent` + +```csharp +@{ + var pageFromGui = Umbraco.Content(Guid.Parse("af22cb83-9bd4-454b-ab06-cc19ac8e983d")); +} + +

@pageFromGui.Value("propertyAlias")

+ +@foreach (var child in pageFromGui.Children()) +{ + @child.Name +} +``` + +### .ContentAtRoot() + +Returns a collection of `IPublishedContent` objects from the Content tree. + +```csharp +@* Get the children of the first content item found in the root *@ +@foreach (var child in Umbraco.ContentAtRoot().First().Children()) +{ + @child.Name +} +``` + +## Working with Media + +### .Media(Guid id) + +Given a node ID, returns an `IPublishedContent` Media entity + +```csharp +@{ + var media = Umbraco.Media(Guid.Parse("ca4249ed-2b23-4337-b522-63cabe5587d1")); + var image = media.Url(); + var height = media.Value("umbracoHeight"); +} +``` + +### .MediaAtRoot() + +Returns a collection of `IPublishedContent` objects from the Media tree. + +```csharp +@foreach (var child in Umbraco.MediaAtRoot()) +{ + +} +``` + +## Working with Tags + +Previously the `UmbracoHelper` could be used to work with tags, this has been moved out of `UmbracoHelper` and is now available from `ITagQuery` which you can read more about in the [ITagQuery document](itagquery.md). + +## Working with Members + +Previously the `UmbracoHelper` could be used to work with members, this has ben moved out of `UmbracoHelper` and is now available from `IMemberManager`, see [IMemberManager](imembermanager.md) for more information + +## Searching + +Previously the `UmbracoHelper` could be used to run queries on your content, this has been moved out of `UmbracoHelper` and is now available from `IPublishedContentQuery`, see [IPublishedContentQuery](ipublishedcontentquery.md) for more information. + +## Fetching Dictionary Values + +### .GetDictionaryValue(string key) + +Returns a dictionary value(`string`) for the key specified. + +```html +

@Umbraco.GetDictionaryValue("createdOn"): @Model.CreateDate

+``` + +Alternatively, you can also specify an `altText` which will be returned if the dictionary value is empty. + +```html +

@Umbraco.GetDictionaryValue("createdOn", "Date Created"): @Model.CreateDate

+``` diff --git a/16/umbraco-cms/reference/response-caching.md b/16/umbraco-cms/reference/response-caching.md new file mode 100644 index 00000000000..c6bfc76ea19 --- /dev/null +++ b/16/umbraco-cms/reference/response-caching.md @@ -0,0 +1,104 @@ +# Response Caching + +Response caching reduces the number of requests a client or proxy makes to a web server. See the Microsoft documentation for details of [Response caching in ASP.NET Core](https://learn.microsoft.com/en-us/aspnet/core/performance/caching/response?view=aspnetcore-6.0) and how to implement the [Response Caching Middleware](https://learn.microsoft.com/en-us/aspnet/core/performance/caching/middleware?view=aspnetcore-6.0). + +## Modify the `Cache-Control` header for Static Files + +Example class to allow the modification of the `Cache-Control` header for static assets by file extension, but excluding Umbraco BackOffice assets. + +```csharp +using System.IO; +using System; +using System.Collections.Generic; + +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Options; +using Microsoft.AspNetCore.Http.Headers; +using Microsoft.Net.Http.Headers; + +using Umbraco.Cms.Core.Configuration.Models; +using IHostingEnvironment = Umbraco.Cms.Core.Hosting.IHostingEnvironment; + +namespace Umbraco.Docs.Samples.Web.Tutorials; + +public class ConfigureStaticFileOptions : IConfigureOptions +{ + // These are the extensions of the file types we want to cache (add and remove as you see fit) + private static readonly HashSet _cachedFileExtensions = new(StringComparer.OrdinalIgnoreCase) + { + ".ico", + ".css", + ".js", + ".svg", + ".woff2", + ".jpg" + }; + + private readonly string _backOfficePath; + + public ConfigureStaticFileOptions(IOptions globalSettings, IHostingEnvironment hostingEnvironment) + => _backOfficePath = globalSettings.Value.GetBackOfficePath(hostingEnvironment); + + public void Configure(StaticFileOptions options) + => options.OnPrepareResponse = ctx => + { + // Exclude Umbraco backoffice assets + if (ctx.Context.Request.Path.StartsWithSegments(_backOfficePath)) + { + return; + } + + // Set headers for specific file extensions + var fileExtension = Path.GetExtension(ctx.File.Name); + if (_cachedFileExtensions.Contains(fileExtension)) + { + ResponseHeaders headers = ctx.Context.Response.GetTypedHeaders(); + + // Update or set Cache-Control header + CacheControlHeaderValue cacheControl = headers.CacheControl ?? new CacheControlHeaderValue(); + cacheControl.Public = true; + cacheControl.MaxAge = TimeSpan.FromDays(365); + headers.CacheControl = cacheControl; + } + }; +} +``` + +Register the service in `Program.cs` + +```csharp +builder.CreateUmbracoBuilder() + .AddBackOffice() + .AddWebsite() + .AddDeliveryApi() + .AddComposers() + .Build(); + +builder.Services.AddTransient, ConfigureStaticFileOptions>(); +``` + +## Modify the `Cache-Control` header for ImageSharp.Web + +For setting `Cache-Control` max-age header for images processed by the ImageSharp middleware, you can set the `Umbraco:CMS:Imaging:Cache:BrowserMaxAge` setting. + +See the [Images Settings](configuration/imagingsettings.md) article for more information. + +## Add the `Cache-Control` header for rendering using the [ResponseCache attribute](https://learn.microsoft.com/en-us/aspnet/core/performance/caching/response?view=aspnetcore-6.0#responsecache-attribute) + +For example using a custom [Default Controller](../implementation/default-routing/controller-selection.md#change-the-default-controllers) you can add the ResponseCache attribute to the `Index` method + +```csharp +public class DefaultController : RenderController +{ + public DefaultController(ILogger logger, ICompositeViewEngine compositeViewEngine, IUmbracoContextAccessor umbracoContextAccessor) : base(logger, compositeViewEngine, umbracoContextAccessor) + { + } + + [ResponseCache(NoStore = true, Location = ResponseCacheLocation.None)] + public override IActionResult Index() + { + return CurrentTemplate(new ContentModel(CurrentPage)); + } +} +``` diff --git a/16/umbraco-cms/reference/routing/README.md b/16/umbraco-cms/reference/routing/README.md new file mode 100644 index 00000000000..ec399757c96 --- /dev/null +++ b/16/umbraco-cms/reference/routing/README.md @@ -0,0 +1,37 @@ +--- +description: >- + All about Umbraco's routing pipeline & the types of Controllers used in + Umbraco +--- + +# Routing & Controllers + +All about Umbraco's routing pipeline & the types of Controllers used in Umbraco, how they work, and what they are used for. + +## [Request Pipeline](request-pipeline/) + +Explains how Umbraco builds its URLs and how the URLs are mapped back to content items. + +## [Routing Properties](./) + +Describes the Umbraco special/reserved Property Type aliases that can be used to directly manipulate Umbraco's default routing pipeline. These special Property Type aliases can be useful when creating an Umbraco website. + +## [Surface Controllers](surface-controllers/) + +What is a Surface Controller and how to use it? + +## [Custom Controllers (hijacking routes)](./#custom-controllers-hijacking-routes) + +Creating custom controllers to have 100% full control over how your pages are rendered. Also known as: Hijacking Umbraco Routes + +## [Custom Routes](../../implementation/custom-routing/) + +How to specify your own custom MVC routes in your Umbraco application? + +## [Routes & Authentication](./#routes-and-authentication) + +Routing requirements for authenticated controllers for both the front end and the backoffice. + +## [URL Tracking](url-tracking.md) + +Moving and renaming Umbraco documents will lead to URL redirects being created. diff --git a/16/umbraco-cms/reference/routing/custom-controllers.md b/16/umbraco-cms/reference/routing/custom-controllers.md new file mode 100644 index 00000000000..8bbe0c101cc --- /dev/null +++ b/16/umbraco-cms/reference/routing/custom-controllers.md @@ -0,0 +1,415 @@ +--- +description: >- + Use a custom MVC controller to handle and control incoming requests for + content pages based on a specific Document Type, also called Route Hijacking. +--- + +# Custom MVC controllers (Umbraco Route Hijacking) + +_Use a custom controller to handle and control incoming requests for content pages based on a specific Document Type_ + +## What is Umbraco Route Hijacking? + +By default, all front-end requests to an Umbraco site are auto-routed via the 'Index' action of a core Controller: `Umbraco.Cms.Web.Common.Controllers.RenderController`. This core controller handles the incoming request, builds the associated PublishedContent model, and passes this to the appropriate Umbraco Template/MVC View. + +It is however possible to implement a custom Controller to replace this default implementation to give complete control over this execution. + +For example: + +* To enrich the view model passed to the template with additional properties (from other published content items or outside Umbraco) +* To implement serverside paging +* To implement any custom/granular security +* To return alternative templates depending on some custom business logic + +This replacement of the default controller can be made 'globally' for all requests (see last example). It can also be by _'hijacking'_ requests for types of pages based on their specific Document Type following this controller naming convention: `[DocumentTypeAlias]Controller`. + +## Creating a custom controller + +### Example: Hijacking route requests to a 'product' page + +In the following example, imagine an Umbraco site with a set of 'product' pages created from a Document Type called 'Product Page' with an alias 'productPage'. + +Create a custom locally declared controller in the Umbraco web application project named 'ProductPageController'. + +Ensure that this controller inherits from the base controller `Umbraco.Cms.Web.Common.Controllers.RenderController`. + +eg: + +```csharp +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.ViewEngines; +using Microsoft.Extensions.Logging; +using Umbraco.Cms.Core.Web; +using Umbraco.Cms.Web.Common.Controllers; + +namespace My.Website; + +public class ProductPageController : RenderController +{ + public ProductPageController(ILogger logger, ICompositeViewEngine compositeViewEngine, IUmbracoContextAccessor umbracoContextAccessor) + : base(logger, compositeViewEngine, umbracoContextAccessor) + { + } + + public override IActionResult Index() + { + // you are in control here! + + // return a 'model' to the selected template/view for this page. + return CurrentTemplate(CurrentPage); + } +} +``` + +All requests to any **product** pages in the site will be **hijacked** and routed through the custom ProductPageController. + +If you prefer to use an async controller your need to override both the sync and the async Index()-methods. This is done to disable the default behavior from the base controller. + +```csharp +public class ProductPageController : RenderController +{ + + [NonAction] + public sealed override IActionResult Index() => throw new NotImplementedException(); + public async Task Index(CancellationToken cancellationToken) + { + await SomethingAsync(cancellationToken); + return CurrentTemplate(CurrentPage); + } +} +``` + +This example shows the default behavior that Umbraco's core RenderController provides. The 'Index' action of the controller is executed, and the CurrentTemplate helper sends the model containing the details of the published content item related to the request to the relevant template/view. + +## Routing via template + +A further convention is that if an action on the controller has a name that matches the template name, this action will be executed instead of the default 'Index' action. + +### Example: Hijacking route requests to a 'product' for an alternative 'AMP' template + +In this example, the Product Page Document Type has two templates 'ProductPage' and 'ProductAmpPage'. We can hijack and handle the requests to the two templates differently. + +Create the Controller as before: + +```csharp +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.ViewEngines; +using Microsoft.Extensions.Logging; +using Umbraco.Cms.Core.Web; +using Umbraco.Cms.Web.Common.Controllers; + + +namespace My.Website; + +public class ProductPageController : RenderController +{ + public ProductPageController(ILogger logger, ICompositeViewEngine compositeViewEngine, IUmbracoContextAccessor umbracoContextAccessor) + : base(logger, compositeViewEngine, umbracoContextAccessor) + { + } + + // Any request for the 'ProductAmpPage' template will be handled by this Action + public IActionResult ProductAmpPage() + { + // Create AMP specific content here... + return CurrentTemplate(CurrentPage); + } + + public override IActionResult Index() + { + // you are in control here! + + // return a 'model' to the selected template/view for this page. + return CurrentTemplate(CurrentPage); + } +} +``` + +#### How can a page be requested via two different templates? + +The page in Umbraco will have a single 'template' selected as it's default template, but it's possible to call this same page on a different template by adding `?altTemplate=othertemplatename` to the Url QueryString eg: + +`/products/superfancyproduct/?altTemplate=ProductAmpPage` + +### Summary - How the route hijacking convention works + +* Document Type name = controller name +* Template name = action name (if no action matches or is not specified - then the 'Index' action will be executed). +* Controller Inherits from `Umbraco.Cms.Web.Common.Controllers.RenderController` + +## Returning a view with a custom model + +The steps to achieve this will differ, depending if your template views are using IPublishedContent or Modelsbuilder generated Models. + +### Changing the @inherits directive of your template + +By default, your Umbraco Template will be based on the `ContentModel` that the default `RenderController` passes through to it. + +The default inherits statement: + +```csharp +@inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage +``` + +or if you are using **modelsbuilder**: + +```csharp +@inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage +``` + +`<>` contains a model generated for each Document Type to give strongly typed access to the Document Type properties in the template view. + +To use a specific custom view model, the `@inherits` directive will need to be updated to reference your custom model using the `Umbraco.Cms.Web.Common.Views.UmbracoViewPage` format where 'T' is the type of your custom model. + +So for example, if your custom model is of type 'MyProductViewModel' then your `@inherits` directive will look like: + +```csharp +@inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage +``` + +{% hint style="info" %} +Views will likely specify a master view to use as the common layout for the site HTML. When using a custom view model it's necessary to make sure this doesn't conflict with any implementation in the master layout view. Eg. if your master layout view is inheriting from a specific model `UmbracoViewPage` and using a property from SpecificModel that isn't available in your custom model an exception will be thrown. To avoid this you could: + +* Keep your Master layout view 'generically typed', eg. only have `@inherits UmbracoViewPage`, and use Model.Value syntax to access properties. or +* Break the dependency on `Umbraco.Cms.Core.Models` in your master layout by having it instead inherit from `Umbraco.Cms.Web.Common.Views.UmbracoViewPage`. This would be where ISomeInterface is implemented by all your models and contains the properties that the master layout view uses. or +* Ensure your custom models inherit from whichever class is used to strongly type the master layout view. +{% endhint %} + +In most cases you will need your custom model to build upon the underlying existing PublishedContent model for the page. This can be achieved by making your custom model inherit from a special base class called `PublishedContentWrapped`: + +```csharp +public class MyProductViewModel : PublishedContentWrapped +{ + // The PublishedContentWrapped accepts an IPublishedContent item as a constructor + public MyProductViewModel(IPublishedContent content, IPublishedValueFallback publishedValueFallback) : base(content, publishedValueFallback) + { + } + + // Custom properties here... + public int StockLevel { get; set; } + public IEnumerable ProductDistributors { get; set; } +} +``` + +`PublishedContentWrapped` will take care of populating all the usual underlying Umbraco properties and means the `@Model.` syntax will continue to work in the layouts used by your template. + +Using Modelsbuilder you will find that all the generated models have a constructor that takes an IPublishedContent item in a similar way: + +```csharp +public class MyProductViewModel : ProductPage +{ + // The ProductPage model accepts an IPublishedContent item as a constructor + public MyProductViewModel(IPublishedContent content, IPublishedValueFallback publishedValueFallback) : base(content, publishedValueFallback) + { + } + + // Custom properties here... + public int StockLevel { get; set; } + public IEnumerable ProductDistributors { get; set; } +} +``` + +{% hint style="info" %} +The models generated by Modelsbuilder are created as partial classes so it's possible to extend them by adding your own partial classes with matching signature. +{% endhint %} + +We can now populate our custom view model in our controller and use the values from the custom model in our template view: + +```csharp +using System.Collections.Generic; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.ViewEngines; +using Microsoft.Extensions.Logging; +using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Web; +using Umbraco.Cms.Web.Common.Controllers; +using My.Website.Models; + + +namespace My.Website; + +public class ProductPageController : RenderController +{ + private readonly IVariationContextAccessor _variationContextAccessor; + private readonly ServiceContext _serviceContext; + public ProductPageController(ILogger logger, ICompositeViewEngine compositeViewEngine, IUmbracoContextAccessor umbracoContextAccessor, IVariationContextAccessor variationContextAccessor, ServiceContext context) + : base(logger, compositeViewEngine, umbracoContextAccessor) + { + _variationContextAccessor = variationContextAccessor; + _serviceContext = context; + } + + public override IActionResult Index() + { + + // you are in control here! + // create our ViewModel based on the PublishedContent of the current request: + // set our custom properties + var productViewModel = new MyProductViewModel(CurrentPage, new PublishedValueFallback(_serviceContext, _variationContextAccessor)) + { + StockLevel = 4, + ProductDistributors = new List() + }; + + + // return our custom ViewModel + return CurrentTemplate(productViewModel); + + } +} +``` + +and in our template + +```csharp +@inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage +@{ +Layout = "Master"; +} +

@Model.Name

+ +@(Model.Value("productDescription")) +// or using Modelsbuilder +@Model.ProductDescription + +
+
Stock Level
+
@Model.StockLevel
+
Distributors
+@foreach (var distributor in Model.Distributors){ +
@distributor.Name
+} +``` + +## Processing QueryString values in the controller + +You can also pass values directly into the controller action using the query string. + +``` +?page=1&andanotherthing=umbraco +``` + +The values in the `querystring` will be bound to the matching parameters defined in the controller's action: + +```csharp +public class ProductListingPageController : Umbraco.Cms.Web.Common.Controllers.RenderController +{ + //notice how we are no longer overriding the Index action because the signature is now different to the base signature. + [HttpGet] + public IActionResult Index([FromQuery(Name = "page")] int page, [FromQuery(Name = "andAnotherThing")] string andAnotherThing) + { + var products = _madeUpProductService.GetProductsByPage(page); + var productListingViewModel = new ProductListingViewModel(CurrentPage, new PublishedValueFallback(_serviceContext, _variationContextAccessor)); + productListingViewModel.Products = products; + productListViewModel.Thing = andAnotherThing; + + return CurrentTemplate(productListViewModel); + } +} +``` + +## Controller Injection + +Injecting services into your controller constructors is possible with Umbraco's underlying dependency injection implementation. See [Services and Helpers](../../implementation/services/#custom-services-and-helpers) for more info on this. + +For example: + +```csharp +public class ProductListingPageController : RenderController +{ + private readonly IMadeUpProductService _madeUpProductService; + + public ProductListingPageController(ILogger logger, ICompositeViewEngine compositeViewEngine, IUmbracoContextAccessor umbracoContextAccessor, IMadeUpProductService madeUpProductService) + { + _madeUpProductService = madeUpProductService; + } + + public override IActionResult Index() + { + var products = _madeUpProductService.GetProductsByPage(page); + ... + } +} +``` + +To wire up a concrete instance of IMadeUpProductService, use a composer: + +```csharp +using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Core.DependencyInjection; +using Umbraco.Extensions; + +namespace MyWebsite.Composers; + +public class RegisterSuperSiteServiceComposer : IUserComposer +{ + public void Compose(IUmbracoBuilder builder) + { + public class RegisterSuperSiteServiceComposer : IUserComposer + { + public void Compose(IUmbracoBuilder builder) + { + builder.Services.AddUnique(); + + } + } + } +} +``` + +See [Composing](../../implementation/composing.md) for further information. + +## Replace Umbraco's default `RenderController` + +You can replace Umbraco's default implementation of RenderController with your own custom controller for all MVC requests. This is possible by assigning your own default controller type in the Umbraco setup during initialization. + +You can achieve this by updating the options for `UmbracoRenderingDefaultsOptions` in `Program.cs`. + +First of all, you have to create your own controller. Your custom implementation of RenderController should either inherit from the core `RenderController` as in the examples above or implement the `IRenderController` interface. + +Implement the `IRenderController`: + +```csharp +public class MyRenderController : IRenderController +{ + public IActionResult Index() + { + return new OkObjectResult("Hello from your custom Render Controller"); + } +} +``` + +Or inherit from `RenderController` + +```csharp +public class MyRenderController : RenderController +{ + public MyRenderController(ILogger logger, ICompositeViewEngine compositeViewEngine, IUmbracoContextAccessor umbracoContextAccessor) + : base(logger, compositeViewEngine, umbracoContextAccessor) + { + } + + public override IActionResult Index() + { + // Add some custom logic here. + return CurrentTemplate(CurrentPage); + } +} +``` + +The last step is to configure Umbraco to use your implementation. You can do that in the `Program.cs` class. + +```csharp +builder.CreateUmbracoBuilder() + .AddBackOffice() + .AddWebsite() + .AddDeliveryApi() + .AddComposers() + .Build(); + +builder.Services.Configure(c => +{ + c.DefaultControllerType = typeof(MyRenderController); +}); +``` diff --git a/16/umbraco-cms/reference/routing/custom-middleware.md b/16/umbraco-cms/reference/routing/custom-middleware.md new file mode 100644 index 00000000000..0a6e4b4205d --- /dev/null +++ b/16/umbraco-cms/reference/routing/custom-middleware.md @@ -0,0 +1,67 @@ +--- +description: Customizing the ASP.NET middleware pipeline in Umbraco +--- + +# Custom Middleware + +Middleware is responsible for processing/customizing an incoming request before generating an outgoing response. Shortly defined the middleware is a step to check the requests before giving a response back. + +Umbraco automatically configures all required middleware in the `WithMiddleware()` method in a specific order based on the [Microsoft documentation](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/middleware/?view=aspnetcore-7.0#middleware-order). + +You can use Umbraco pipeline filters in case you want to add your own middleware before, in-between or after the default Umbraco middleware. Filters are added by configuring the `UmbracoPipelineOptions` and require an instance of `IUmbracoPipelineFilter` that contains the following callbacks: + +* `PrePipeline` - executed before any Umbraco-specific middleware is added, an example can be [URL rewrites](iisrewriterules.md). +* `PreRouting` - executed after the static files middleware and before the [routing middleware](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/routing?view=aspnetcore-7.0) is added (using `UseRouting()`). It can also be used to change the incoming URL. +* `PostRouting` - executed after the routing middleware is added and can be used to [configure Cross-origin resource sharing (CORS)](https://learn.microsoft.com/en-us/aspnet/core/security/cors?view=aspnetcore-7.0). +* `PostPipeline` - executed after all Umbraco-specific middleware is added. +* `Endpoints` - executed right before the Umbraco-specific endpoints are added using `WithEndpoints()`. + +The addition of the `PostRouting` callback is to allow correctly configuring the Cross-Origin Resource Sharing (CORS) middleware without having to use the `WithCustomMiddleware()` method. + +* `IUmbracoPipelineFilter` is an interface in Umbraco that allows the creation of custom filters which then modifies the behavior of the request pipeline. It can be used to change different aspects of how Umbraco handles incoming requests, such as changing content or adding security checks. +* `WithCustomMiddleware()` is a method that can be used in Umbraco for adding custom middleware. This includes some specific customizable instructions that run in the request processing pipeline. + +{% hint style="warning" %} +Using `WithCustomMiddleware()` instead of `WithMiddelware()` should only be used as a last resort. This is because Umbraco can break if you forget to add middleware or add them in the wrong order. +{% endhint %} + +## Configuring the Cross-Origin Resource Sharing (CORS) middleware + +Create a composer with the following: + +```csharp + +using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Web.Common.ApplicationBuilder; + +public class CorsComposer : IComposer +{ + public const string AllowAnyOriginPolicyName = nameof(AllowAnyOriginPolicyName); + + public void Compose(IUmbracoBuilder builder) + => builder.Services + .AddCors(options => options.AddPolicy(AllowAnyOriginPolicyName, policy => policy.AllowAnyOrigin())) + .Configure(options => options.AddFilter(new UmbracoPipelineFilter("Cors", postRouting: app => app.UseCors()))) + // For testing only + .Configure(options => options.AddFilter(new UmbracoPipelineFilter("CorsTest", endpoints: app => app.UseEndpoints(endpoints => + { + endpoints.MapGet("/echo", context => context.Response.WriteAsync("echo")).RequireCors(AllowAnyOriginPolicyName); + endpoints.MapGet("/echo2", context => context.Response.WriteAsync("echo2")); + })))); +} +``` + +You should be able to request `/echo` from any origin, but get an error when trying to fetch `/echo2` from a foreign origin. This can be tested by pasting the following JavaScript code in browser console when not on the local origin/Umbraco website (such as umbraco.com): + +```javascript +fetch('https://localhost:44331/echo', {method: 'GET'}).then(result => result.text().then(text => console.log(text))); +fetch('https://localhost:44331/echo2', {method: 'GET'}).then(result => result.text().then(text => console.log(text))); +``` + +This should return the following: + +

Browser result image

+ +An additional bonus is that this doesn't require any changes in your `Program.cs` file. It also allows packages to enable Cross-Origin Resource Sharing (CORS) and configure their own policies. + +Users that currently use `WithCustomMiddleware()` will need to add calls to `RunPreRouting()` and `RunPostRouting()`. This is similar to Umbraco adding additional middleware in future versions. diff --git a/16/umbraco-cms/reference/routing/custom-routes.md b/16/umbraco-cms/reference/routing/custom-routes.md new file mode 100644 index 00000000000..22e717586d2 --- /dev/null +++ b/16/umbraco-cms/reference/routing/custom-routes.md @@ -0,0 +1,491 @@ +--- +description: "Setting up your own controllers and routes that exist alongside the Umbraco pipeline." +--- + +# Custom MVC Routes + +_Documentation about how to setup your own custom controllers and routes that need to exist alongside the Umbraco pipeline_ + +## Where to put your routing logic? + +There's two places you can specify your routing, depending on whether it's in the context of a package, or your own site. If it's your own site you can do it in the `Program.cs` file, within the `WithEndpoints` method call like so: + +```csharp +app.UseUmbraco() + .WithMiddleware(u => + { + u.UseBackOffice(); + u.UseWebsite(); + }) + .WithEndpoints(u => + { + // This is where to put the custom routing + + u.UseBackOfficeEndpoints(); + u.UseWebsiteEndpoints(); + }); +``` + +If you're creating a package you won't have access to the `Program.cs` file. Instead you must use a composer, for an example of this, see the example below. + +## User defined routes + +Umbraco doesn't interfere with any user defined routes that you wish to have. Your custom routes to your own custom controllers will work perfectly and seamlessly alongside Umbraco's routes. + +## Custom routes within the Umbraco pipeline + +For a request to be considered executing in an Umbraco context, and therefore the Umbraco pipeline, it needs to have an HTTP request feature with the type `UmbracoRouteValues`, all the information required for Umbraco to handle the request is stored there. The question is now, how do we add this request feature? There's three possibilities: + +1. Do it manually - This requires that you have a custom route, controller, even middleware, and manually assign the `UmbracoRouteValues` as an HTTP request feature, however you see fit. To create an `UmbracoRouteValues` object generally requires: `IUmbracoContextAccessor` (to access the `CleanedUmbracoUrl`), `IPublishedRouter` (to create the `IPublishedRequestBuilder`), `IPublishedRequestBuilder` (to set the published content and to build the `IPublishedRequest`), `IPublishedRequest` to assign to the `UmbracoRouteValues`. As you can see this is a lot of work, but luckily there's some much easier ways. +2. Route a custom controller that implements the `IVirtualPageController` interface, assigning the `UmbracoRouteValues` to the HTTP requests will then be taken care of for you. +3. Route a custom controller with conventional routing, using the typical call to `endpoints.MapControllerRoute`, and then call `.ForUmbracoPage()` with an action for finding content on what `MapControllerRoute` returns, now `UmbracoRouteValues` will automatically be applied to any request to that controller. + +Don't fret if this all seems a bit overwhelming, we'll be going through an example of the last two options. + +### Custom route with IVirtualPageController + +As mentioned, with this approach we need to implement the `IVirtualPageController` interface, this interface only has one method `FindContent` which accepts an `ActionExecutingContext`: + +```csharp +IPublishedContent FindContent(ActionExecutingContext actionExecutingContext); +``` + +It can also be helpful to inherit from the `UmbracoPageController` since this includes some useful helper methods such as `CurrentPage`, do however note that it is _not_ possible to inherit from `RenderController` when doing custom routes like this. + +Let's create a shop controller, with an Index action showing all our products. We will also add a Product action showing custom data about the product that could exists outside Umbraco. A common approach in a scenario like this is to have a "real" Umbraco node as a starting point. In this example we will use an empty "Products" Document Type with a Collection, and "Product" Document Type containing a Stock-Keeping Unit (SKU). We also need some content based on those document types, a "Products" content node, which contains two product nodes, each with their own SKU. + +After that bit of setup we can go ahead and create our shop controller which inherits from `UmbracoPageController` and implements `IVirtualPageController`, it'll look like this: + +```csharp +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.AspNetCore.Mvc.ViewEngines; +using Microsoft.Extensions.Logging; +using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Web.Common.Controllers; + +namespace RoutingDocs.Controllers; + +public class ShopController : UmbracoPageController, IVirtualPageController +{ + public ShopController( + ILogger logger, + ICompositeViewEngine compositeViewEngine) + : base(logger, compositeViewEngine) + { } + + public IPublishedContent FindContent(ActionExecutingContext actionExecutingContext) + { } +} +``` + +Now you'll see that `FindContent` is complaining because we're not returning anything yet, but let's start by creating our to action methods that `FindContent` will find content for. + +First off we have the Index method: + +```csharp +[HttpGet] +public IActionResult Index() +{ + // CurrentPage (IPublishedContent) will be the content returned + // from the FindContent method. + + // return the view with the IPublishedContent + return View(CurrentPage); +} +``` + +With this method we return the view with the content found by the `FindContent` method. This can then be used to list all the children in the view with `Model.Children()`. + +Next we have our Product method: + +```csharp +[HttpGet] +public IActionResult Product(string id) +{ + // CurrentPage (IPublishedContent) will be the content returned + // from the FindContent method. + + // One example of using a custom route would be to include additional + // model information based on external services. For example, if + // we wanted to return the stores the product is available in from + // a custom data store. + var dbProduct = DbContext.Products.GetBySku(id); + var shopModel = new Product(CurrentPage) + { + Sku = id, + AvailableStores = dbProduct.AvailableStores + }; + + return View(shopModel); +} +``` + +This method is a bit more interesting, here we get some extra data from a different source, in this case a `DbContext`, but this can be anything you want, using the id we get from the route values. We use this extra data to create a custom model, wich includes the available stores, which we then render the view with. + +It's important to note that this custom model must implement `IPublishedContent`, to do this we inherit from the `ContentModel` class, in this case our model looks like this: + +```csharp +public class Product : ContentModel +{ + public Product(IPublishedContent content) : base(content) + { + } + + public string Sku { get; set; } + public IEnumerable AvailableStores { get; set; } +} +``` + +What's great about this is that we can use this model as a type argument when inheriting from `UmbracoViewPage` in our model like so: + +```html +@inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage +``` + +Which makes the model typed, so we can access the available stores like so: + +```html +
    + @foreach (var store in Model.AvailableStores) + { +
  • @store
  • + } +
+``` + +But let's get back to our controller, the last thing we need now is to implement `FindContent` method so we can find content for our actions and serve it to them. First we need to be able to get our content, and properties, so we need to inject `IUmbracoContextAccessor` and `IPublishedValueFallback` and save them to some fields like so: + +```csharp +private readonly IUmbracoContextAccessor _umbracoContextAccessor; +private readonly IPublishedValueFallback _publishedValueFallback; + +public ShopController( + ILogger logger, + ICompositeViewEngine compositeViewEngine, + IUmbracoContextAccessor umbracoContextAccessor, + IPublishedValueFallback publishedValueFallback) + : base(logger, compositeViewEngine) +{ + _umbracoContextAccessor = umbracoContextAccessor; + _publishedValueFallback = publishedValueFallback; +} +``` + +Now that we have our dependencies, and our action methods, we're finally ready to implement the `FindContent` method: + +```csharp +public IPublishedContent FindContent(ActionExecutingContext actionExecutingContext) +{ + if (_umbracoContextAccessor.TryGetUmbracoContext(out var umbracoContext)) + { + var productRoot = umbracoContext.Content.GetById(2074); + if (productRoot!=null) + { + + if (actionExecutingContext.ActionDescriptor is ControllerActionDescriptor controllerActionDescriptor) + { + // Check which action is executing + switch (controllerActionDescriptor.ActionName) + { + case nameof(Index): + return productRoot; + + case nameof(Product): + // Get the SKU/Id from the route values + if (actionExecutingContext.ActionArguments.TryGetValue("id", out var sku)) + { + return productRoot + .Children() + .FirstOrDefault(c => c.Value(_publishedValueFallback, "sku") == sku.ToString()); + } + else + { + return productRoot; + } + } + } + + return productRoot; + } + } + + return null; +} +``` + +We start off by getting our product root using the `UmbracoContext` to get it based off its id. Next we need to figure out what action is being requested. To do this we cast the `actionExecutingContext.ActionDescriptor` to a `ControllerActionDescriptor` and use its `ActionName` propperty. If the action name is index, we return the product root. If it's product, we try to get the SKU from the route value `id`. Then we try to find the child node which matches the SKU and return that. + +There's only one last thing to do. We need to register our shop controller. If you're creating a controller for your own site you can do it in the `Program.cs` like so: + +```csharp +app.UseUmbraco() + .WithMiddleware(u => + { + u.UseBackOffice(); + u.UseWebsite(); + }) + .WithEndpoints(u => + { + u.EndpointRouteBuilder.MapControllerRoute( + "Shop Controller", + "/shop/{action}/{id?}", + new {Controller = "Shop", Action = "Index"}); + + u.UseBackOfficeEndpoints(); + u.UseWebsiteEndpoints(); + }); +``` + +As you can see there's nothing Umbraco specific abouth the controller routing, it's using the default `MapController` route of the `EndpointRouteBuilder`, we give our mapping a name, a pattern for the controller and some default values, so if no action is specified it will default to `Index`. + +If you're creating a package you won't have access to the `Program.cs`, so instead you can use a composer with an `UmbracoPipelineFilter` like so: + +```csharp +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; +using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Core.DependencyInjection; +using Umbraco.Cms.Web.Common.ApplicationBuilder; + +namespace RoutingDocs.Controllers; + +public class ShopControllerComposer : IComposer +{ + public void Compose(IUmbracoBuilder builder) + { + builder.Services.Configure(options => + { + options.AddFilter(new UmbracoPipelineFilter(nameof(ShopController)) + { + Endpoints = app => app.UseEndpoints(endpoints => + { + endpoints.MapControllerRoute( + "Shop Controller", + "/shop/{action}/{id?}", + new {Controller = "Shop", Action = "Index"}); + }) + }); + }); + } +} +``` + +With that we have our controller with a custom route within an Umbraco context. + +#### Client-Side Requests + +If the endpoint of your custom route is considered a client-side request e.g. **/sitemap.xml**, you will need to make a few changes to get this to work. + +Define your route as before, specifying the correct client type route: + +```csharp +.WithEndpoints(u => +{ + u.EndpointRouteBuilder.MapControllerRoute("Sitemap Xml", "/sitemap.xml", + new { Controller = "SitemapXml", Action = "Index" }); +}); +``` + +You will need to configure your route request options within your **Program.cs** class. For single routes: + +```csharp +builder.Services.Configure(options => +{ + options.HandleAsServerSideRequest = httpRequest => httpRequest.Path.StartsWithSegments("/sitemap.xml"); +}); +``` + +Or it can handle multiple routes: + +```csharp +builder.Services.Configure(options => +{ + string[] allowList = new[] {"/sitemap.xml", ...}; + options.HandleAsServerSideRequest = httpRequest => + { + foreach (string route in allowList) + { + if (httpRequest.Path.StartsWithSegments(route)) + { + return true; + } + } + + return false; + }; +}); +``` + +In your **FindContent** method you should still be able to access and use **IUmbracoContextAccessor** through standard DI: + +``` +public IPublishedContent? FindContent(ActionExecutingContext actionExecutingContext) +{ + IUmbracoContext context = _umbracoContextAccessor.GetRequiredUmbracoContext(); + IPublishedContent? content = context.Content?.GetAtRoot().FirstOrDefault(); + + return content; +} +``` + +{% hint style="info" %} +There is currently a bug in all versions below 9.5, where this fix won't work for mapping a client-side request to an Umbraco Controller. See [https://github.com/umbraco/Umbraco-CMS/issues/12083](https://github.com/umbraco/Umbraco-CMS/issues/12083) for more details. v9.5 fixes this issue and it's recommended to update to the latest version! +{% endhint %} + +#### Attribute routing with IVirtualPageController + +One of the benefits of the `IVirtualPageController` is that it allows you to use attribute routing. If you wish to use attribute routing you must use an `IVirtualPageController` and decorate your controller and/or actions with the `Route` attribute. If we want to convert our above example into using attribute routing we must first add the attributes to our actions: + +```csharp +[Route("[controller]")] +[Route("[controller]/[action]")] +[HttpGet] +public IActionResult Index() +``` + +```csharp +[Route("[controller]/[action]/{id?}")] +[HttpGet] +public IActionResult Product(string id) +``` + +Now all we need to do is change our routing to use `EndpointRouteBuilder.MapControllers();` instead of adding a specific route. + +```csharp +app.UseUmbraco() + .WithMiddleware(u => + { + u.UseBackOffice(); + u.UseWebsite(); + }) + .WithEndpoints(u => + { + u.EndpointRouteBuilder.MapControllers(); + + u.UseBackOfficeEndpoints(); + u.UseWebsiteEndpoints(); + }); +``` + +This will give us routing that's similar to what we have in the other example. It's worth noting that there's no defaults when using attribute routing, so to allow our index action to be accessed through both `/shop` and `/shop/index`, we add two attributes, specifying both routes individually. + +### Custom route with ForUmbracoPage + +Making a custom route within the Umbraco context using `ForUmbracoPage` is similar to using `IVirtualPageController`. The main difference is that with `ForUmbracoPage` we no longer find the content from within the controller. Instead we assign the `FindContent` method when routing the controller. One important thing about `ForUmbracoPage` is that attribute routing is _not_ available. To make our example from above work with `ForUmbracoPage`, we want to remove any attribute routing, and no longer implement `IVirtualPageController`. We'll also remove the `FindContent` method, our controller will then end up looking like this: + +```csharp +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.ViewEngines; +using Microsoft.Extensions.Logging; +using RoutingDocs.Models; +using RoutingDocs.Persistence; +using Umbraco.Cms.Web.Common.Controllers; + +namespace RoutingDocs.Controllers; + +public class ShopController : UmbracoPageController +{ + public ShopController( + ILogger logger, + ICompositeViewEngine compositeViewEngine) + : base(logger, compositeViewEngine) + { } + + [HttpGet] + public IActionResult Index() + { + // CurrentPage (IPublishedContent) will be the content returned + // from the FindContent method. + + // return the view with the IPublishedContent + return View(CurrentPage); + } + + [HttpGet] + public IActionResult Product(string id) + { + // CurrentPage (IPublishedContent) will be the content returned + // from the FindContent method. + + // One example of using a custom route would be to include additional + // model information based on external services. For example, if + // we wanted to return the stores the product is available in from + // a custom data store. + var dbProduct = DbContext.Products.GetBySku(id); + var shopModel = new Product(CurrentPage) + { + Sku = id, + AvailableStores = dbProduct.AvailableStores + }; + + return View(shopModel); + } +} +``` + +As you can see we still inherit from `UmbracoPageController` to get access to the helper method `CurrentPage`, but the rest is a normal controller. + +The Umbraco magic will now instead happen where we route the controller, here we will pass a `Func` delegate to the `ForUmbracoPage` method, this delegate is then responsible for finding the content, for instance using a composer with the same logic as in the `IVirtualPageController` it will look like this: + +```csharp +public class ShopControllerComposer : IComposer +{ + public void Compose(IUmbracoBuilder builder) + { + builder.Services.Configure(options => + { + options.AddFilter(new UmbracoPipelineFilter(nameof(ShopController)) + { + Endpoints = app => app.UseEndpoints(endpoints => + { + endpoints.MapControllerRoute( + "Shop Controller", + "/shop/{action}/{id?}", + new {Controller = "Shop", Action = "Index"}) + .ForUmbracoPage(FindContent); // Here we register our FindContent method + }) + }); + }); + } + + private IPublishedContent FindContent(ActionExecutingContext actionExecutingContext) + { + // Resolve services from the container + var umbracoContextAccessor = actionExecutingContext.HttpContext.RequestServices + .GetRequiredService(); + var publishedValueFallback = actionExecutingContext.HttpContext.RequestServices + .GetRequiredService(); + + var umbracoContext = umbracoContextAccessor.GetRequiredUmbracoContext(); + var productRoot = umbracoContext.Content.GetById(2074); + + if (actionExecutingContext.ActionDescriptor is ControllerActionDescriptor controllerActionDescriptor) + { + // Check which action is executing + switch (controllerActionDescriptor.ActionName) + { + case nameof(ShopController.Index): + return productRoot; + + case nameof(ShopController.Product): + // Get the SKU/Id from the route values + if (actionExecutingContext.ActionArguments.TryGetValue("id", out var sku)) + { + return productRoot + .Children() + .FirstOrDefault(c => c.Value(publishedValueFallback, "sku") == sku.ToString()); + } + else + { + return productRoot; + } + } + } + return productRoot; + } +} +``` + +The `Compose` method of our composer is much the same as any other normal routing. The one difference is that we call `ForUmbracoPage` on the `MapControllerRoute` where we pass in our `FindContent` method. The `FindContent` method is almost the same as it was in the controller in the `IVirtualPageController` example, with one important difference. Since we can no longer inject our required service into the constructor, we instead request them using `actionExecutingContext.HttpContext.RequestServices.GetRequiredService`. You should _not_ save the `HttpContext` or the `IServiceProvider` you get from the `actionExecutingContext` to a field or property on the class. The reason for this is that they will be specific to each request. + +With this we have a custom routed controller within the Umbraco pipeline. If you navigate to `/shop` or `/shop/product/` you will see the controllers actions being called with the content found in `FindContent`.zspo diff --git a/16/umbraco-cms/reference/routing/iisrewriterules.md b/16/umbraco-cms/reference/routing/iisrewriterules.md new file mode 100644 index 00000000000..8863fb573c9 --- /dev/null +++ b/16/umbraco-cms/reference/routing/iisrewriterules.md @@ -0,0 +1,188 @@ +# URL Rewrites in Umbraco + +Since the release of Umbraco v9 and the change of the underlying web framework that is decoupled from the webserver, the way that you configure rewrites has changed as well. + +Instead of the URL Rewriting extension in IIS you can use the [URL Rewriting Middleware in ASP.NET Core](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/url-rewriting), which needs to be added to your project startup code first. + +{% hint style="info" %} +If you are running Umbraco v9+ on IIS you can still add a `web.config` file to configure IIS features such as URL rewrites. +{% endhint %} + +## When to use the URL Rewriting Middleware + +Make sure to check the official [URL Rewriting Middleware in ASP.NET Core](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/url-rewriting#when-to-use-url-rewriting-middleware) documentation for more information about when you should or should not use the URL Rewriting Middleware. + +## Using the URL Rewriting Middleware + +To use rewrites with Umbraco v9+ you have to register the middleware in your `Program.cs` by using the [`UseRewriter`](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.builder.rewritebuilderextensions.userewriter) extension method and then configure the rewrite options. + +### Example + +* Create an `IISUrlRewrite.xml` file in the root of your project (next to your `Program.cs` file) containing: + +```xml + + + + + + + + + + + + + + +``` + +* In the `Program.cs` file you can add the URL Rewriting Middleware before the call to `app.UseUmbraco()` and use [`AddIISUrlRewrite`](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.rewrite.iisurlrewriteoptionsextensions.addiisurlrewrite) to add the rewrite rules from the XML file: + +```csharp +using Microsoft.AspNetCore.Rewrite; + +var rewriteOptions = new RewriteOptions() + .AddIISUrlRewrite(builder.Environment.ContentRootFileProvider, "IISUrlRewrite.xml"); + +app.UseRewriter(rewriteOptions); + +// This line is needed for the rewrites to take effect. +app.UseStaticFiles(); +``` + +{% hint style="info" %} +On Linux, make sure to place the `app.UseStaticFiles()` after the `app.UseUmbraco()` statements for the redirect to work as intended. +{% endhint %} + +* In your csproj file add the XML file to a new item group and set `CopyToOutputDirectory` to `Always`: + +```xml + + + Always + + +``` + +{% hint style="info" %} +On Umbraco Cloud the item group needs to be set to `Always` for the file to be published to your deployed site. +{% endhint %} + +## Rewrite rule shortcuts + +`RewriteOptions` has a number of "shortcut" methods to implement commonly used rewrites including: + +* `AddRedirectToNonWww()` +* `AddRedirectToWww()` +* `AddRedirectToNonWwwPermanent()` +* `AddRedirectToWwwPermanent()` +* `AddRedirectToHttps()` + +For more details and other examples, take a look at the [URL Rewriting Middleware in ASP.NET Core](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/url-rewriting) and [RewriteOptions Class](https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.rewrite.rewriteoptions) documentation. + +## Examples of rewrite rules + +### External Resources + +If you are looking for additional inspiration or examples for creating IIS Rewrite rules, these external resources are a great starting point: + +* A great site showing 10 handy IIS Rewrite rules: [URL rewriting tips and tricks](https://ruslany.net/2009/04/10-url-rewriting-tips-and-tricks/) +* Another site showing some handy examples of IIS Rewrite rules: [Some useful IIS rewrite rules](https://odetocode.com/blogs/scott/archive/2014/03/27/some-useful-iis-rewrite-rules.aspx) +* If you needed to a lot of static rewrites using rewrite maps: [Rule with rewrite map rule template](https://www.iis.net/learn/extensions/url-rewrite-module/rule-with-rewrite-map-rule-template) + +### Example: Remove a Trailing Slash + +The following rule removes any trailing slashes from the URL. + +```xml + + + + + + + + + +``` + +Ensure Umbraco does not add a trailing slash by setting `AddTrailingSlash` to `false` in your [RequestHandler settings](../configuration/requesthandlersettings.md). + +### Example: Enforce HTTPS + +The following rule ensures your site only runs on HTTPS: + +```xml + + + + + + + + +``` + +### Example: Redirect Non-www to www + +The following rule redirects traffic from non-www to www (excluding the Umbraco Cloud project hostname): + +```xml + + + + + + + + + +``` + +### Example: Remove the .aspx Extension + +The following rule redirects `.aspx` URLs to their extensionless counterparts. + +```xml + + + + + + + + + + + + + + + + + +``` + +### Example: Custom Rewrite Rules for Umbraco Cloud + +An example configuration to help ensure your custom rules integrate properly: + +```xml + + + + + + + + + + + + +``` + +{% hint style="info" %} +If you use **Umbraco Cloud**, check the [Rewrite Rules](https://docs.umbraco.com/umbraco-cloud/set-up/project-settings/manage-hostnames/rewrites-on-cloud) article. +{% endhint %} diff --git a/16/umbraco-cms/reference/routing/request-pipeline/README.md b/16/umbraco-cms/reference/routing/request-pipeline/README.md new file mode 100644 index 00000000000..212435cea18 --- /dev/null +++ b/16/umbraco-cms/reference/routing/request-pipeline/README.md @@ -0,0 +1,25 @@ +--- +description: What the Umbraco Request Pipeline is +--- + +# Routing in Umbraco + +_This section describes what the Umbraco Request Pipeline is. It explains how Umbraco matches a document to a given request and how it generates a URL for a document._ + +## Request pipeline + +### What is the pipeline + +The request pipeline is the process of building up the URL for a node and resolving a request to a specified node. It ensures that the right content is sent back. + +![what is the pipeline](../../../../../10/umbraco-cms/reference/routing/request-pipeline/images/what-is-the-pipeline.png) + +### Outbound vs Inbound + +The pipeline works bidirectional: [**inbound**](inbound-pipeline.md) and [**outbound**](outbound-pipeline.md). + +[**Outbound**](outbound-pipeline.md) is the process of building up a URL for a requested node. [**Inbound**](inbound-pipeline.md) is every request received by the web server and handled by Umbraco. + +### Customizing the pipeline + +This section will describe the components that you can use to modify Umbraco's request pipeline: [**IContentFinder**](icontentfinder.md) & `IUrlProvider` diff --git a/16/umbraco-cms/reference/routing/request-pipeline/document/TheUmbracoRequestPipeline.pdf b/16/umbraco-cms/reference/routing/request-pipeline/document/TheUmbracoRequestPipeline.pdf new file mode 100644 index 00000000000..44fdfa7b829 Binary files /dev/null and b/16/umbraco-cms/reference/routing/request-pipeline/document/TheUmbracoRequestPipeline.pdf differ diff --git a/16/umbraco-cms/reference/routing/request-pipeline/find-publishedcontent-and-template.md b/16/umbraco-cms/reference/routing/request-pipeline/find-publishedcontent-and-template.md new file mode 100644 index 00000000000..b10ba0db146 --- /dev/null +++ b/16/umbraco-cms/reference/routing/request-pipeline/find-publishedcontent-and-template.md @@ -0,0 +1,64 @@ +# FindPublishedContentAndTemplate() + +The followed method is called on the "PublishedContentRequest.PrepareRequest()" method: `FindPublishedContentAndTemplate()`. We discuss shortly what this method is doing: + +1. FindPublishedContent () +2. Handles redirects +3. HandlePublishedContent() +4. FindTemplate() +5. FollowExternalRedirect() +6. HandleWildcardDomains() + +#### HandlePublishedContent + +* No content? +* Run the LastChanceFinder +* Is an IContentFinder, resolved by ContentLastChanceFinderResolver +* By default, is null (= ugly 404) +* Follow internal redirects +* Take care of infinite loops +* Ensure user has access to published content +* Else redirect to login or access denied published content +* Loop while there is no content +* Take care of infinite loops + +#### FindTemplate + +* Use altTemplate if +* Initial content +* Internal redirect content, and InternalRedirectPreservesTemplate is true +* No alternate template? +* Use the current template if one has already been selected +* Else use the template specified for the content, if any +* Alternate template? + * Use the alternate template, if any + * Else use what’s already there: a template, else none +* Alternate template is used only if displaying the intended content + * Except for internal redirects + * If you enable InternalRedirectPreservesTemplate + * Which is false by default +* Alternate template replaces whatever template the finder might have set + * ContentFinderByNiceUrlAndTemplate + * /path/to/page/template1?altTemplate=template2  template2 +* Alternate template does not falls back to the specified template for the content + * /path/to/page?altTemplate=missing  no template + * Even if the page has a template +* But preserves whatever template the finder might have set + * /path/to/page/template1?altTemplate=missing  template1 + +#### FollowExternalRedirect() + +* Content.GetPropertyValue("umbracoRedirect") +* If it’s there, sets the published content request to redirect to the content +* Will trigger an external (browser) redirect + +#### HandleWildcardDomains() + +![Culture and Hostnames](images/culture-and-hostnames-v14.png) + +* Finds the deepest wildcard domain between +* Domain root (or top) +* Request’s published content +* If found, updates the request’s culture accordingly + +This implements separation between hostnames and cultures diff --git a/16/umbraco-cms/reference/routing/request-pipeline/icontentfinder.md b/16/umbraco-cms/reference/routing/request-pipeline/icontentfinder.md new file mode 100644 index 00000000000..85756f224e9 --- /dev/null +++ b/16/umbraco-cms/reference/routing/request-pipeline/icontentfinder.md @@ -0,0 +1,215 @@ +--- +description: Information about creating your own content finders +--- + +# IContentFinder + +{% hint style="info" %} +The `IContentFinder` is not available when using the **Content Delivery API**. Create your own implementation of the`IApiContentPathResolver` interface to provide similar functionality. +{% endhint %} + +To create a custom content finder, with custom logic to find an Umbraco document based on a request, implement the IContentFinder interface: + +```csharp +public interface IContentFinder +{ + Task TryFindContent(IPublishedRequestBuilder contentRequest); +} +``` + +and use either an Umbraco builder extension, or a composer to add it to it to the `ContentFindersCollection`. + +Umbraco runs all content finders in the collection 'in order', until one of the IContentFinders returns true. Once this occurs, the request is then handled by that finder, and no further IContentFinders are executed. Therefore the order in which ContentFinders are added to the ContentFinderCollection is important. + +The ContentFinder can set the PublishedContent item for the request, or template or even execute a redirect. + +## Example + +This IContentFinders will find a document with id 1234, when the Url begins with /woot. + +```csharp +public class MyContentFinder : IContentFinder +{ + private readonly IUmbracoContextAccessor _umbracoContextAccessor; + + public MyContentFinder(IUmbracoContextAccessor umbracoContextAccessor) + { + _umbracoContextAccessor = umbracoContextAccessor; + } + + public Task TryFindContent(IPublishedRequestBuilder contentRequest) + { + var path = contentRequest.Uri.GetAbsolutePathDecoded(); + if (path.StartsWith("/woot") is false) + { + return Task.FromResult(false); // Not found + } + + if (!_umbracoContextAccessor.TryGetUmbracoContext(out var umbracoContext)) + { + return Task.FromResult(false); + } + + // Have we got a node with ID 1234 + var content = umbracoContext.Content.GetById(1234); + if (content is null) + { + // If not found, let another IContentFinder in the collection try. + return Task.FromResult(false); + } + + // If content is found, then render that node + contentRequest.SetPublishedContent(content); + return Task.FromResult(true); + } +} +``` + +### Adding and removing IContentFinders + +You can use either an extension on the Umbraco builder or a composer to access the `ContentFinderCollection` and add or remove specific `ContentFinders`. + +{% hint style="info" %} +Learn more about registering dependencies and when to use which method in the [Dependency Injection](../../using-ioc.md) article. +{% endhint %} + +#### Umbraco builder extension + +First create the extension method: + +```csharp +using RoutingDocs.ContentFinders; +using Umbraco.Cms.Core.DependencyInjection; +using Umbraco.Cms.Core.Routing; + +namespace RoutingDocs.Extensions; + +public static class UmbracoBuilderExtensions +{ + public static IUmbracoBuilder AddMyCustomContentFinders(this IUmbracoBuilder builder) + { + // Add our custom content finder just before the core ContentFinderByUrl + builder.ContentFinders().InsertBefore(); + // You can also remove content finders, this is not required here though, since our finder runs before the url one + builder.ContentFinders().Remove(); + // You use Append to add to the end of the collection + builder.ContentFinders().Append(); + // or Insert for a specific position in the collection + builder.ContentFinders().Insert(3); + return builder; + } +} +``` + +Then invoke in the `Program.cs` file: + +```csharp +builder.CreateUmbracoBuilder() + .AddBackOffice() + .AddWebsite() + .AddDeliveryApi() + .AddComposers() + .AddMyCustomContentFinders() + .Build(); +``` + +#### Composer + +```csharp +using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Core.DependencyInjection; +using Umbraco.Cms.Core.Routing; + +namespace RoutingDocs.ContentFinders; + +public class UpdateContentFindersComposer : IComposer +{ + public void Compose(IUmbracoBuilder builder) + { + // Add our custom content finder just before the core ContentFinderByUrl + builder.ContentFinders().InsertBefore(); + // You can also remove content finders, this is not required here though, since our finder runs before the url one + builder.ContentFinders().Remove(); + // You use Append to add to the end of the collection + builder.ContentFinders().Append(); + // or Insert for a specific position in the collection + builder.ContentFinders().Insert(3); + } +} +``` + +## NotFoundHandlers + +To set your own 404 finder create an IContentLastChanceFinder and set it as the ContentLastChanceFinder. (perhaps you have a multilingual site and need to find the appropriate 404 page in the correct language). + +A `IContentLastChanceFinder` will always return a 404 status code. This example creates a new implementation of the `IContentLastChanceFinder` and gets the 404 page for the current language of the request. + +```csharp +using Umbraco.Cms.Core.PublishedCache; +using Umbraco.Cms.Core.Routing; +using Umbraco.Cms.Core.Services; + +namespace RoutingDocs.ContentFinders; + +public class My404ContentFinder : IContentLastChanceFinder +{ + private readonly IDomainService _domainService; + private readonly IPublishedContentCache _publishedContentCache; + + public My404ContentFinder( + IDomainService domainService, + IPublishedContentCache publishedContentCache) + { + _domainService = domainService; + _publishedContentCache = publishedContentCache; + } + + public async Task TryFindContent(IPublishedRequestBuilder contentRequest) + { + // Find the root node with a matching domain to the incoming request + var allDomains = (await _domainService.GetAllAsync(true)).ToList(); + var domain = allDomains + .FirstOrDefault(f => f.DomainName == contentRequest.Uri.Authority + || f.DomainName == $"https://{contentRequest.Uri.Authority}" + || f.DomainName == $"http://{contentRequest.Uri.Authority}"); + + var siteId = domain != null ? domain.RootContentId : allDomains.Any() ? allDomains.FirstOrDefault()?.RootContentId : null; + + + var siteRoot = _publishedContentCache.GetById(false, siteId ?? -1); + + if (siteRoot is null) + { + return false; + } + + // Assuming the 404 page is in the root of the language site with alias fourOhFourPageAlias + var notFoundNode = siteRoot.Children()?.FirstOrDefault(f => f.ContentType.Alias == "fourOhFourPageAlias"); + + if (notFoundNode is not null) + { + contentRequest.SetPublishedContent(notFoundNode); + } + + // Return true or false depending on whether our custom 404 page was found + return contentRequest.PublishedContent is not null; + } +} +``` + +You can configure Umbraco to use your own implementation in the `Program.cs` file: + +```csharp +builder.CreateUmbracoBuilder() + .AddBackOffice() + .AddWebsite() + .AddDeliveryApi() + .AddComposers() + // If you need to add something Umbraco specific, do it in the "AddUmbraco" builder chain, using the IUmbracoBuilder extension methods. + .SetContentLastChanceFinder() + .Build(); +``` + +{% hint style="warning" %} +When adding a custom `IContentLastChanceFinder` to the pipeline any `Error404Collection`-settings in `appSettings.json` will be ignored. +{% endhint %} diff --git a/16/umbraco-cms/reference/routing/request-pipeline/images/backoffice-see-prod.png b/16/umbraco-cms/reference/routing/request-pipeline/images/backoffice-see-prod.png new file mode 100644 index 00000000000..ee802086d4d Binary files /dev/null and b/16/umbraco-cms/reference/routing/request-pipeline/images/backoffice-see-prod.png differ diff --git a/16/umbraco-cms/reference/routing/request-pipeline/images/culture-and-hostnames-v14.png b/16/umbraco-cms/reference/routing/request-pipeline/images/culture-and-hostnames-v14.png new file mode 100644 index 00000000000..d104f053c7a Binary files /dev/null and b/16/umbraco-cms/reference/routing/request-pipeline/images/culture-and-hostnames-v14.png differ diff --git a/16/umbraco-cms/reference/routing/request-pipeline/images/culture-and-hostnames-v8.png b/16/umbraco-cms/reference/routing/request-pipeline/images/culture-and-hostnames-v8.png new file mode 100644 index 00000000000..e5b3da02cad Binary files /dev/null and b/16/umbraco-cms/reference/routing/request-pipeline/images/culture-and-hostnames-v8.png differ diff --git a/16/umbraco-cms/reference/routing/request-pipeline/images/culture-and-hostnames.png b/16/umbraco-cms/reference/routing/request-pipeline/images/culture-and-hostnames.png new file mode 100644 index 00000000000..5c2570a56a6 Binary files /dev/null and b/16/umbraco-cms/reference/routing/request-pipeline/images/culture-and-hostnames.png differ diff --git a/16/umbraco-cms/reference/routing/request-pipeline/images/no-sitedomainhelp.png b/16/umbraco-cms/reference/routing/request-pipeline/images/no-sitedomainhelp.png new file mode 100644 index 00000000000..f5eb3192b10 Binary files /dev/null and b/16/umbraco-cms/reference/routing/request-pipeline/images/no-sitedomainhelp.png differ diff --git a/16/umbraco-cms/reference/routing/request-pipeline/images/path-example-v14.png b/16/umbraco-cms/reference/routing/request-pipeline/images/path-example-v14.png new file mode 100644 index 00000000000..46efababb12 Binary files /dev/null and b/16/umbraco-cms/reference/routing/request-pipeline/images/path-example-v14.png differ diff --git a/16/umbraco-cms/reference/routing/request-pipeline/images/path-example-v8.png b/16/umbraco-cms/reference/routing/request-pipeline/images/path-example-v8.png new file mode 100644 index 00000000000..ba2377d00d6 Binary files /dev/null and b/16/umbraco-cms/reference/routing/request-pipeline/images/path-example-v8.png differ diff --git a/16/umbraco-cms/reference/routing/request-pipeline/images/path-example.png b/16/umbraco-cms/reference/routing/request-pipeline/images/path-example.png new file mode 100644 index 00000000000..34f87c24f25 Binary files /dev/null and b/16/umbraco-cms/reference/routing/request-pipeline/images/path-example.png differ diff --git a/16/umbraco-cms/reference/routing/request-pipeline/images/simple-content-tree-v14.png b/16/umbraco-cms/reference/routing/request-pipeline/images/simple-content-tree-v14.png new file mode 100644 index 00000000000..b4340889592 Binary files /dev/null and b/16/umbraco-cms/reference/routing/request-pipeline/images/simple-content-tree-v14.png differ diff --git a/16/umbraco-cms/reference/routing/request-pipeline/images/simple-content-tree-v8.png b/16/umbraco-cms/reference/routing/request-pipeline/images/simple-content-tree-v8.png new file mode 100644 index 00000000000..bd3e00d55ee Binary files /dev/null and b/16/umbraco-cms/reference/routing/request-pipeline/images/simple-content-tree-v8.png differ diff --git a/16/umbraco-cms/reference/routing/request-pipeline/images/simple-content-tree.png b/16/umbraco-cms/reference/routing/request-pipeline/images/simple-content-tree.png new file mode 100644 index 00000000000..2ee40ebeb99 Binary files /dev/null and b/16/umbraco-cms/reference/routing/request-pipeline/images/simple-content-tree.png differ diff --git a/16/umbraco-cms/reference/routing/request-pipeline/images/staging-only-staging.png b/16/umbraco-cms/reference/routing/request-pipeline/images/staging-only-staging.png new file mode 100644 index 00000000000..d2fd4280321 Binary files /dev/null and b/16/umbraco-cms/reference/routing/request-pipeline/images/staging-only-staging.png differ diff --git a/16/umbraco-cms/reference/routing/request-pipeline/images/what-is-the-pipeline.png b/16/umbraco-cms/reference/routing/request-pipeline/images/what-is-the-pipeline.png new file mode 100644 index 00000000000..d6f646b606f Binary files /dev/null and b/16/umbraco-cms/reference/routing/request-pipeline/images/what-is-the-pipeline.png differ diff --git a/16/umbraco-cms/reference/routing/request-pipeline/images/zpqrtbnk-status-345125834434158592.png b/16/umbraco-cms/reference/routing/request-pipeline/images/zpqrtbnk-status-345125834434158592.png new file mode 100644 index 00000000000..2e54304afd1 Binary files /dev/null and b/16/umbraco-cms/reference/routing/request-pipeline/images/zpqrtbnk-status-345125834434158592.png differ diff --git a/16/umbraco-cms/reference/routing/request-pipeline/inbound-pipeline.md b/16/umbraco-cms/reference/routing/request-pipeline/inbound-pipeline.md new file mode 100644 index 00000000000..f98da3d49bd --- /dev/null +++ b/16/umbraco-cms/reference/routing/request-pipeline/inbound-pipeline.md @@ -0,0 +1,64 @@ +--- +description: How the Umbraco inbound request pipeline works +--- + +# Inbound request pipeline + +The inbound process is triggered by `UmbracoRouteValueTransformer` and then handled with the Published router. The [**published content request preparation**](published-content-request-preparation.md) process kicks in and creates a `PublishedRequestBuilder` which will be used to create a `PublishedContentRequest`. + +The `PublishedContentRequest` object represents the request which Umbraco must handle. It contains everything that will be needed to render it. All this occurs when the Umbraco modules knows that an incoming request maps to a document that can be rendered. + +```csharp +public class PublishedContentRequest +{ + public Uri Uri { get; } + … +} +``` + +There are 3 important properties, which contains all the information to find a node: + +```csharp +public bool HasDomain { get; } +public DomainAndUri Domain { get; } +public CultureInfo Culture { get; } +``` + +Domain is a DomainAndUri object that is a standard Domain plus the fully qualified uri. For example, the Domain may contain "example.com" whereas the Uri will be fully qualified for example "https://example.com/". + +It contains the content to render: + +```csharp +public bool HasPublishedContent { get; } +public IPublishedContent PublishedContent { get; set; } +public bool IsInternalRedirect { get; } +public bool IsRedirect {get; } +``` + +Contains template information: + +```csharp +public bool HasTemplate { get; } +public string GetTemplateAlias { get; } +public ITemplate Template {get; } +``` + +The published request is created using the `PublishedRequestBuilder`, which implements `IPublishedRequestBuilder`. It's only in this builder that it's possible to set values, such as domain, culture, published content, redirects, and so on. + +You can subscribe to the 'routing request' notification, which is published right after the `PublishedRequestBuilder` has been prepared, but before the request is built, and processed. Here you can modify anything in the request before it is built and processed! For example content, template, etc: + +```csharp +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Notifications; + +namespace Umbraco9.NotificationHandlers; + +public class PublishedRequestHandler : INotificationHandler +{ + public void Handle(RoutingRequestNotification notification) + { + var requestBuilder = notification.RequestBuilder; + // Do something with the IPublishedRequestBuilder here + } +} +``` diff --git a/16/umbraco-cms/reference/routing/request-pipeline/outbound-pipeline.md b/16/umbraco-cms/reference/routing/request-pipeline/outbound-pipeline.md new file mode 100644 index 00000000000..ee6d8d8a1e9 --- /dev/null +++ b/16/umbraco-cms/reference/routing/request-pipeline/outbound-pipeline.md @@ -0,0 +1,484 @@ +--- +description: How the Umbraco outbound request pipeline works +--- + +# Outbound request pipeline + +The **outbound pipeline** consists out of the following steps: + +1. [Create segments](outbound-pipeline.md#segments) +2. [Create paths](outbound-pipeline.md#paths) +3. [Create urls](outbound-pipeline.md#urls) + +To explain things we will use the following content tree: + +![Content Tree](images/simple-content-tree-v14.png) + +## 1. Create segments + +When the URL is constructed, Umbraco will convert every node in the tree into a segment. Each published Content item has a corresponding URL segment. + +In our example "Our Products" will become "our-products" and "Swibble" will become "swibble". + +The segments are created by the "Url Segment provider" + +### Url Segment Provider + +The DI container of an Umbraco implementation contains a collection of `UrlSegmentProviders`. This collection is populated during Umbraco boot up. Umbraco ships with a 'DefaultUrlSegmentProvider' - but custom implementations can be added to the collection. + +When the `GetUrlSegment` extension method is called for a content item + culture combination, each registered `IUrlSegmentProvider` in the collection is executed in 'collection order'. This continues until a particular `UrlSegmentProvider` returns a segment value for the content, and no further `UrlSegmentProviders` in the collection will be executed. If no segment is returned by any provider in the collection a `DefaultUrlSegmentProvider` will be used to create a segment. This ensures that a segment is always created, like when a default provider is removed from a collection without a new one being added. + +To create a new Url Segment Provider, implement the following interface: + +```csharp +public interface IUrlSegmentProvider +{ + string GetUrlSegment(IContentBase content, string? culture = null); +} +``` + +Note each 'culture' variation can have a different Url Segment! + +The returned string will be the Url Segment for this node. Any string value can be returned here but it cannot contain the URL segment separator character `/`. This would create additional "segments" - something like `5678/swibble` is not allowed. + +#### Example + +For the segment of a 'product page', add its unique SKU / product ref to the existing Url segment. + +```csharp +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Strings; + +namespace RoutingDocs.SegmentProviders; + +public class ProductPageUrlSegmentProvider : IUrlSegmentProvider +{ + private readonly IUrlSegmentProvider _provider; + + public ProductPageUrlSegmentProvider(IShortStringHelper stringHelper) + { + _provider = new DefaultUrlSegmentProvider(stringHelper); + } + + public string GetUrlSegment(IContentBase content, string? culture = null) + { + // Only apply this rule for product pages + if (content.ContentType.Alias != "productPage") + { + return null; + } + + var segment = _provider.GetUrlSegment(content, culture); + var productSku = content.GetValue("productSKU"); + return $"{segment}--{productSku}".ToLower(); + } +} +``` + +The returned string becomes the native Url segment - there is no need for any Url rewriting. + +For our "swibble" product in our example content tree the `ProductPageUrlSegmentProvider`, would return a segment `swibble--123xyz`. In this case, 123xyz is the unique product sku/reference for the swibble product. + +Register the custom UrlSegmentProvider with Umbraco, either using a composer or an extension method on the `IUmbracoBuilder`: + +```csharp +using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Core.DependencyInjection; + +namespace RoutingDocs.SegmentProviders; + +public class RegisterCustomSegmentProviderComposer : IComposer +{ + public void Compose(IUmbracoBuilder builder) + { + builder.UrlSegmentProviders().Insert(); + } +} +``` + +### The Default Url Segment Provider + +The Default Url Segment provider builds its segments by looking for one of the below values, checked in this order: + +1. A property with alias _umbracoUrlName_ on the node. (this is a convention led way of giving editors control of the segment name - with variants - this can vary by culture). +2. The 'name' of the content item e.g. `content.Name`. + +The Umbraco string extension `ToUrlSegment()` is used to produce a clean 'Url safe' segment. + +## 2. Create paths + +To create a path, the pipeline will use the segments of each node to produce a path. + +If we look at our example, the "swibble" node will receive the path: "/our-products/swibble". If we take the `ProductPageUrlSegmentProvider` from above, the path would become: "/our-products/swibble-123xyz". + +### Multiple sites in a single Umbraco implementation + +But, what if there are multiple websites in a single Umbraco Implementation? in this multi-site scenario then an (internal) path to a node such as "/our-products/swibble-123xyz" could belong to any of the sites, or match multiple nodes in multiple sites. In this scenario additional sites will have their internal path prefixed by the node id of their root node. Any content node with a hostname defines a “new root” for paths. + +![Path example](images/path-example-v14.png) + +| Node | Segment | Internal Path | +| ------------ | -------------- | ---------------------------- | +| Our Values | our-values | /our-values | +| Our Products | our-products | /our-products | +| Swibble | swibble-123xyz | /our-products/swibble-123xyz | +| Dibble | dibble-456abc | /our-products/dibble-456abc | +| Another Site | another-site | **9676**/ | +| Their Values | their-values | **9676**/their-values | + +Paths can be cached, what comes next cannot (http vs https, current request…). + +#### Some further considerations when **working with hostnames** + +* **Domain without path** e.g. "www.site.com" will become "1234/path/to/page" +* **Domain with path** e.g. "www.site.com/dk" will produce "1234/dk/path/to/page" as path +* **No domain specified**: "/path/to/page" +* **Unless HideTopLevelNodeFromPath config is true**, then the path becomes "/to/page" + +## 3. Creating Urls + +The Url of a node consists of a complete [URI](https://en.wikipedia.org/wiki/Uniform\_Resource\_Identifier): the Schema, Domain name, (port) and the path. + +In our example the "swibble" node could have the following URL: "[http://example.com/our-products/swibble](http://example.com/our-products/swibble)" + +Generating this url is handled by the Url Provider. The Url Provider is called whenever a request is made in code for a Url e.g.: + +```csharp +@Model.Url +@Umbraco.Url(1234) +@UmbracoContext.UrlProvider.GetUrl(1234); +``` + +The DI container of an Umbraco implementation contains a collection of `UrlProviders` this collection is populated during Umbraco boot up. Umbraco ships with a `DefaultUrlProvider` - but custom implementations can be added to the collection. When .Url is called each `IUrlProvider` registered in the collection is executed in 'collection order' until a particular `IUrlProvider` returns a value. (and no further `IUrlProviders` in the collection will be executed.) + +### DefaultUrlProvider + +Umbraco ships with a `DefaultUrlProvider`, which provides the implementation for the out-of-the-box mapping of the structure of the content tree to the URL. + +```csharp +// This one is initialized by default +public class NewDefaultUrlProvider : IUrlProvider +{ + public virtual UrlInfo GetUrl(IPublishedContent content, UrlMode mode, string? culture, Uri current) + {…} + + public virtual IEnumerable GetOtherUrls(int id, Uri current) + {…} +} +``` + +### How the Default Url provider works + +* If the current domain matches the root domain of the target content. + * Return a relative Url. + * Else must return an absolute Url. +* If the target content has only one root domain. + * Use that domain to build the absolute Url. +* If the target content has more than one root domain. + * Figure out which one to use. + * To build the absolute Url. +* Complete the absolute Url with scheme (http vs https). + * If the domain contains a scheme use it. + * Else use the current request’s scheme. +* If "addTrailingSlash" is true, then add a slash. +* Then add the virtual directory. + +If the URL provider encounters collisions when generating content URLs, it will always select the first available node and assign the URL to this one. The remaining nodes will be marked as colliding and will not have a URL generated. Fetching the URL of a node with a collision URL will result in an error string including the node ID (#err-1094) since this node does not currently have an active URL. This can happen if an umbracoUrlName property is being used to override the generated URL of a node, or in some cases when having multiple root nodes without hostnames assigned. + +{% hint style="warning" %} +This means publishing an unpublished node with a conflicting URL, might change the active node being rendered on that specific URL in cases where the published node should now take priority according to sort order in the tree! +{% endhint %} + +### Custom Url Provider + +Create a custom Url Provider by implementing `IUrlProvider` interface: + +```csharp +public interface IUrlProvider +{ + UrlInfo? GetUrl(IPublishedContent content, UrlMode mode, string? culture, Uri current); + + IEnumerable GetOtherUrls(int id, Uri current); +} +``` + +The URL returned in the 'UrlInfo' object by GetUrl can be completely custom. + +If implementing a custom Url Provider, consider the following things: + +* Cache things. +* Be sure to know how to handle schema's (http vs https) and hostnames. +* Inbound might require rewriting. + +{% hint style="info" %} +If there is only a small change to the logic around Url generation, then a smart way to create a custom Url Provider is to inherit from the DefaultUrlProvider and override the GetUrl() virtual method. +{% endhint %} + +#### Example + +Add /fish on the end of every URL. It's important to note here that since we're changing the outbound URL, but not how we handle URLs inbound, this **will** break the routing. In order to make the routing work again you have to implement a custom content finder, see [IContentFinder](icontentfinder.md) for more information on how to do that. + +{% hint style="warning" %} +The below example is using `ILocalizationService` which is currently obselete and will be removed in v15. Use `ILanguageService` or `IDictionaryItemService` (for dictionary item operations) instead. +{% endhint %} + +```csharp +using System; +using System.Collections.Generic; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; +using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.Routing; +using Umbraco.Cms.Core.Web; + +namespace RoutingDocs.UrlProviders; + +public class ProductPageUrlProvider : NewDefaultUrlProvider +{ + public ProductPageUrlProvider( + IOptionsMonitor requestSettings, + ILogger logger, + ISiteDomainMapper siteDomainMapper, + IUmbracoContextAccessor umbracoContextAccessor, + UriUtility uriUtility, + ILocalizationService localizationService) + : base(requestSettings, logger, siteDomainMapper, umbracoContextAccessor, uriUtility, localizationService) + { + } + + public override IEnumerable GetOtherUrls(int id, Uri current) + { + // Add custom logic to return 'additional urls' - this method populates a list of additional urls for the node to display in the Umbraco backoffice + return base.GetOtherUrls(id, current); + } + + public override UrlInfo? GetUrl(IPublishedContent content, UrlMode mode, string? culture, Uri current) + { + if (content is null) + { + return null; + } + + // Only apply this to product pages + if (content.ContentType.Alias == "productPage") + { + // Get the original base url that the DefaultUrlProvider would have returned, + // it's important to call this via the base, rather than .Url, or UrlProvider.GetUrl to avoid cyclically calling this same provider in an infinite loop!!) + UrlInfo? defaultUrlInfo = base.GetUrl(content, mode, culture, current); + if (defaultUrlInfo is null) + { + return null; + } + + if (!defaultUrlInfo.IsUrl) + { + // This is a message (eg published but not visible because the parent is unpublished or similar) + return defaultUrlInfo; + } + else + { + // Manipulate the url somehow in a custom fashion: + var originalUrl = defaultUrlInfo.Text; + var customUrl = $"{originalUrl}fish/"; + return new UrlInfo(customUrl, true, defaultUrlInfo.Culture); + } + } + // Otherwise return null + return null; + } +} +``` + +Register the custom UrlProvider with Umbraco: + +```csharp +using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Core.DependencyInjection; + +namespace RoutingDocs.UrlProviders; + +public class RegisterCustomUrlProviderComposer : IComposer +{ + public void Compose(IUmbracoBuilder builder) + { + builder.UrlProviders().Insert(); + } +} +``` + +{% hint style="info" %} +If you want to have multiple URL providers, you can add them one after the other with multiple `Insert` methods. Umbraco will cycle through all the providers registered until it finds one that doesn't return `null`. If all custom URL providers return `null` it will fall back to the default URL provider. The last added with `Insert` is the first that will be executed. +{% endhint %} + +### GetOtherUrls + +The GetOtherUrls method is only used in the Umbraco Backoffice to provide a list to editors of other Urls which also map to the node. + +For example, let's consider a convention-led `umbracoUrlAlias` property that enables editors to specify a comma-delimited list of alternative URLs for the node. It has a corresponding `AliasUrlProvider` registered in the `UrlProviderCollecton` to display this list to the Editor in the backoffice Info Content app for a node. + +### Url Provider Mode + +Specifies the type of URLs that the URL provider should produce, eg. absolute vs. relative URLs. Auto is the default + +These are the different modes: + +```csharp +public enum UrlMode +{ + /// + /// Indicates that the url provider should do what it has been configured to do. + /// + Default = 0, + + /// + /// Indicates that the url provider should produce relative urls exclusively. + /// + Relative, + + /// + /// Indicates that the url provider should produce absolute urls exclusively. + /// + Absolute, + + /// + /// Indicates that the url provider should determine automatically whether to return relative or absolute urls. + /// + Auto +} +``` + +Default setting can be changed in the `Umbraco:CMS:WebRouting` section of `appsettings.json`: + +```json +"Umbraco": { + "CMS": { + "WebRouting": { + "UrlProviderMode": "Relative" + } + } +} +``` + +See [WebRouting config reference documentation](../../configuration/webroutingsettings.md) for more information on routing settings. + +### Site Domain Mapper + +The `ISiteDomainMapper` implementation is used in the `IUrlProvider` and filters a list of `DomainAndUri` to pick one that best matches the current request. + +Create a custom SiteDomainMapper by implementing ISiteDomainMapper + +```csharp +public interface ISiteDomainMapper +{ + DomainAndUri? MapDomain(IReadOnlyCollection domainAndUris, Uri current, string? culture, string? defaultCulture); + IEnumerable MapDomains(IReadOnlyCollection domainAndUris, Uri current, bool excludeDefault, string? culture, string? defaultCulture); +} +``` + +The MapDomain methods will receive the Current Uri of the request, and custom logic can be implemented to decide upon the preferred domain to use for a site in the context of that request. The SiteDomainMapper's role is to get the current Uri and all eligible domains, and only return one domain which is then used by the UrlProvider to create the Url. + +Only a single `ISiteDomainMapper` can be registered with Umbraco. + +Register the custom `ISiteDomainMapper` with Umbraco using the `SetSiteDomainHelper` extension method + +```csharp +using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Core.DependencyInjection; +using Umbraco.Extensions; + +namespace RoutingDocs.SiteDomainMapper; + +public class RegisterCustomSiteDomainMapperComposer : IComposer +{ + public void Compose(IUmbracoBuilder builder) + { + builder.SetSiteDomainHelper(); + } +} +``` + +### Default SiteDomainMapper + +Umbraco ships with a default `SiteDomainMapper`. This has some useful functionality for grouping sets of domains together. With Umbraco Cloud, or another Umbraco development environment scenario, there maybe be multiple domains setup for a site 'live, 'staging', 'testing' or a separate domain to access the backoffice. Each domain will be setup as a 'Culture and Hostname' inside Umbraco. By default editors will see the full list of possible URLs for each of their content items on each domain, which can be confusing. If the additional URLs aren't present in Culture and Hostnames, then when testing the front-end of the site on a 'staging' URL, will result in navigation links taking you to the registered domain! + +![Culture and Hostnames multiple domains](images/culture-and-hostnames-v8.png) + +What the editor sees without any SiteDomainMapper, visiting the backoffice URL: + +![All domains listed](images/no-sitedomainhelp.png) + +Which is 'noise' and can lead to confusion: accidentally clicking the staging url, which is likely to be served from a different environment / different database etc may display the wrong content... + +To avoid this problem, use the default SiteDomainMapper's AddSite method to group Urls together. + +Since the SiteDomainMapper is registered in the DI, we can't consume it directly from a composer, so first create a component which adds the sites in the initialize method: + +```csharp +using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Core.Routing; + +namespace RoutingDocs.SiteDomainMapping; + +public class SiteDomainMapperComponent : IComponent +{ + private readonly SiteDomainMapper? _siteDomainMapper; + + public SiteDomainMapperComponent(ISiteDomainMapper siteDomainMapper) + { + // SiteDomainMapper can be overwritten, so ensure it's the default one which contains the AddSite + if (siteDomainMapper is SiteDomainMapper concreteSiteDomainMapper) + { + _siteDomainMapper = concreteSiteDomainMapper; + } + } + + public void Initialize() + { + _siteDomainMapper?.AddSite("backoffice", "umbraco-v8-backoffice.localtest.me", "umbraco-v8.localtest.me"); + _siteDomainMapper?.AddSite("preproduction", "umbraco-v8-preprod.localtest.me"); + _siteDomainMapper?.AddSite("staging", "umbraco-v8-staging.localtest.me"); + } + + public void Terminate() + { } +} +``` + +Then add the component with a composer: + +```csharp +using Umbraco.Cms.Core.Composing; + +namespace RoutingDocs.SiteDomainMapping; + +public class AddSiteComposer : ComponentComposer +{ +} +``` + +Now if an editor visits the backoffice via the staging url they will only see domains for the staging url: + +![Staging domain only](images/staging-only-staging.png) + +Now if an editor visits the backoffice via the backoffice url they will only see domains for the backoffice url and the production url: + +![Backoffice + production domains only](images/backoffice-see-prod.png) + +NB: it's not a 1-1 mapping, but a grouping. Multiple Urls can be added to a group. Think multilingual production and staging variations, and in the example above, if an editor logged in to the backoffice via the production url, eg umbraco-v8.localtest.me/umbraco - they would see the umbraco-v8-backoffice.localtest.me domain listed. + +#### Grouping the groupings - BindSites + +The SiteDomainMapper contains a 'BindSites' method that enables different site groupings to be bound together: + +```csharp +public void Initialize() +{ + _siteDomainMapper?.AddSite("backoffice", "umbraco-v8-backoffice.localtest.me", "umbraco-v8.localtest.me"); + _siteDomainMapper?.AddSite("preproduction", "umbraco-v8-preprod.localtest.me"); + _siteDomainMapper?.AddSite("staging", "umbraco-v8-staging.localtest.me"); + _siteDomainMapper?.BindSites("backoffice", "staging"); +} +``` + +Visiting the backoffice now via umbraco-v8-backoffice.localtest.me/umbraco would list all the 'backoffice' grouped domains AND all the 'staging' grouped domains. diff --git a/16/umbraco-cms/reference/routing/request-pipeline/published-content-request-preparation.md b/16/umbraco-cms/reference/routing/request-pipeline/published-content-request-preparation.md new file mode 100644 index 00000000000..79d8667f9b1 --- /dev/null +++ b/16/umbraco-cms/reference/routing/request-pipeline/published-content-request-preparation.md @@ -0,0 +1,99 @@ +--- +description: How Umbraco prepares content requests +--- + +# Published Content Request Preparation + +Is started in `UmbracoRouteValueTransformer` where it gets the `HttpContext` and `RouteValueDictionary` from the netcore framework: + +``` + async ValueTask TransformAsync(…) +``` + +What it does: + +* It ensures Umbraco is ready, and the request is a document request. +* Ensures there's content in the published cache, if there isn't it routes to the `RenderNoContentController` which displays the no content page you see when running a fresh install. +* Creates a published request builder. +* Routes the request with the request builder using the `PublishedRouter.RouteRequestAsync(…)`. + * This will handle redirects, find domain, template, published content and so on. + * Build the final `IPublishedRequest`. +* Sets the routed request in the Umbraco context, so it will be available to the controller. +* Create the route values with the `UmbracoRouteValuesFactory`. + * This is what actually routes your request to the correct controller and action, and allows you to hijack routes. +* Set the route values to the http context. +* Handles posted form data. +* Returns the route values to netcore so it routes your request correctly. + +## RouteRequestAsync + +When the `RouteRequestAsync` method is invoked on the `PublishedRouter` it will: + +* FindDomain(). +* Handle redirects. +* Set culture. +* Find the published content. + * Only if it doesn't exist, allowing you to handle it in a custom way with a custom router handler. +* Find the template. +* Set the culture (again, in case it was changed). +* Publish `RoutingRequestNotification`. +* Handle redirects and missing content. +* Initialize a few internal stuff. + +We will discuss a few of these steps below. + +### FindDomain() + +The FindDomain method looks for a domain matching the request Uri + +* Using a greedy match: `domain.com/foo` takes over `domain.com`. +* Sets published content request’s domain. +* If a domain was found. + * Sets published content request’s culture accordingly. + * Computes domain Uri based upon the current request (`domain.com` for `http://domain.com` or `https://domain.com`). +* Else. + * Sets published content request’s culture by default (first language, else system). + +### Find published content + +When finding published content the `PublishedRouter` will first check if the `PublishedRequestBuilder` already has content, if it doesn't the content finders will kick in. There a many different types of content finders, such as find by url, by id path, and more. If none of the content finders manages to find any content, the request will be set as 404, and the `ContentLastChanceFinder` will run, this will try to find a page to handle a 404, if it can't find one, the ugly 404 will be used. + +You can also implement your own content finders and last chance finder, for more information, see [IContentFinder](icontentfinder.md) + +The `PublishedRouter` will also follow any internal redirects there might be, it is however limited, as to not spiral out of control if there is an infite loop of redirects. + +### Find template + +Once the content has been found, the `PublishedRouter` moves on to finding the template. + +First off it checks if any content was found, if it wasn't it sets the template to null, since there can't be a template without content. + +Next it checks to see if there is an alternative template which should be used. An alternative template will be used if the router can find a value with the key "altTemplate", in either the querystring, form, or cookie, and there is content found by the contentfinders, so not the 404 page, or it's an internal redirect and the web routing setting has `InternalRedirectPreservesTemplate`. + +If no alternative template is found the router will get the template with the file service, using the ID specified on the published content, and then assign the template to the request. + +If an alternative template is specified, the router will check if it's an allowed template for the content, if the template is not allowed on that specific piece of content it will revert to using the default template. If the template is allowed it will then use the file service to get the specified alternative template and assign the template to the request. + +### Redirects + +The router will pick up the redirect and redirect. There is no need to write your own redirects: + +```csharp +public class PublishedRequestHandler : INotificationHandler +{ + public void Handle(RoutingRequestNotification notification) + { + var requestBuilder = notification.RequestBuilder; + var content = requestBuilder.PublishedContent; + var redirect = content.Value("myRedirect"); + if (!string.IsNullOrWhiteSpace(redirect)) + { + requestBuilder.SetRedirect(redirect); + } + } +} +``` + +## Missing template? + +In case the router can't find a template, it will try and verify if there's route hijacking in place, if there is, it will run the hijacked route. If route hijacking is not in place, the router will set the content to null, and run through the routing of the request again, in order for the last chance finder to find a 404. diff --git a/16/umbraco-cms/reference/routing/routing-properties.md b/16/umbraco-cms/reference/routing/routing-properties.md new file mode 100644 index 00000000000..167b2925fed --- /dev/null +++ b/16/umbraco-cms/reference/routing/routing-properties.md @@ -0,0 +1,34 @@ +--- +description: "Describes special property type aliases which can be used to customise routing" +--- + +# Routing properties + +_There are a few special/reserved Umbraco Property Type aliases that can be used which can manipulate how the standard Umbraco routing pipeline works. You can add these Property Types to any Document Type and if values are assigned to these properties, Umbraco will adjust its routing accordingly. See below for full details._ + +## umbracoRedirect + +Creating a property alias with this name and using a Content Picker property editor lets you create a 302 temporary redirect. +This in effect means that when a user navigates to this node, they will be redirected away from it. + +## umbracoInternalRedirectId + +Add this property alias to your Document Type with a Content Picker property editor and +Umbraco will load the selected page’s content transparently without performing any URL redirection. +This essentially performs a rewrite. + +## umbracoUrlName + +This property when created as a text string lets you provide a different URL name to what is created by default by the name of the node. +If you enter a value for this property and save/publish the content node you will see that its main URL is updated with a new path suffix. + +## umbracoUrlAlias + +This property when created as a text string lets you provide a comma separated +list of alternate full URL paths for the node. For example, if your URL was /some-category/some-page/content-node, +by adding an umbracoUrlAlias of "flowers", a user can navigate to the node by going to /flowers. +The URL alias remains in the browser address bar as a 'mask' over the real URL. You can also specify paths like "flowers/roses/red". + +## Filtering + +[See Filtering Property Conventions](../querying/ipublishedcontent/collections.md#filtering-conventions) diff --git a/16/umbraco-cms/reference/routing/surface-controllers/README.md b/16/umbraco-cms/reference/routing/surface-controllers/README.md new file mode 100644 index 00000000000..0479149cbcc --- /dev/null +++ b/16/umbraco-cms/reference/routing/surface-controllers/README.md @@ -0,0 +1,274 @@ +--- +description: "Information about Surface Controllers in Umbraco" +--- + +# Surface controllers + +_A surface controller is an MVC controller that interacts with the front-end rendering of an Umbraco page. They can be used for rendering view components and for handling form data submissions. Surface controllers are auto-routed, meaning that you don't have to add/create your own routes for these controllers to work._ + +## What is a surface controller? + +It is a regular ASP.NET Core MVC controller that: + +* Is auto-routed, meaning you don't have to setup any custom routes to make it work +* Is used for interacting with the front-end of Umbraco (not the backoffice) + +Since any surface controller inherits from the `Umbraco.Cms.Web.Website.Controllers.SurfaceController` class, the controller instantly supports many of the helper methods and properties that are available on the base `SurfaceController` class including `UmbracoContext`. Therefore, all surface controllers have native Umbraco support for: + +* Interacting with Umbraco routes during HTTP POSTs (i.e. `return CurrentUmbracoPage();` ) +* Rendering forms in Umbraco (i.e. `@using (Html.BeginUmbracoForm(...)){}` ) +* Rendering of ASP.NET Core MVC view components + +## Creating a surface controller + +Surface controllers are plugins, meaning they are found when the Umbraco application boots. There are 2 types of surface controllers: **locally declared** & **plugin based**. The main difference between the two is that a plugin based controller gets routed via an MVC area, which is defined in the controller (see below). Because a plugin based controller is routed via an MVC area, it means that the views can be stored in a custom folder specific to the package it is being shipped in. This can be done without interfering with the local developer's MVC view files. + +### Locally declared controllers + +A locally declared surface controller is one that is not shipped within an Umbraco package. It is created by the developer of the website they are creating. If you are planning on shipping a surface controller in an Umbraco package, then you will need to create a plugin based surface controller (see the next heading). + +To create a locally declared surface controller: + +* Create a controller that inherits from `Umbraco.Cms.Web.Website.Controllers.SurfaceController` +* The controller must be a public class. +* The controller must call the base constructor of `SurfaceController` +* The controller's name must be suffixed with the term `Controller` +* The controller must be inside a namespace + +For example: + +```csharp +using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Core.Cache; +using Umbraco.Cms.Core.Logging; +using Umbraco.Cms.Core.Routing; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Web; +using Umbraco.Cms.Infrastructure.Persistence; +using Umbraco.Cms.Web.Website.Controllers; + +namespace RoutingDocs.Controllers; + +public class MyController : SurfaceController +{ + public MyController( + IUmbracoContextAccessor umbracoContextAccessor, + IUmbracoDatabaseFactory databaseFactory, + ServiceContext services, + AppCaches appCaches, + IProfilingLogger profilingLogger, + IPublishedUrlProvider publishedUrlProvider) + : base(umbracoContextAccessor, databaseFactory, services, appCaches, profilingLogger, publishedUrlProvider) + { + } + + public IActionResult Index() + { + return Content("Hello world"); + } +} +``` + +#### Routing for locally declared controllers + +All locally declared controllers gets routed to: + + /umbraco/surface/{controllername}/{action}/{id} + +They do not get routed via an MVC area, so any views must exist in the following folders: + +* `/Views/{controllername}/` +* `/Views/Shared/` +* `/Views/` + +{% hint style="info" %} +If you get a 404 error when trying to access your surface controller, you may have forgotten to add a namespace to it! +{% endhint %} + +## Plugin based controllers + +If you are shipping a surface controller in a package, then you should definitely be creating a plugin based surface controller. The only difference between creating a plugin based controller and locally declared controller, is that you need to add an attribute to your class, which defines the MVC area you'd like your controller to be routed through. Here's an example: + +```csharp +using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Core.Cache; +using Umbraco.Cms.Core.Logging; +using Umbraco.Cms.Core.Routing; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Web; +using Umbraco.Cms.Infrastructure.Persistence; +using Umbraco.Cms.Web.Common.Attributes; +using Umbraco.Cms.Web.Website.Controllers; + +namespace SurfaceControllerPackage; + +[PluginController("SurfaceControllerPackage")] +public class MyController : SurfaceController +{ + public MyController( + IUmbracoContextAccessor umbracoContextAccessor, + IUmbracoDatabaseFactory databaseFactory, + ServiceContext services, + AppCaches appCaches, + IProfilingLogger profilingLogger, + IPublishedUrlProvider publishedUrlProvider) + : base(umbracoContextAccessor, databaseFactory, services, appCaches, profilingLogger, publishedUrlProvider) + { + } + + public IActionResult Index() + { + return Content("Hello world"); + } +} +``` + +In the above, the surface controller will belong to the MVC area called 'SurfaceControllerPackage'. Perhaps it is obvious, but if you are creating a package that contains many surface controllers, then you should most definitely ensure that all of your controllers are routed through the same MVC area. + +### Routing for plugin based controllers + +All plugin based controllers get routed to: + + /umbraco/{areaname}/{controllername}/{action}/{id} + +Since they get routed via an MVC area, your views should be placed in the following folder: + +* `~/App_Plugins/{areaname}/Views/{controllername}/` +* `~/App_Plugins/{areaname}/Views/Shared/` + +Since you're only able to place static filese within your package's `App_Plugin` folder, it's highly recommend to ensure that the area you use is the same as your package name, since that allows your views to be found. + +The controller itself should not be placed in the App_Plugins folder, the App_Plugins folder is for static files only, compiled files like the controller will be included in the dlls used by the nuget package. + +#### Protecting surface controller routes + +If you only want a surface controller action to be available when it's used within an Umbraco form and not from the auto-routed URL, you can add the `[ValidateUmbracoFormRouteString]` attribute to the action method. This can be especially useful for plugin based controllers, as this makes sure the actions can only be activated from a form whenever it's used within the website. + +```csharp +namespace RoutingDocs.Controllers; + +public class MyController : SurfaceController +{ + public MyController( + IUmbracoContextAccessor umbracoContextAccessor, + IUmbracoDatabaseFactory databaseFactory, + ServiceContext services, + AppCaches appCaches, + IProfilingLogger profilingLogger, + IPublishedUrlProvider publishedUrlProvider) + : base(umbracoContextAccessor, databaseFactory, services, appCaches, profilingLogger, publishedUrlProvider) + { + } + + [HttpPost] + [ValidateUmbracoFormRouteString] + public IActionResult HandleSubmit() + { + return RedirectToCurrentUmbracoPage(); + } +} +``` + +Whenever you render an Umbraco form within your view using `Html.BeginUmbracoForm(...)`, the forms action will be the URL of the current page (not the auto-routed URL of the surface controller). Umbraco will therefore add a hidden `ufprt` field to the form with an encrypted value containing the controller, action and optional area (known as the 'Umbraco form route string'). On form submission, this value is decrypted and Umbraco will activate the specified action of the surface controller. + +```html +@using (Html.BeginUmbracoForm("HandleSubmit")) +{ + +} +``` + +{% hint style="info" %} +In Umbraco 9 the `__RequestVerificationToken` token is automatically added to forms for you, so you no longer need to add `@Html.AntiForgeryToken()` to your forms. +{% endhint %} + +### Preventing Cross-Site Request Forgery (XSRF/CSRF) Attacks + +Cross-Site Request Forgery (CSRF) is an attack that forces an end user to execute unwanted actions on a web application in which they are currently authenticated. + +{% hint style="info" %} +By default, `Html.BeginUmbracoForm` and `Html.BeginForm` adds an antiforgery token. +{% endhint %} + +If the token is not added automatically, for instance, if you don't use `Html.BeginUmbracoForm` or use an overload to `Html.BeginForm` where you've set the `antiForgery` parameter to false, you can add it manually like so: + +```cs +@using (Html.BeginForm(nameof(ContactFormController.Submit), "ContactForm", new object(), FormMethod.Post, false /* this is where you disable it */, new object())) +{ + @Html.AntiForgeryToken() + // Add your form fields here +} +``` + +If you are using a SurfaceController the antiforgery token will automatically be validated. However, if you are using a standard (non-umbraco) controller, you can manually specify it with the `ValidateAntiForgeryToken` attribute: + +```cs +[HttpPost] +[ValidateAntiForgeryToken] +public ActionResult EditAction(FormViewModel formData) +{ + // Handle your form submit here +} +``` + +The `BeginUmbracoForm` and `BeginForm` will only add the antiforgery token to the form as a hidden input. This means that you have to manually handle this if you're sending the request via JavaScript, for example, ajax. + +The routing expects the antiforgery token to be in a header called `RequestVerificationToken`. You can use the `beforeSend` hook to read the antiforgery token and set it as a header if you're using ajax: + +```js +$.ajax({ + beforeSend: function (xhr) { + xhr.setRequestHeader("RequestVerificationToken", + $('input:hidden[name="__RequestVerificationToken"]').val()); + } +}); +``` + +For more information, see the [Antiforgery in ASP.NET Core](https://docs.microsoft.com/en-us/aspnet/core/security/anti-request-forgery?view=aspnetcore-6.0#antiforgery-in-aspnet-core-1) article. + +### Asynchronous surface controller actions + +Surface controller actions can be asynchronous. A common naming convention for asynchronous methods is using an `Async` suffix for the action name. However, this will not work by default due to the inner workings of ASP.NET Core MVC. + +Consider the following asynchronous surface controller action: + +```csharp +public class MySurfaceController : SurfaceController +{ + // ... + + [HttpPost] + [ValidateAntiForgeryToken] + public async Task SomeMethodAsync() + { + // do some async work here + return CurrentUmbracoPage(); + } +} +``` + +To use this action in a view you can add this: + +```html +@using (Html.BeginUmbracoForm(nameof(MySurfaceController.SomeMethodAsync), FormMethod.Post)) +{ + +} +``` + +But once you click the button, you will encounter an error message along the lines of: `InvalidOperationException: Could not find a Surface controller route in the RouteTable for controller name MySurface`. + +To counter this you need to instruct ASP.NET Core MVC to explicitly accept the `Async` suffix for controller names in the `program.cs`: + +```csharp +builder.Services.AddMvc(options => +{ + options.SuppressAsyncSuffixInActionNames = false; +}); +``` + +Alternatively you can rename your surface controller action so it does not contain the `Async` suffix. + +### Surface Controller Actions + +You can read more about the surface controller [action result helpers](surface-controllers-actions.md). diff --git a/16/umbraco-cms/reference/routing/surface-controllers/surface-controllers-actions.md b/16/umbraco-cms/reference/routing/surface-controllers/surface-controllers-actions.md new file mode 100644 index 00000000000..994df0d166b --- /dev/null +++ b/16/umbraco-cms/reference/routing/surface-controllers/surface-controllers-actions.md @@ -0,0 +1,190 @@ +--- +description: "Information about Surface Controller Actions Result Helpers in Umbraco" +--- + +# Surface controller actions + +A surface controller can return a few Umbraco specific actions. + +## CurrentUmbracoPage + +Returns the current Umbraco page. + +```csharp +namespace RoutingDocs.Controllers; + +public class MyController : SurfaceController +{ + public MyController( + IUmbracoContextAccessor umbracoContextAccessor, + IUmbracoDatabaseFactory databaseFactory, + ServiceContext services, + AppCaches appCaches, + IProfilingLogger profilingLogger, + IPublishedUrlProvider publishedUrlProvider) + : base(umbracoContextAccessor, databaseFactory, services, appCaches, profilingLogger, publishedUrlProvider) + { + } + + [HttpPost] + public IActionResult PostMethod() + { + if (!ModelState.IsValid) + { + return CurrentUmbracoPage(); + } + + return RedirectToCurrentUmbracoPage(); + } +} +``` + +## RedirectToCurrentUmbracoPage + +Redirects to the currently rendered Umbraco page. + +```csharp +namespace RoutingDocs.Controllers; + +public class MyController : SurfaceController +{ + public MyController( + IUmbracoContextAccessor umbracoContextAccessor, + IUmbracoDatabaseFactory databaseFactory, + ServiceContext services, + AppCaches appCaches, + IProfilingLogger profilingLogger, + IPublishedUrlProvider publishedUrlProvider) + : base(umbracoContextAccessor, databaseFactory, services, appCaches, profilingLogger, publishedUrlProvider) + { + } + + [HttpPost] + public IActionResult PostMethod() + { + if (!ModelState.IsValid) + { + return CurrentUmbracoPage(); + } + + return RedirectToCurrentUmbracoPage(); + } +} +``` + +This action can also take in a `QueryString` object to be included in the redirect. + +### Querystring parameter using a string value + +```csharp +namespace RoutingDocs.Controllers; + +public class MyController : SurfaceController +{ + public MyController( + IUmbracoContextAccessor umbracoContextAccessor, + IUmbracoDatabaseFactory databaseFactory, + ServiceContext services, + AppCaches appCaches, + IProfilingLogger profilingLogger, + IPublishedUrlProvider publishedUrlProvider) + : base(umbracoContextAccessor, databaseFactory, services, appCaches, profilingLogger, publishedUrlProvider) + { + } + + [HttpPost] + public IActionResult PostMethod() + { + var paramValue = "someValue"; + var queryString = QueryString.Create("param", paramValue); + return RedirectToCurrentUmbracoPage(queryString); + } +} +``` + +## RedirectToCurrentUmbracoUrl + +Redirects to the currently rendered Umbraco URL. + +```csharp +namespace RoutingDocs.Controllers; + +public class MyController : SurfaceController +{ + public MyController( + IUmbracoContextAccessor umbracoContextAccessor, + IUmbracoDatabaseFactory databaseFactory, + ServiceContext services, + AppCaches appCaches, + IProfilingLogger profilingLogger, + IPublishedUrlProvider publishedUrlProvider) + : base(umbracoContextAccessor, databaseFactory, services, appCaches, profilingLogger, publishedUrlProvider) + { + } + + [HttpPost] + public IActionResult PostMethod() + { + return RedirectToCurrentUmbracoUrl(); + } +} +``` + +## RedirectToUmbracoPage + +Redirects to a given Umbraco page. + +```csharp +namespace RoutingDocs.Controllers; + +public class MyController : SurfaceController +{ + public MyController( + IUmbracoContextAccessor umbracoContextAccessor, + IUmbracoDatabaseFactory databaseFactory, + ServiceContext services, + AppCaches appCaches, + IProfilingLogger profilingLogger, + IPublishedUrlProvider publishedUrlProvider) + : base(umbracoContextAccessor, databaseFactory, services, appCaches, profilingLogger, publishedUrlProvider) + { + } + + [HttpPost] + public IActionResult PostMethod() + { + // Gets the first child page of the current page + var childPage = CurrentPage.FirstChild(); + return RedirectToUmbracoPage(childPage); + } +} +``` + +You can also redirect to a page key (GUID). + +```csharp +namespace RoutingDocs.Controllers; + +public class MyController : SurfaceController +{ + public MyController( + IUmbracoContextAccessor umbracoContextAccessor, + IUmbracoDatabaseFactory databaseFactory, + ServiceContext services, + AppCaches appCaches, + IProfilingLogger profilingLogger, + IPublishedUrlProvider publishedUrlProvider) + : base(umbracoContextAccessor, databaseFactory, services, appCaches, profilingLogger, publishedUrlProvider) + { + } + + [HttpPost] + public IActionResult PostMethod() + { + var childPage = CurrentPage.FirstChild(); + return RedirectToUmbracoPage(childPage.Key); + } +} +``` + +There are overloads for adding a `QueryString` object. diff --git a/16/umbraco-cms/reference/routing/umbraco-api-controllers/README.md b/16/umbraco-cms/reference/routing/umbraco-api-controllers/README.md new file mode 100644 index 00000000000..78f902727fc --- /dev/null +++ b/16/umbraco-cms/reference/routing/umbraco-api-controllers/README.md @@ -0,0 +1,152 @@ +--- +description: A guide to implementing APIs in Umbraco projects +--- + +# Umbraco API Controllers + +This article describes how to work with API Controllers in Umbraco to create REST services. + +{% hint style="warning" %} +`UmbracoApiController` has been removed from Umbraco CMS as of version 15. + +Read the article [Porting old Umbraco APIs](porting-old-umbraco-apis.md) for more details. +{% endhint %} + +## What is an API? + +The Microsoft ASP.NET Core API documentation is a great place to familiarize yourself with API concepts. It can be found on the [official ASP.NET Core site](https://dotnet.microsoft.com/en-us/apps/aspnet/apis). + +## Public APIs in Umbraco + +A public API in Umbraco is created as any other ASP.NET Core API: + +{% code title="ProductsController.cs" %} +```csharp +using Microsoft.AspNetCore.Mvc; + +namespace UmbracoDocs.Samples; + +[ApiController] +[Route("/api/shop/products")] +public class ProductsController : Controller +{ + [HttpGet] + public IActionResult GetAll() => Ok(new[] { "Table", "Chair", "Desk", "Computer" }); +} +``` +{% endcode %} + +## Adding member protection to public APIs + +To protect your APIs based on front-end membership, you can annotate your API controllers with the `[UmbracoMemberAuthorize]` attribute. + +There are 3 parameters that can be supplied to control how the authorization works: + +```csharp +// Comma delimited list of allowed member types +string AllowType + +// Comma delimited list of allowed member groups +string AllowGroup + +// Comma delimited list of allowed member Ids +string AllowMembers +``` + +To allow all members, use the attribute without supplying any parameters. + +You can apply these attributes either at controller level or at action level. + +{% hint style="info" %} +Read more about members and member login in the [Member Registration and Login](../../../tutorials/members-registration-and-login.md) article. +{% endhint %} + +### Examples + +This will allow any logged in member to access all actions in the `ProductsController` controller: + +{% code title="ProductsController.cs" %} +```csharp +using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Web.Common.Filters; + +namespace UmbracoDocs.Samples; + +[ApiController] +[Route("/api/shop/products")] +[UmbracoMemberAuthorize] +public class ProductsController : Controller +{ + [HttpGet] + public IActionResult GetAll() => Ok(new[] { "Table", "Chair", "Desk", "Computer" }); +} +``` +{% endcode %} + +This will only allow logged in members of type "Retailers" to access the `GetAll` action: + +{% code title="ProductsController.cs" %} +```csharp +using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Web.Common.Filters; + +namespace UmbracoDocs.Samples; + +[ApiController] +[Route("/api/shop/products")] +public class ProductsController : Controller +{ + [HttpGet] + [UmbracoMemberAuthorize("Retailers", "", "")] + public IActionResult GetAll() => Ok(new[] { "Table", "Chair", "Desk", "Computer" }); +} +``` +{% endcode %} + +This will only allow members belonging to the "VIP" group to access any actions on the controller: + +{% code title="ProductsController.cs" %} +```csharp +using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Web.Common.Filters; + +namespace UmbracoDocs.Samples; + +[ApiController] +[Route("/api/shop/products")] +[UmbracoMemberAuthorize("", "VIP", "")] +public class ProductsController : Controller +{ + [HttpGet] + public IActionResult GetAll() => Ok(new[] { "Table", "Chair", "Desk", "Computer" }); +} +``` +{% endcode %} + +This will only allow the members with ids 1, 10 and 20 to access the `GetAll` action: + +{% code title="ProductsController.cs" %} +```csharp +using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Web.Common.Filters; + +namespace UmbracoDocs.Samples; + +[ApiController] +[Route("/api/shop/products")] +public class ProductsController : Controller +{ + [HttpGet] + [UmbracoMemberAuthorize("", "", "1,10,20")] + public IActionResult GetAll() => Ok(new[] { "Table", "Chair", "Desk", "Computer" }); +} +``` +{% endcode %} + +## Backoffice API Controllers + +Read the [Creating a Backoffice API article](../../../tutorials/creating-a-backoffice-api/README.md) for a comprehensive guide to writing APIs for the Management API. + +{% hint style="info" %} +The Umbraco Backoffice API is also known as the Management API. Thus, a Backoffice API Controller is often referred to as a Management API Controller. +{% endhint %} diff --git a/16/umbraco-cms/reference/routing/umbraco-api-controllers/porting-old-umbraco-apis.md b/16/umbraco-cms/reference/routing/umbraco-api-controllers/porting-old-umbraco-apis.md new file mode 100644 index 00000000000..1ecd5b536e3 --- /dev/null +++ b/16/umbraco-cms/reference/routing/umbraco-api-controllers/porting-old-umbraco-apis.md @@ -0,0 +1,97 @@ +--- +description: Tips to porting over API controllers from Umbraco 13 and below +--- + +# Porting old Umbraco APIs + +Umbraco 14 has obsoleted or removed base classes that were widely adopted for building APIs in previous versions of Umbraco. This article outlines the recommended approach for porting over APIs that were created before Umbraco 14. + +## Porting over `UmbracoApiController` implementations + +`UmbracoApiController` is obsolete from Umbraco 14 and will be removed in Umbraco 15. The recommended approach is to base APIs on the ASP.NET Core `Controller` class instead. + +`UmbracoApiController` would automatically route the API actions to `/umbraco/api/[ControllerName]/[ControllerAction]`. Moving forward, you control your API routes with the `[Route]` annotation. + +If you rely on the route previously generated by `UmbracoApiController`, you can still create identical routes. For example, the following `Controller` implementation: + +{% code title="ProductsController.cs" %} +```csharp +using Microsoft.AspNetCore.Mvc; + +namespace UmbracoDocs.Samples; + +[ApiController] +[Route("/umbraco/api/products")] +public class ProductsController : Controller +{ + [HttpGet("getall")] + public IActionResult GetAll() => Ok(new[] { "Table", "Chair", "Desk", "Computer" }); +} +``` +{% endcode %} + +...is routing-wise equivalent to this `UmbracoApiController` implementation: + +{% code title="ProductsController.cs" %} +```csharp +using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Web.Common.Controllers; + +namespace UmbracoDocs.Samples; + +public class ProductsController : UmbracoApiController +{ + public IActionResult GetAll() => Ok(new[] { "Table", "Chair", "Desk", "Computer" }); +} +``` +{% endcode %} + +In both cases, the API endpoint will be available at `/umbraco/api/products/getall`. + +## Porting over "plugin based" implementations + +In previous versions of Umbraco, it was possible to route controllers to dedicated areas by annotating `UmbracoApiController` implementations with `[PluginController]`. This annotation would automatically route the API actions to `/Umbraco/[PluginAreaName]/[ControllerName]/[ActionName]`. + +As this approach was based on `UmbracoApiController`, it is no longer viable - use the `[Route]` annotation instead. For example, the following `Controller` implementation: + +{% code title="ProductsController.cs" %} +```csharp +using Microsoft.AspNetCore.Mvc; + +namespace UmbracoDocs.Samples; + +[ApiController] +[Route("/umbraco/shop/products")] +public class ProductsController : Controller +{ + [HttpGet("getall")] + public IActionResult GetAll() => Ok(new[] { "Table", "Chair", "Desk", "Computer" }); +} +``` +{% endcode %} + +...is routing wise equivalent to this `[PluginController]` annotated implementation: + +{% code title="ProductsController.cs" %} +```csharp +using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Web.Common.Attributes; +using Umbraco.Cms.Web.Common.Controllers; + +namespace UmbracoDocs.Samples; + +[PluginController("shop")] +public class ProductsController : UmbracoApiController +{ + public IActionResult GetAll() => Ok(new[] { "Table", "Chair", "Desk", "Computer" }); +} +``` +{% endcode %} + +In both cases, the API endpoint will be available at `/umbraco/shop/products/getall`. + +## Backoffice API Controllers + +The `UmbracoAuthorizedApiController` and `UmbracoAuthorizedJsonController` base classes have been removed in Umbraco 14. Moving forward, backoffice APIs (also known as Management APIs) must be based on the `ManagementApiControllerBase`. + +Read the [Creating a Backoffice API article](../../../tutorials/creating-a-backoffice-api/README.md) for a comprehensive guide to writing APIs for the Management API. diff --git a/16/umbraco-cms/reference/routing/url-tracking.md b/16/umbraco-cms/reference/routing/url-tracking.md new file mode 100644 index 00000000000..939b8728b27 --- /dev/null +++ b/16/umbraco-cms/reference/routing/url-tracking.md @@ -0,0 +1,41 @@ +--- +description: "URL redirect management in Umbraco" +--- + +# URL Redirect Management + +## User Overview + +Whenever a document is published, and this causes changes to its URL (and any of its descendants' URLs), Umbraco makes a note of the old URLs. Whenever an incoming request is served and the default content finders cannot find a matching published document, Umbraco checks whether the URL matches one of these saved URLs. If a match is found, Umbraco returns a "301 Redirect" response pointing to the new URL of the document. + +The URL Redirect Management functionality does not support rewriting "rules" (e.g. regular expressions), nor complex scenarios (e.g. changing the culture and hostnames configuration). There are already powerful solutions to deal with these types of situations, such as Microsoft's own [Url Rewrite](https://www.iis.net/downloads/microsoft/url-rewrite) module for IIS. Since netcore is decoupled from the webserver hosting it, your approach for URL rewriting, outside what Umbraco provide out of the box, will depend on what you use to host your solutions, but for more info on the IIS Url Rewrite module have a look at the [official documentation](https://docs.microsoft.com/en-us/iis/extensions/url-rewrite-module/url-rewrite-module-configuration-reference). + +## Dashboard + +It is possible to list the redirect URLs via the *Redirect Url Management* dashboard in the *Content* section. This dashboard lists the original URL, new URL, and culture. It also allows you to delete a URL redirect. + +In addition, the dashboard can be used to disable or enable the 301 Redirect Management (via the `appsettings.json` configuration option described below - note that this requires an application restart to take effect). + +## Technical Overview + +Anytime a document is published and its corresponding *url segment* changes, Umbraco checks its URL (and all its descendants' URLs) for changes. For every URL that has changed, it creates (or updates) a row in the `umbracoRedirectUrl` table. Rows in this table contain: the old url, the create date, and the target content identifier, culture, and a url hash. + +Umbraco registers a new content finder, `ContentFinderByRedirectUrl`, which runs as a normal content finder after the other content finders. It looks for the incoming URL in the database table and, if found, computes the URL of the target document and returns a "301 Redirect". These redirects are considered "permanent". It's good to note that we explicitly set `no-cache` headers on these redirects so that when they change, browsers update the URL immediately. They are a "true" 301, however, and search engines will accept them as such. + +## Enable / Disable / Configure + +The 301 Redirect Management feature is enabled by default. + +It is possible to disable the feature entirely (both generating URLs in the database table, and running the content finder) by editing the `appsettings.json` file: + +```json +"Umbraco": { + "CMS": { + "WebRouting": { + "DisableRedirectUrlTracking": false + } + } +} +``` + +See [the web routing config reference](../configuration/webroutingsettings.md) for more configuration options diff --git a/16/umbraco-cms/reference/scheduling.md b/16/umbraco-cms/reference/scheduling.md new file mode 100644 index 00000000000..5ef2ce78f9a --- /dev/null +++ b/16/umbraco-cms/reference/scheduling.md @@ -0,0 +1,321 @@ +--- +description: Run a background job on a recurring basis +--- + +# Scheduling + +It is possible to run recurring code using a recurring background job. Background job classes should implement the `IRecurringBackgroundJob` interface, which controls when and where the job gets run. + +Once you have created your background job class, register it using a Composer. It will be detected at startup and a new `HostedService` will be created to run your job. + +{% hint style="warning" %} +Be aware you may or may not want this background job to run on all servers. If you are using Load Balancing with multiple servers, see [load balancing documentation](../fundamentals/setup/server-setup/load-balancing/) for more information +{% endhint %} + +## `IRecurringBackgroundJob` + +- `Period` - this property is a `TimeSpan` that tells Umbraco how often to run your job. + + ```c# + // Run this job every 5 minutes + TimeSpan Period = TimeSpan.FromMinutes(5); + ``` + +- `Delay` - this property is also a `TimeSpan` that tells Umbraco how long to wait after starting up before running the task for the first time. The default is 3 minutes. + + ```c# + // Wait 3 minutes after application startup before starting to run this job. + TimeSpan Delay = TimeSpan.FromMinutes(3); + ``` + +- `ServerRoles` - a list of roles that should run this job. In a multi-server setup, you may want your job to run on _all_ servers, or only on _one_ of your servers. + For example, a temporary file cleanup task might need to run on all servers. A database import job might be better to be run once per day on a single server. + + The default value is: `{ Single, SchedulingPublisher }`, which will cause the job to only run on _one_ server. + + ```c# + // Run this job on ALL servers + ServerRole[] ServerRoles = Enum.GetValues(); + ``` + + For more information about server roles, see the [Load Balancing](../fundamentals/setup/server-setup/load-balancing#scheduling-and-server-role-election) documentation. + +- `PeriodChanged` - an event you can trigger to tell the background job service that the period has changed. For example, if the period for your job is controlled by a configuration file setting. + + ```c# + // No-op event as the period never changes on this job + public event EventHandler PeriodChanged { add { } remove { } } + ``` + + See below for a full example of how to implement the PeriodChanged event. + +- `RunJobAsync()` - the main method of your job. + + ```c# + public Task RunJobAsync() { + // your job code goes here + } + ``` + +## Minimal example + +This example shows the minimum code necessary to implement the `IRecurringBackgroundJob` interface. The job runs every 60 minutes on all servers. + +```csharp +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Logging; +using Umbraco.Cms.Core.Scoping; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; +using Umbraco.Cms.Infrastructure.BackgroundJobs; + +namespace Umbraco.Docs.Samples.Web.RecurringBackgroundJob; + +public class CleanUpYourRoom : IRecurringBackgroundJob +{ + public TimeSpan Period { get => TimeSpan.FromMinutes(60); } + + // Runs on all servers + public ServerRole[] ServerRoles { get => Enum.GetValues(); } + + // No-op event as the period never changes on this job + public event EventHandler PeriodChanged { add { } remove { } } + + public async Task RunJobAsync() + { + // YOUR CODE GOES HERE + } +} +``` + +## Example with dependency injection + +This example shows how to inject other Umbraco services into your background job. This example cleans the recycle bin every 60 minutes. To do so, it injects an `IContentService` to access the Recycle bin and an `IScopeProvider` to provide an ambient scope for the `EmptyRecycleBin` method. + +```csharp +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Logging; +using Umbraco.Cms.Core.Scoping; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; +using Umbraco.Cms.Infrastructure.BackgroundJobs; + +namespace Umbraco.Docs.Samples.Web.RecurringBackgroundJob; + +public class CleanUpYourRoom : IRecurringBackgroundJob +{ + public TimeSpan Period { get => TimeSpan.FromMinutes(60); } + + // Runs on all servers + public ServerRole[] ServerRoles { get => Enum.GetValues(); } + + // No-op event as the period never changes on this job + public event EventHandler PeriodChanged { add { } remove { } } + + private readonly IContentService _contentService; + private readonly ICoreScopeProvider _scopeProvider; + + public CleanUpYourRoom( + IContentService contentService, + ICoreScopeProvider scopeProvider) + { + _contentService = contentService; + _scopeProvider = scopeProvider; + } + + public Task RunJobAsync() + { + // Wrap the three content service calls in a scope to do it all in one transaction. + using ICoreScope scope = _scopeProvider.CreateCoreScope(); + + int numberOfThingsInBin = _contentService.CountChildren(Constants.System.RecycleBinContent); + + if (_contentService.RecycleBinSmells()) + { + _contentService.EmptyRecycleBin(userId: -1); + } + // Remember to complete the scope when done. + scope.Complete(); + return Task.CompletedTask; + } +} +``` + +## Complex example + +The complex example builds on the previous one by injecting additional services. It includes a logger to log error messages, a profiler to capture timings, and an `IServerRoleAccessor` to log the current server role. Additionally, it injects an `IOptionsMonitor` to allow the period to be updated while the server is running. It also demonstrates how to trigger the `PeriodChanged` event to signal the job's host. + +```csharp +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Logging; +using Umbraco.Cms.Core.Scoping; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Sync; +using Umbraco.Cms.Infrastructure.BackgroundJobs; + +namespace Umbraco.Docs.Samples.Web.RecurringBackgroundJob; + +public class CleanUpYourRoom : IRecurringBackgroundJob +{ + public TimeSpan Period { get; set; } = TimeSpan.FromMinutes(60); + + // Runs on all servers + public ServerRole[] ServerRoles { get => Enum.GetValues(); } + + // + private event EventHandler? _periodChanged; + public event EventHandler PeriodChanged + { + add { _periodChanged += value; } + remove { _periodChanged -= value; } + } + + + private readonly IContentService _contentService; + private readonly IServerRoleAccessor _serverRoleAccessor; + private readonly IProfilingLogger _profilingLogger; + private readonly ILogger _logger; + private readonly ICoreScopeProvider _scopeProvider; + + + public CleanUpYourRoom( + IContentService contentService, + IServerRoleAccessor serverRoleAccessor, + IProfilingLogger profilingLogger, + ILogger logger, + ICoreScopeProvider scopeProvider, + IOptionsMonitor healthChecksSettings) + { + _contentService = contentService; + _serverRoleAccessor = serverRoleAccessor; + _profilingLogger = profilingLogger; + _logger = logger; + _scopeProvider = scopeProvider; + + // if the settings are updated trigger the event to let the job host know + healthChecksSettings.OnChange(x => + { + Period = x.Notification.Period; + _periodChanged?.Invoke(this, EventArgs.Empty); + }); + } + + public Task RunJobAsync() + { + + // Wrap the three content service calls in a scope to do it all in one transaction. + using ICoreScope scope = _scopeProvider.CreateCoreScope(); + + int numberOfThingsInBin = _contentService.CountChildren(Constants.System.RecycleBinContent); + _logger.LogInformation("Go clean your room - {ServerRole}", _serverRoleAccessor.CurrentServerRole); + _logger.LogInformation("You have {NumberOfThingsInTheBin} items to clean", numberOfThingsInBin); + + if (_contentService.RecycleBinSmells()) + { + // Take out the trash + using (_profilingLogger.TraceDuration("Mum, I am emptying out the bin", + "It's all clean now")) + { + _contentService.EmptyRecycleBin(userId: -1); + } + } + + // Remember to complete the scope when done. + scope.Complete(); + return Task.CompletedTask; + } +} +``` + +### Registering with a composer + +All we need to do here is to create the composer where we register the background job with `AddRecurringBackgroundJob`. + +```csharp +using Umbraco.Cms.Core.Composing; + +namespace Umbraco.Docs.Samples.Web.RecurringBackgroundJob; + +public class CleanUpYourRoomComposer : IComposer +{ + public void Compose(IUmbracoBuilder builder) + { + builder.Services.AddRecurringBackgroundJob(); + } +} +``` + +{% hint style="info" %} +Learn more about how to register dependencies in the [Dependency Injection](./using-ioc.md) article. +{% endhint %} + +## Base Classes + +`RecurringHostedServiceBase` is a low-level base class. It implements the dotnetcore interface `IHostedService` to run itself in the background, and creates and manages the timer that runs the job on a recurring basis. + +`RecurringBackgroundJobHostedService` is an Umbraco specific Hosted Service that extends `RecurringHostedServiceBase`. It uses some system-level Umbraco services to ensure that your jobs only execute once Umbraco is up and running. It checks: + +- Server Roles - see above for more discussion about Server roles + +- MainDom - The `MainDom` lock ensures that only one instance of Umbraco is running at a time on a given machine. This ensures the integrity of certain files used by Umbraco. See [Host Synchronization](../fundamentals/setup/server-setup/load-balancing/azure-web-apps.md#host-synchronization) for more details. + +- Runtime State - On a fresh install or when waiting for a database upgrade, Umbraco may be fully up and running yet. + +## Notifications + +The `RecurringBackgroundJobHostedService` publishes a number of notifications that can be hooked to report on the status of background jobs. All notifications extend from the base `Umbraco.CMS.Infrastructure.Notifications.RecurringBackgroundJobNotification` class. + +The following notifications are available: + +- Starting +- Started +- Stopping +- Stopped +- Executing +- Executed +- Failed +- Ignored + +### Start/Stop + +The Starting/Started and Stopping/Stopped notification pairs are published when the `RecurringBackgroundJobHostedService` is started or stopped. The start event normally occurs soon after application start as part of the .Net WebHost startup process. Similarly the stop event would happen as part of application shutdown. + +These notifications are there to support low-level debugging of background jobs to ensure they are starting/stopping correctly. Due to the timing of the notification, all handlers associated with these notifications should not depend on any Umbraco services, including database access. + +### Ignored + +The Ignored notification is published when a background job's schedule is triggered, but the Umbraco runtime checks prevent it from running. + +This notification is there to support low-level debugging of background jobs to ascertain why they are/aren't running. As the runtime checks include runtime state readiness, this event may be triggered during the install phase. Any notification handlers associated with this notification should **also** conduct their own checks before relying on Umbraco services, including database access. + +### Executing/Executed/Failed + +These notifications will be triggered in pairs depending on the success/failure of the job itself. + +- The executing notification is triggered before the job is run. +- The executed notification is triggered after the job is completed. +- The failed notification is triggered from the catch block if an exception is thrown. + +For **successful** job runs, the following notifications will be published: + +1. Executing +2. Executed + +For **failed** job runs, the following notifications will be published: + +1. Executing +2. Failed + +```csharp +// Do not run the code on subscribers or unknown role servers +// ONLY run for SchedulingPublisher server or Single server roles +switch (_serverRoleAccessor.CurrentServerRole) +{ + case ServerRole.Subscriber: + _logger.LogDebug("Does not run on subscriber servers."); + return Task.CompletedTask; // We return Task.CompletedTask to try again as the server role may change! + case ServerRole.Unknown: + _logger.LogDebug("Does not run on servers with unknown role."); + return Task.CompletedTask; // We return Task.CompletedTask to try again as the server role may change! +} +``` diff --git a/16/umbraco-cms/reference/searching/README.md b/16/umbraco-cms/reference/searching/README.md new file mode 100644 index 00000000000..43a71c22df0 --- /dev/null +++ b/16/umbraco-cms/reference/searching/README.md @@ -0,0 +1,18 @@ +# Searching + +_Search in Umbraco is powered by Examine out of the box, which is a Lucene-based search and index engine for Umbraco. Umbraco provides everything required to have powerful and fast search up and running on your website. You can also extend or replace the available configuration to exactly match your requirements. This documentation focuses on the Examine implementation._ + +## [Examine](examine/) + +{% hint style="info" %} +Examine and Lucene are external parts used in Umbraco. When working with either of the two, we strongly recommend using their official documentation. + +* [Lucene](https://lucenenet.apache.org/) +* [Examine](https://shazwazza.github.io/Examine/) +{% endhint %} + +Understand how Examine works and walk through the many available options and settings in Umbraco. + +## Umbraco Training + +{% include "../../.gitbook/includes/umbraco-searching-and-indexing-training-course.md" %} diff --git a/16/umbraco-cms/reference/searching/examine/README.md b/16/umbraco-cms/reference/searching/examine/README.md new file mode 100644 index 00000000000..b8c4b8f30e7 --- /dev/null +++ b/16/umbraco-cms/reference/searching/examine/README.md @@ -0,0 +1,33 @@ +# Examine + +_Examine uses Lucene as its search and index engine. Searching using Examine with Lucene can be powerful and fast._ + +## What is Examine? + +The Examine documentation is found here [https://shazwazza.github.io/Examine/](https://shazwazza.github.io/Examine/) and the source code repository for Examine is here [https://github.com/Shazwazza/Examine](https://github.com/Shazwazza/Examine). + +Examine allows you to index and search data quickly. Examine is a library that sits on top of [Lucene.Net](https://lucenenet.apache.org/), a high performance search engine library. Examine provides APIs to make searching and indexing as straight forward as possible. Umbraco provides a further layer on top, UmbracoExamine. This opens up the Umbraco-specific APIs for indexing and searching content and media out of the box. + +Examine is provider based so it is extensible and allows you to configure your own custom indexes if required. The backoffice search in Umbraco also uses this same search engine, so you can trust that you're in good hands. + +## [Quick start](quick-start.md) + +Get up and running with Examine straight away with this quick start guide. + +## [Customizing indexes](indexing.md) + +Learn how to customize the built in Umbraco indexes and how to create your own Lucene indexes using Examine in Umbraco. + +## [PDF indexing and multisearchers](pdfindex-multisearcher.md) + +Learn how to index PDF files in Examine and how to create a multisearcher that searches through both the External Index and the Pdf Index. + +## [Examine Management in the backoffice](examine-management.md) + +Provides an overview of the available Examine functionality available directly within the Umbraco backoffice. + +Details about subscribing to Examine events which can provide a way to modify the data being indexed. + +## [API - Examine Manager](examine-manager.md) + +Describes the singleton object which exposes all of the index and search providers which are registered in the configuration of the Umbraco application. diff --git a/16/umbraco-cms/reference/searching/examine/corrupt-indexes.md b/16/umbraco-cms/reference/searching/examine/corrupt-indexes.md new file mode 100644 index 00000000000..cdff800b506 --- /dev/null +++ b/16/umbraco-cms/reference/searching/examine/corrupt-indexes.md @@ -0,0 +1,31 @@ +--- +description: How to deal with Corrupt Examine indexes +hidden: true +--- + +# Corrupt Indexes + +The data integrity of Examine index files can be compromised if for example files are removed. When this happens, Umbraco considers the index to be corrupt. + +As some systems are already hooked into the Examine index lifecycle, resolving this issue outside the application is safest. + +## Resolution in a self-hosted environment + +The following steps cover clearing out corrupt indexes in a self-hosted environment. + +1. Stop the website/app pool. +2. Remove the directory containing the corrupted index files. +3. Restart the website. + +## Resolution on Umbraco Cloud + +The following steps cover clearing out corrupt indexes in a setup hosted on Umbraco Cloud. + +1. Open the project in the Cloud Portal. +2. Select the correct environment. +3. Access KUDU. +4. Open the debug console. +5. Choose CMD. +6. Navigate to `C:\home\site\wwwroot\umbraco\Data\Temp`. +7- Click the delete button next to the index file. +8. Restart the environment. diff --git a/16/umbraco-cms/reference/searching/examine/examine-management.md b/16/umbraco-cms/reference/searching/examine/examine-management.md new file mode 100644 index 00000000000..4b0ebea714d --- /dev/null +++ b/16/umbraco-cms/reference/searching/examine/examine-management.md @@ -0,0 +1,43 @@ +# Examine Management + +_Provides an overview of the available Examine functionality available directly within the Umbraco backoffice_ + +## Overview + +The Umbraco backoffice allows you to view details about your Examine indexes and searchers - all in one place. You can see which fields are being indexed, rebuild the indexes if there's a problem, and test keywords to see what results would be returned. + +![Examine Management within the Developer section](images/overview-examine-v14.png) + +The Examine Management dashboard, accessible from within the Settings section, is split into two sections: Indexers and Searchers. + +## Indexers + +From the Indexers section, you can view details about each Examine index currently configured within your Umbraco installation. Clicking any of these indexes will show you additional options, each discussed below. + +### Index info + +This section displays properties of the selected index, including the number of stored documents and fields. + +![Rebuild Index within Examine Management](images/External-indexes-v14.png) + +Within the Indexers it displays the details for the index provider as well. + +This can be useful to confirm the configuration that Umbraco is using and to ensure it is working as expected. This section also displays the full file path of the index itself. + +This section also provides the ability to rebuild the index, should this be required. Depending on how much content your website has, rebuilding the search indexes could take a while and affect the site performance temporarily. It is not recommended to do this while the website is under high load. + +### Fields + +From here, you can see the default system fields that are stored for each document within the search index. That includes the number of fields document, and the score which is calculated by Examine depending on how closely the individual results matched the search term. + +## Searchers + +From the Searchers section, you can view details about each Examine searcher currently configured within your Umbraco installation. Clicking any of these searchers will take you to a search page, where you can test out your search terms. + +You can see an example here how to configure an Examine searcher in the [Examine Multisearcher documentation](pdfindex-multisearcher.md#multi-index-searchers). + +### Search field + +![Search field for custom searcher within Examine Management](../../../../../10/umbraco-cms/reference/searching/examine/images/examine-management-search-field.png) + +The search field allows you to enter a search term and receive results back from the searcher in question. You can confirm if your query is working as expected. Matching results are returned in their raw format, with the score, document ID and Name being returned. The score is calculated by Examine depending on how closely the individual results matched the search term. diff --git a/16/umbraco-cms/reference/searching/examine/examine-manager.md b/16/umbraco-cms/reference/searching/examine/examine-manager.md new file mode 100644 index 00000000000..1fdf75f13b1 --- /dev/null +++ b/16/umbraco-cms/reference/searching/examine/examine-manager.md @@ -0,0 +1,151 @@ +# Examine Manager + +Accessing the singleton can be done by using dependency injection. + +In a class you can inject the IExamineManager interface: + +```csharp +using Examine; + +namespace MyCustomUmbracoSolution; + +public class MyClass +{ + private readonly IExamineManager _examineManager; + public MyClass(IExamineManager examineManager) + { + _examineManager = examineManager; + } +} +``` + +In a view the IExamineManager can be injected as well: + +```csharp +@inject IExamineManager ExamineManager; +``` + +This returns an active instance of the ExamineManager which exposes operations such as: + +* Default index & search providers +* Full collection of index & search providers +* All indexing and searching methods + +## Searching + +Important to note that the `Search` methods on the ExamineManager will call the Search methods of the **default** search provider specified in config. If you want to search using a specific provider, there are generally two approaches for this. + +If you want to use the searcher of a specific index, you should get the the searcher via the index: + +```csharp +@inherits UmbracoViewPage +@using Examine +@using Umbraco.Cms.Core +@inject IExamineManager ExamineManager +@{ + + // Get the search text from the query string + string query = Context.Request.Query["query"]; + + // Try to get the "ExternalIndex" from the Examine manager + if (ExamineManager.TryGetIndex(Constants.UmbracoIndexes.ExternalIndexName, out IIndex index)) + { + + // Search via the searcher of the index + ISearchResults searchResults = index.Searcher.Search(query); + + // Check whether the search revealed any results + if (searchResults.Any()) + { +
    + @foreach (ISearchResult result in searchResults) + { + + // Skip the result if the ID is null + if (result.Id is null) continue; + + // Skip the result if not found in the content cache + if (Umbraco.Content(result.Id) is not {} node) continue; + +
  • + @node.Name +
  • + + } +
+ } + + } + +} +``` + +If you have configured a custom searcher that you wish to use instead, you can access the searcher directly via the `IExamineManager` instance: + +```csharp +bool canGetSearcher = ExamineManager.TryGetSearcher("MyCustomSearcher", out ISearcher searcher); +``` + +An example using a custom searcher is below: + +```csharp +@inherits UmbracoViewPage +@using Examine +@inject IExamineManager ExamineManager +@{ + + // Get the search text from the query string + string query = Context.Request.Query["query"]; + + // Try to get the "MyCustomSearcher" searcher + if (ExamineManager.TryGetSearcher("MyCustomSearcher", out ISearcher searcher)) + { + + // Search via the searcher + ISearchResults searchResults = searcher.Search(query); + + // Check whether the search revealed any results + if (searchResults.Any()) + { +
    + @foreach (ISearchResult result in searchResults) + { + + // Skip the result if the ID is null + if (result.Id is null) continue; + + // Skip the result if not found in the content cache + if (Umbraco.Content(result.Id) is not {} node) continue; + +
  • + @node.Name +
  • + + } +
+ } + + } + +} +``` + +## Indexing + +When you wanna populate an index, you will need to use the `IExamineManager` and get the specific index. The build-in index names are all available as constants from the `Umbraco.Cms.Core.Constants.UmbracoIndexes` namespace + +```csharp +if (_examineManager.TryGetIndex(Umbraco.Cms.Core.Constants.UmbracoIndexes.ExternalIndexName, out IIndex index)) +{ + // Use index here +} +``` + +The indexing methods available on a single index are: + +```csharp +void DeleteFromIndex(IEnumerable itemIds); +void DeleteFromIndex(this IIndex index, string itemId); +void IndexExists(); +void IndexItems(IEnumerable values); +``` diff --git a/16/umbraco-cms/reference/searching/examine/images/External-indexes-v10.png b/16/umbraco-cms/reference/searching/examine/images/External-indexes-v10.png new file mode 100644 index 00000000000..9e397730d7a Binary files /dev/null and b/16/umbraco-cms/reference/searching/examine/images/External-indexes-v10.png differ diff --git a/16/umbraco-cms/reference/searching/examine/images/External-indexes-v14.png b/16/umbraco-cms/reference/searching/examine/images/External-indexes-v14.png new file mode 100644 index 00000000000..415f0115197 Binary files /dev/null and b/16/umbraco-cms/reference/searching/examine/images/External-indexes-v14.png differ diff --git a/16/umbraco-cms/reference/searching/examine/images/examine-management-home.png b/16/umbraco-cms/reference/searching/examine/images/examine-management-home.png new file mode 100644 index 00000000000..7a4f82e8896 Binary files /dev/null and b/16/umbraco-cms/reference/searching/examine/images/examine-management-home.png differ diff --git a/16/umbraco-cms/reference/searching/examine/images/examine-management-product-document.png b/16/umbraco-cms/reference/searching/examine/images/examine-management-product-document.png new file mode 100644 index 00000000000..100222968ed Binary files /dev/null and b/16/umbraco-cms/reference/searching/examine/images/examine-management-product-document.png differ diff --git a/16/umbraco-cms/reference/searching/examine/images/examine-management-product-index.png b/16/umbraco-cms/reference/searching/examine/images/examine-management-product-index.png new file mode 100644 index 00000000000..ee91e43bba0 Binary files /dev/null and b/16/umbraco-cms/reference/searching/examine/images/examine-management-product-index.png differ diff --git a/16/umbraco-cms/reference/searching/examine/images/examine-management-rebuild-index.png b/16/umbraco-cms/reference/searching/examine/images/examine-management-rebuild-index.png new file mode 100644 index 00000000000..db57b168b8a Binary files /dev/null and b/16/umbraco-cms/reference/searching/examine/images/examine-management-rebuild-index.png differ diff --git a/16/umbraco-cms/reference/searching/examine/images/examine-management-search-field.png b/16/umbraco-cms/reference/searching/examine/images/examine-management-search-field.png new file mode 100644 index 00000000000..b8bbb00d317 Binary files /dev/null and b/16/umbraco-cms/reference/searching/examine/images/examine-management-search-field.png differ diff --git a/16/umbraco-cms/reference/searching/examine/images/examine-management-search-tools.png b/16/umbraco-cms/reference/searching/examine/images/examine-management-search-tools.png new file mode 100644 index 00000000000..6d451906c50 Binary files /dev/null and b/16/umbraco-cms/reference/searching/examine/images/examine-management-search-tools.png differ diff --git a/16/umbraco-cms/reference/searching/examine/images/overview-examine-v14.png b/16/umbraco-cms/reference/searching/examine/images/overview-examine-v14.png new file mode 100644 index 00000000000..8fcfc7620ef Binary files /dev/null and b/16/umbraco-cms/reference/searching/examine/images/overview-examine-v14.png differ diff --git a/16/umbraco-cms/reference/searching/examine/images/overview-examine.png b/16/umbraco-cms/reference/searching/examine/images/overview-examine.png new file mode 100644 index 00000000000..0c243fc97cc Binary files /dev/null and b/16/umbraco-cms/reference/searching/examine/images/overview-examine.png differ diff --git a/16/umbraco-cms/reference/searching/examine/images/transforming-index-values.PNG b/16/umbraco-cms/reference/searching/examine/images/transforming-index-values.PNG new file mode 100644 index 00000000000..49f1078e9ef Binary files /dev/null and b/16/umbraco-cms/reference/searching/examine/images/transforming-index-values.PNG differ diff --git a/16/umbraco-cms/reference/searching/examine/indexing.md b/16/umbraco-cms/reference/searching/examine/indexing.md new file mode 100644 index 00000000000..c76007b49e4 --- /dev/null +++ b/16/umbraco-cms/reference/searching/examine/indexing.md @@ -0,0 +1,486 @@ +--- +description: >- + Learn how to build and customize the indexes that comes with your Umbraco + website. +--- + +# Custom indexing + +## Customizing the built in indexes + +You can modify the built-in indexes in the following ways: + +* [Events](https://shazwazza.github.io/Examine/articles/indexing.html#events) - giving you control over exactly what data goes into them and how the fields are configured +* Changing the field value types to change how values are stored in the index +* Changing the `IValueSetValidator` to change what goes into the index +* Take control of the entire index creation pipeline to change the implementation + +We can do all this by using the `ConfigureNamedOptions` pattern. + +## Creating a ConfigureOptions class + +We will start by creating a ConfigureExamineOptions class, that derives from `IConfigureNamedOptions`: + +```csharp +using Examine.Lucene; +using Microsoft.Extensions.Options; + +namespace Umbraco.Docs.Samples.Web.CustomIndexing; + +public class ConfigureExternalIndexOptions : IConfigureNamedOptions +{ + public void Configure(string name, LuceneDirectoryIndexOptions options) + { + throw new System.NotImplementedException(); + } + + public void Configure(LuceneDirectoryIndexOptions options) + { + throw new System.NotImplementedException(); + } +} +``` + +{% hint style="info" %} +In this sample we are altering the external index and thus we name the class `ConfigureExternalIndexOptions`. If you are altering multiple indexes, it is recommended to have separate classes for each index - i.e. `ConfigureExternalIndexOptions` for the external index, `ConfigureInternalIndexOptions` for the internal index and so on. +{% endhint %} + +When using the `ConfigureNamedOptions` pattern, we have to register this in a composer for it to configure our indexes, this can be done like this: + +```csharp +using Microsoft.Extensions.DependencyInjection; +using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Core.DependencyInjection; + +namespace Umbraco.Docs.Samples.Web.CustomIndexing; + +public class ExamineComposer : IComposer +{ + public void Compose(IUmbracoBuilder builder) + { + builder.Services.ConfigureOptions(); + } +} +``` + +### Changing field value types + +By default, Examine will store values into the Lucene index as "Full Text" fields, meaning the values will be indexed and analyzed for a textual search. However, if a field value is numerical, date/time, or another non-textual value type, you might want to change how the value is stored in the index. This will let you take advantage of some value type-specific search features such as numerical or date range. + +There is some documentation about this in the [Examine documentation](https://shazwazza.github.io/Examine/articles/configuration.html). + +The easiest way to modify how a field is configured is using the `ConfigureNamedOptions` pattern like so: + +```csharp +using Examine; +using Examine.Lucene; +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core; + +namespace Umbraco.Docs.Samples.Web.CustomIndexing; + +public class ConfigureExternalIndexOptions : IConfigureNamedOptions +{ + public void Configure(string name, LuceneDirectoryIndexOptions options) + { + if (name.Equals(Constants.UmbracoIndexes.ExternalIndexName)) + { + options.FieldDefinitions.AddOrUpdate(new FieldDefinition("price", FieldDefinitionTypes.Double)); + } + } + + // Part of the interface, but does not need to be implemented for this. + public void Configure(LuceneDirectoryIndexOptions options) + { + throw new System.NotImplementedException(); + } +} +``` + +This will ensure that the `price` field in the index is treated as a `double` type (if the `price` field does not exist in the index, it is added). + +## Changing IValueSetValidator + +An `IValueSetValidator` is responsible for validating a `ValueSet` to see if it should be included in the index. For example, by default the validation process for the ExternalIndex checks if a `ValueSet` has a category type of either "media" or "content" (not member). If a `ValueSet` was passed to the ExternalIndex and it did not pass this requirement it would be ignored. + +The `IValueSetValidator` is also responsible for filtering the data in the `ValueSet`. For example, by default the validator for the MemberIndex will validate on all the default member properties, so an extra property "PhoneNumber", would not pass validation, and therefore not be included. + +The `IValueSetValidator` implementation for the built-in indexes, can be changed like this: + +```csharp +using Examine.Lucene; +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core; +using Umbraco.Cms.Infrastructure.Examine; + +namespace Umbraco.Docs.Samples.Web.CustomIndexing; + +public class ConfigureMemberIndexOptions : IConfigureNamedOptions +{ + public void Configure(string name, LuceneDirectoryIndexOptions options) + { + if (name.Equals(Constants.UmbracoIndexes.MembersIndexName)) + { + options.Validator = new MemberValueSetValidator(null, null, new[] {"email"}, null); + } + } + + // Part of the interface, but does not need to be implemented for this. + public void Configure(LuceneDirectoryIndexOptions options) + { + throw new System.NotImplementedException(); + } +} +``` + +{% hint style="info" %} +Remember to register `ConfigureMemberIndexOptions` in your composer. +{% endhint %} + +## Creating your own index + +The following example will show how to create an index that will only include nodes based on the document type _product_. + +{% hint style="info" %} +We always recommend that you use the existing built in ExternalIndex. You should then query based on the NodeTypeAlias instead of creating a new separate index based on that particular node type. However, should the need arise, the example below will show you how to do it. + +Take a look at our [Examine Quick Start](quick-start.md) to see some examples of how to search the ExternalIndex. +{% endhint %} + +To create this index we need five things: + +1. An `UmbracoExamineIndex` implementation that defines the index. +2. An `IConfigureNamedOptions` implementation that configures the index fields and options. +3. An `IValueSetBuilder` implementation that builds index value sets a piece of content. +4. An `IndexPopulator` implementation that populates the index with the value sets for all applicable content. +5. An `INotificationHandler` implementation that updates the index when content changes. +6. A composer that adds all these services to the runtime. + +### ProductIndex + +```csharp +using Examine.Lucene; +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Infrastructure.Examine; +using IHostingEnvironment = Umbraco.Cms.Core.Hosting.IHostingEnvironment; + +namespace Umbraco.Docs.Samples.Web.CustomIndexing; + +public class ProductIndex : UmbracoExamineIndex +{ + public ProductIndex( + ILoggerFactory loggerFactory, + string name, + IOptionsMonitor indexOptions, + IHostingEnvironment hostingEnvironment, + IRuntimeState runtimeState) + : base(loggerFactory, + name, + indexOptions, + hostingEnvironment, + runtimeState) + { + } +} +``` + +### ConfigureProductIndexOptions + +```csharp +using Examine; +using Examine.Lucene; +using Lucene.Net.Analysis.Standard; +using Lucene.Net.Index; +using Lucene.Net.Util; +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core.Configuration.Models; + +namespace Umbraco.Docs.Samples.Web.CustomIndexing; + +public class ConfigureProductIndexOptions : IConfigureNamedOptions +{ + private readonly IOptions _settings; + + public ConfigureProductIndexOptions(IOptions settings) + => _settings = settings; + + public void Configure(string? name, LuceneDirectoryIndexOptions options) + { + if (name?.Equals("ProductIndex") is false) + { + return; + } + + options.Analyzer = new StandardAnalyzer(LuceneVersion.LUCENE_48); + + options.FieldDefinitions = new( + new("id", FieldDefinitionTypes.Integer), + new("name", FieldDefinitionTypes.FullText) + ); + + options.UnlockIndex = true; + + if (_settings.Value.LuceneDirectoryFactory == LuceneDirectoryFactory.SyncedTempFileSystemDirectoryFactory) + { + // if this directory factory is enabled then a snapshot deletion policy is required + options.IndexDeletionPolicy = new SnapshotDeletionPolicy(new KeepOnlyLastCommitDeletionPolicy()); + } + } + + // not used + public void Configure(LuceneDirectoryIndexOptions options) => throw new NotImplementedException(); +} +``` + +### ProductIndexValueSetBuilder + +```csharp +using Examine; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Infrastructure.Examine + +namespace Umbraco.Docs.Samples.Web.CustomIndexing; + +public class ProductIndexValueSetBuilder : IValueSetBuilder +{ + public IEnumerable GetValueSets(params IContent[] contents) + { + foreach (IContent content in contents.Where(CanAddToIndex)) + { + var indexValues = new Dictionary + { + // this is a special field used to display the content name in the Examine dashboard + [UmbracoExamineFieldNames.NodeNameFieldName] = content.Name!, + ["name"] = content.Name!, + // add the fields you want in the index + ["nodeName"] = content.Name!, + ["id"] = content.Id, + }; + + yield return new ValueSet(content.Id.ToString(), IndexTypes.Content, content.ContentType.Alias, indexValues); + } + } + + // filter out all content types except "product" + private bool CanAddToIndex(IContent content) => content.ContentType.Alias == "product"; +} +``` + +### ProductIndexPopulator + +```csharp +using Examine; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Infrastructure.Examine; + +namespace Umbraco.Docs.Samples.Web.CustomIndexing; + +public class ProductIndexPopulator : IndexPopulator +{ + private readonly IContentService _contentService; + private readonly ProductIndexValueSetBuilder _productIndexValueSetBuilder; + + public ProductIndexPopulator(IContentService contentService, ProductIndexValueSetBuilder productIndexValueSetBuilder) + { + _contentService = contentService; + _productIndexValueSetBuilder = productIndexValueSetBuilder; + RegisterIndex("ProductIndex"); + } + + protected override void PopulateIndexes(IReadOnlyList indexes) + { + foreach (IIndex index in indexes) + { + IContent[] roots = _contentService.GetRootContent().ToArray(); + index.IndexItems(_productIndexValueSetBuilder.GetValueSets(roots)); + + foreach (IContent root in roots) + { + const int pageSize = 10000; + var pageIndex = 0; + IContent[] descendants; + do + { + descendants = _contentService.GetPagedDescendants(root.Id, pageIndex, pageSize, out _).ToArray(); + IEnumerable valueSets = _productIndexValueSetBuilder.GetValueSets(descendants); + index.IndexItems(valueSets); + + pageIndex++; + } + while (descendants.Length == pageSize); + } + } + } +} +``` + +{% hint style="info" %} +This is only an example of how you could do indexing. In this example, we're indexing all content, both published and unpublished. + +In certain scenarios only published content should be added to the index. To achieve that, you will need to implement your own logic to filter out unpublished content. This can be somewhat tricky as the published state can vary throughout an entire structure of content nodes in the content tree. For inspiration on how to go about such filtering, you can look at the [ContentIndexPopulator in Umbraco](https://github.com/umbraco/Umbraco-CMS/blob/c878567633a6a3354c1414ccd130c9be518b25f0/src/Umbraco.Infrastructure/Examine/ContentIndexPopulator.cs#L115). +{% endhint %} + +### ProductIndexingNotificationHandler + +The index will only update its content when you manually trigger an index rebuild in the Examine dashboard. This is not always the desired behavior for a custom index. + +To update your index when content changes, you can use notification handlers. + +{% hint style="info" %} +The following handler class does not automatically update the descendant items of the modified content nodes, such as removing descendants of deleted content. If changes to the parent content item can affect its children or descendant items in your setup, please refer to the [UmbracoContentIndex.PerformDeleteFromIndex() in Umbraco](https://github.com/umbraco/Umbraco-CMS/blob/contrib/src/Umbraco.Examine.Lucene/UmbracoContentIndex.cs#L124-L153). Such logic should be applied when both removing and reindexing content items of type _product_. +{% endhint %} + +```csharp +using Examine; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Cache; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Services.Changes; +using Umbraco.Cms.Core.Sync; +using Umbraco.Cms.Infrastructure; +using Umbraco.Cms.Infrastructure.Search; + +namespace Umbraco.Docs.Samples.Web.CustomIndexing; + +public class ProductIndexingNotificationHandler : INotificationHandler +{ + private readonly IRuntimeState _runtimeState; + private readonly IUmbracoIndexingHandler _umbracoIndexingHandler; + private readonly IExamineManager _examineManager; + private readonly IContentService _contentService; + private readonly ProductIndexValueSetBuilder _productIndexValueSetBuilder; + + public ProductIndexingNotificationHandler( + IRuntimeState runtimeState, + IUmbracoIndexingHandler umbracoIndexingHandler, + IExamineManager examineManager, + IContentService contentService, + ProductIndexValueSetBuilder productIndexValueSetBuilder) + { + _runtimeState = runtimeState; + _umbracoIndexingHandler = umbracoIndexingHandler; + _examineManager = examineManager; + _contentService = contentService; + _productIndexValueSetBuilder = productIndexValueSetBuilder; + } + + /// + /// Updates the index based on content changes. + /// + public void Handle(ContentCacheRefresherNotification notification) + { + if (NotificationHandlingIsDisabled()) + { + return; + } + + if (!_examineManager.TryGetIndex("ProductIndex", out IIndex? index)) + { + throw new InvalidOperationException("Could not obtain the product index"); + } + + ContentCacheRefresher.JsonPayload[] payloads = GetNotificationPayloads(notification); + + foreach (ContentCacheRefresher.JsonPayload payload in payloads) + { + // Remove + if (payload.ChangeTypes.HasType(TreeChangeTypes.Remove)) + { + index.DeleteFromIndex(payload.Id.ToString()); + } + // Reindex + else if (payload.ChangeTypes.HasType(TreeChangeTypes.RefreshNode) || + payload.ChangeTypes.HasType(TreeChangeTypes.RefreshBranch)) + { + IContent? content = _contentService.GetById(payload.Id); + if (content == null || content.Trashed) + { + index.DeleteFromIndex(payload.Id.ToString()); + continue; + } + + IEnumerable valueSets = _productIndexValueSetBuilder.GetValueSets(content); + index.IndexItems(valueSets); + } + } + } + + private bool NotificationHandlingIsDisabled() + { + // Only handle events when the site is running. + if (_runtimeState.Level != RuntimeLevel.Run) + { + return true; + } + + if (_umbracoIndexingHandler.Enabled == false) + { + return true; + } + + if (Suspendable.ExamineEvents.CanIndex == false) + { + return true; + } + + return false; + } + + private ContentCacheRefresher.JsonPayload[] GetNotificationPayloads(CacheRefresherNotification notification) + { + if (notification.MessageType != MessageType.RefreshByPayload || + notification.MessageObject is not ContentCacheRefresher.JsonPayload[] payloads) + { + throw new NotSupportedException(); + } + + return payloads; + } +} +``` + +{% hint style="info" %} +You can find further inspiration for implementing notification handlers (_for example, for media updates_) in the [UmbracoExamine.PDF package](https://github.com/umbraco/UmbracoExamine.PDF). +{% endhint %} + +### ExamineComposer + +```csharp +using Examine; +using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Core.Notifications; +using Umbraco.Cms.Infrastructure.Examine; + +namespace Umbraco.Docs.Samples.Web.CustomIndexing; + +public class ExamineComposer : IComposer +{ + public void Compose(IUmbracoBuilder builder) + { + builder.Services.AddExamineLuceneIndex("ProductIndex"); + + builder.Services.ConfigureOptions(); + + builder.Services.AddSingleton(); + + builder.Services.AddSingleton(); + + builder.AddNotificationHandler(); + } +} +``` + +{% hint style="info" %} +The order of these registrations matters. It is important to register your index with `AddExamineLuceneIndex` before calling `ConfigureOptions`. +{% endhint %} + +### Result + +![Custom product index](images/examine-management-product-index.png) + +![Product document](images/examine-management-product-document.png) \ No newline at end of file diff --git a/16/umbraco-cms/reference/searching/examine/pdfindex-multisearcher.md b/16/umbraco-cms/reference/searching/examine/pdfindex-multisearcher.md new file mode 100644 index 00000000000..8e3cfafaf5e --- /dev/null +++ b/16/umbraco-cms/reference/searching/examine/pdfindex-multisearcher.md @@ -0,0 +1,59 @@ +# PDF indexes and multisearchers + +If you want to index PDF files and search for them you will need to use the [UmbracoExamine.Pdf extension package](https://github.com/umbraco/UmbracoExamine.PDF). + +## Installation + +Install with NuGet: +`dotnet add package Umbraco.ExaminePDF` + +This will create a new Examine index called "PDFIndex", which will appear in "Examine Management" dashboard under the "Settings" section. Using this index you can start searching the contents of any PDF files uploaded to the media section. + +![image](https://user-images.githubusercontent.com/7405322/189886089-d23b45c7-814b-4101-b143-31c5cd9fa655.png) + + +## Multi-index searchers +A multi-index searcher is a searcher that can search multiple indexes. This can be helpful when you for example want to search both the external and internal indexes. +You can register a multi-index searcher with the ExamineManager on startup like: + +```csharp +using Examine; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Core.DependencyInjection; +using UmbracoExamine.PDF; + +namespace MySite.MyCustomIndex; + +[ComposeAfter(typeof(ExaminePdfComposer))] +public class ExamineComposer : IComposer +{ + public void Compose(IUmbracoBuilder builder) + { + builder.Services.AddExamineLuceneMultiSearcher("MultiSearcher", new[] {Constants.UmbracoIndexes.ExternalIndexName, PdfIndexConstants.PdfIndexName}); + } +} +``` + +With this approach, the multi-index searcher will show up in the "Examine Management" dashboard. + +![image](https://user-images.githubusercontent.com/7405322/189887744-af2d8e69-4807-4407-868d-b43e9fa9518d.png) + +The multi-index searcher can be resolved in code from the ExamineManager like this: + +```csharp +if (_examineManager.TryGetSearcher("MultiSearcher", out var searcher)) +{ + //TODO: use the `searcher` to search +} +``` + +{% hint style="warning" %} +The implementation of IPdfTextExtractor is PdfSharpTextExtractor in this library, which uses PDFSharp to extract the bytes to convert to text. That implementation doesn't deal well with Unicode text which means when some PDF files are read, the result will be 'junk' strings. + +It is certainly possible to replace the IPdfTextExtractor using your own composer like + +`composition.RegisterUnique();` + +The iTextSharp library deals with Unicode in a better way but is a paid for license. If you wish to use iTextSharp or another PDF library you can swap out the IPdfTextExtractor with your own implementation. +{% endhint %} diff --git a/16/umbraco-cms/reference/searching/examine/quick-start.md b/16/umbraco-cms/reference/searching/examine/quick-start.md new file mode 100644 index 00000000000..8f36d44a7ff --- /dev/null +++ b/16/umbraco-cms/reference/searching/examine/quick-start.md @@ -0,0 +1,358 @@ +# Quick start + +_This guide will help you get set up quickly using Examine with minimal configuration options. Umbraco ships Examine with 3 indexes: internal, external, and members. The internal index should not be used for searching when returning results on a public website because it includes content that has not been published yet. Instead, you can use the external index to get up and running._ + +### Performing a search + +{% hint style="info" %} +In the coming examples, the [Umbraco Starter Kit](https://our.umbraco.com/packages/starter-kits/the-starter-kit/) has been used, as it provides some example content that can be searched. Therefore, some of the examples below may require 'the setting up of templates, etc' if you follow the guide on your existing site. +{% endhint %} + +The starter kit comes with some Templates, Document Types, and content nodes created already. We will use some of these to set up a basic search system. This is a 'Quick Start' guide, as many more complex searches are possible with Examine. + +We will make it possible to 'search' on the _People_ page, by adding a search bar to the template page: `people.cshtml` - add the following form at the top of the template, but underneath the ` +--> +
+
+ + +
+
+
+... +``` + +This will create a basic input field at the top of the page and make it post to the same people page when submitted along with the search term. + +#### Handling the search request + +The best practice for POST requests is to encapsulate the request handling in a controller. To do this we will leverage the concept of [route hijacking](../../routing/custom-controllers.md). + +Let's start by creating a `PeopleController` that derives from `RenderController` and add an `Index` method. + +{% hint style="info" %} +It is important to name our controller by the convention _`NameOfViewController`_. In our case the view is named People, so the controller is named `PeopleController`. +{% endhint %} + +```csharp +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.ViewEngines; +using Microsoft.Extensions.Logging; +using Umbraco.Cms.Core.Web; +using Umbraco.Cms.Web.Common.Controllers; + +namespace MyStarterKitSite.Controllers; + +public class PeopleController : RenderController +{ + public PeopleController( + ILogger logger, + ICompositeViewEngine compositeViewEngine, + IUmbracoContextAccessor umbracoContextAccessor) + : base( + logger, + compositeViewEngine, + umbracoContextAccessor) + { + } + + public override IActionResult Index() + { + return CurrentTemplate(CurrentPage); + } +} +``` + +#### Adding a Service that handles our search logic + +To search anything from our controller, we first need to create a service that handles the actual search logic. We'll start by creating an interface for our service. + +```csharp +using System.Collections.Generic; +using Umbraco.Cms.Core.Models.PublishedContent; + +namespace MyStarterKitSite.Services; + +public interface ISearchService +{ + IEnumerable SearchContentNames(string query); +} +``` + +Now create a default implementation of the service interface. + +```csharp +using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Web.UI.Services; + +namespace MyStarterKitSite.Services; + +public class SearchService : ISearchService +{ + public IEnumerable SearchContentNames(string query) => throw new NotImplementedException(); +} +``` + +And finally register the service in `Startup`. + +```csharp +// ... (removed for abbreviation) +builder.Services.AddTransient(); +``` + +#### Examine Search Index + +To perform the search we will first need to get a reference to the particular Examine index that we want to search. Then we will use this index to access its corresponding `Searcher`. We use the `Searcher` to construct the query logic to execute and search the index. + +Umbraco ships with three indexes: + +* ExternalIndex - available to use for indexing published unprotected content. +* InternalIndex - which Umbraco's backoffice search uses. +* MembersIndex - which Umbraco's Membership implementation uses. + +[You can create your own indexes too](indexing.md) if you need to analyse text in a different language for example. + +The service `IExamineManager` is used to retrieve an Examine index by its 'alias', so we need to inject that service into our `SearchService`. + +```csharp +using System.Collections.Generic; +using Examine; +using Umbraco.Cms.Core.Models.PublishedContent; + +namespace MyStarterKitSite.Services; + +public class SearchService : ISearchService +{ + private readonly IExamineManager _examineManager; + public SearchService(IExamineManager examineManager) + { + _examineManager = examineManager; + } + public IEnumerable SearchContentNames(string query) => throw new NotImplementedException(); +} +``` + +#### Creating the Search Query + +With the `IExamineManager` injected in our `SearchService`, we can implement the `SearchContentNames` method. We do this using the `Searcher` for the Examine index 'ExternalIndex'. + +```csharp +IEnumerable ids = Array.Empty(); +if (!string.IsNullOrEmpty(query) && _examineManager.TryGetIndex("ExternalIndex", out IIndex? index)) +{ + ids = index + .Searcher + .CreateQuery("content") + .NodeTypeAlias("person") + .And() + .Field("nodeName", query) + .Execute() + .Select(x => x.Id); +} +``` + +{% hint style="info" %} +We reference the External index by its alias "ExternalIndex". Umbraco has a set of 'Constants' that refer to the indexes that can be more convenient to use `Constants.UmbracoIndexes`. So, in the example here we could have used `Constants.UmbracoIndexes.ExternalIndexName` instead of "ExternalIndex". +{% endhint %} + +The `Searcher` has a CreateQuery method, where you can choose to search content, media or members eg: + +```csharp +Searcher.CreateQuery("content") +``` + +From here you can see how we can chain together the logic to perform the search. In the example, we are searching all `content` using the `person` Document Type, where the `nodeName` is equal to the search term that was typed in the input bar. + +```csharp +Searcher.CreateQuery("content").NodeTypeAlias("person").And().Field("nodeName", searchTerm) +``` + +Calling `.Execute()` at the end of the query logic triggers the search and returns a set of matching search results, which we can loop through to get the IDs of the resulting content items. + +### Getting the content + +We want to retrieve the actual content from the IDs. For that, we need the `UmbracoHelper`, which must be injected into our service as well. The final implementation of `SearchService` then looks like this. + +```csharp +using System; +using System.Collections.Generic; +using System.Linq; +using Examine; +using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Web.Common; +using Umbraco.Extensions; + +namespace MyStarterKitSite.Services; + +public class SearchService : ISearchService +{ + private readonly IExamineManager _examineManager; + private readonly UmbracoHelper _umbracoHelper; + + public SearchService(IExamineManager examineManager, UmbracoHelper umbracoHelper) + { + _examineManager = examineManager; + _umbracoHelper = umbracoHelper; + } + + public IEnumerable SearchContentNames(string query) + { + IEnumerable ids = Array.Empty(); + if (!string.IsNullOrEmpty(query) && _examineManager.TryGetIndex("ExternalIndex", out IIndex? index)) + { + ids = index + .Searcher + .CreateQuery("content") + .NodeTypeAlias("person") + .And() + .Field("nodeName", query) + .Execute() + .Select(x => x.Id); + } + + foreach (var id in ids) + { + yield return _umbracoHelper.Content(id); + } + } +} +``` + +After getting the ids from our search, we then loop through the list and return the content. + +## Creating a custom view model + +We will now need a custom view model so that we can pass our search results to the view. Our view model needs to inherit from `PublishedContentWrapped` because our People view is expecting a model that is content. We then wrap the content and add the search data, all in a convenient view model. + +```csharp +using System.Collections.Generic; +using System.Linq; +using Umbraco.Cms.Core.Models.PublishedContent; + +namespace MyStarterKitSite.Models; + +public class SearchViewModel : PublishedContentWrapped +{ + public SearchViewModel(IPublishedContent content, IPublishedValueFallback publishedValueFallback) + : base(content, publishedValueFallback) + { + } + + public IEnumerable SearchResults { get; set; } = Enumerable.Empty(); + public bool HasSearched { get; set; } +} +``` + +### Using the service and view model in the controller + +Now that we've created our service to handle the actual search logic, and our view model to pass the search results to the view, let's look at using them in the controller. We will want to update the `Index()` method to get out the query string from the request, then create a view model and populate the `SearchResults` property by using our service. + +```csharp +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.ViewEngines; +using Microsoft.Extensions.Logging; +using MyStarterKitSite.Models; +using MyStarterKitSite.Services; +using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.Web; +using Umbraco.Cms.Web.Common.Controllers; + +namespace MyStarterKitSite.Controllers; + +public class PeopleController : RenderController +{ + private readonly IPublishedValueFallback _publishedValueFallback; + private readonly ISearchService _searchService; + + public PeopleController( + ILogger logger, + ICompositeViewEngine compositeViewEngine, + IUmbracoContextAccessor umbracoContextAccessor, + IPublishedValueFallback publishedValueFallback, + ISearchService searchService) + : base(logger, + compositeViewEngine, + umbracoContextAccessor) + { + _publishedValueFallback = publishedValueFallback; + _searchService = searchService; + } + + public override IActionResult Index() + { + // Get the queryString from the request + string queryString = HttpContext.Request.Query["query"]; + + // Create the view model and pass it to the view + SearchViewModel viewModel = new(CurrentPage!, _publishedValueFallback) + { + SearchResults = _searchService.SearchContentNames(queryString), + HasSearched = !string.IsNullOrEmpty(queryString), + }; + + return CurrentTemplate(viewModel); + } +} +``` + +### Updating the view to use the viewmodel + +The final thing we need to do is update the view to use our new view model. We do that by changing the `@inherits` line in the view. + +```csharp +@inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage +``` + +Let's now use the view model to display the search results. We'll place them directly under the form we created earlier. + +```csharp +
+ @if (Model.SearchResults.Any()) + { +
    + @foreach (var content in Model.SearchResults) + { +
  • + @content.Name +
  • + } +
+ } + else if(Model.HasSearched) + { +

No results found

+ } +
+``` + +## Different ways to query + +Examine has a lot of different ways to query data. Building upon the example from before, here are a few other searches that can be done to get different data: + +#### Search through all nodes + +Let's say you want to search through **all content nodes** by their **file names**. You could amend the query from before like this: + +```csharp +Searcher.CreateQuery("content").Field("nodeName", searchTerm).Execute(); +``` + +#### Search using Lucene queries + +To do the search like above, but only use Lucene to query, amend the query from before like this: + +```csharp +Searcher.CreateQuery().NativeQuery("+__IndexType:content +nodeName:" + searchTerm).Execute(); +``` + +#### Search children of a specific node + +To search through **all child nodes of a specific node** by their **bodyText property**, amend the query from before like this: + +```csharp +Searcher.CreateQuery("content").ParentId(1105).And().Field("bodyText", searchTerm).Execute(); +``` diff --git a/16/umbraco-cms/reference/security/README.md b/16/umbraco-cms/reference/security/README.md new file mode 100644 index 00000000000..921e820d015 --- /dev/null +++ b/16/umbraco-cms/reference/security/README.md @@ -0,0 +1,85 @@ +--- +description: >- + This section includes information on Umbraco security, its various security + options and configuring how authentication & authorization works in Umbraco +--- + +# Security + +In this article, you will find everything you need regarding security within Umbraco. + +## [The Umbraco Trust Center (external)](https://umbraco.com/about-us/trust-center/) + +On our main website, we have a dedicated security section which provides all the details you need to know about security within the Umbraco CMS. This includes how to report a vulnerability. + +## [Secure Sockets Layer (SSL)/HTTPS](ssl-https.md) + +We highly encourage the use of HTTPS on Umbraco websites, especially in production environments. By using HTTPS you greatly improve the security of your website. + +In the "Use HTTPS" article you can learn more about how to use HTTPS and how to set it up. + +## [Security Settings](security-settings.md) + +Learn which password settings that can be configured in Umbraco. + +## [Security Hardening](security-hardening.md) + +Learn about how to harden the security on your Umbraco website to secure it even further. + +## [Security on Umbraco Cloud](https://docs.umbraco.com/umbraco-cloud/security) + +When your project is hosted on Umbraco Cloud, you might be interested in more details about the security of the hosting. This information can be found in the Umbraco Cloud FAQs section of the documentation. + +## [Backoffice users and website members (external)](https://docs.microsoft.com/en-us/aspnet/core/security/authentication/identity) + +Authentication for backoffice users and website members in Umbraco uses [ASP.NET Core Identity](https://docs.microsoft.com/en-us/aspnet/core/security/authentication/identity) which is a flexible and extendable framework for authentication. + +Out of the box Umbraco ships with a custom ASP.NET Core Identity implementation which uses Umbraco's database data. Normally this is fine for most Umbraco developers, but in some cases the authentication process needs to be customized. + +### [External login providers](external-login-providers.md) + +The Umbraco users and members supports external login providers (OAuth) for performing authentication of your users/members. This could be any OpenIDConnect provider such as Entra ID/Azure Active Directory, Identity Server, Google or Facebook. + +### [Two-factor authentication](two-factor-authentication.md) + +The Umbraco members supports a two-factor authentication (2FA) abstraction for implementing a 2FA provider of your choice. This could be any Time-based One-time Password (TOTP) Algorithm, including Microsoft and Google Authenticator Apps + +### [BackOfficeUserManager and Notifications](backofficeusermanager-and-notifications.md) + +The [`BackOfficeUserManager`](backofficeusermanager-and-notifications.md) is the ASP.NET Core Identity [UserManager](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.identity.usermanager-1) implementation in Umbraco. It exposes APIs for working with Umbraco Users via the ASP.NET Core Identity including password handling. + +### [Custom password check](custom-password-check.md) + +In most cases [External login providers (OAuth)](external-login-providers.md) will meet the needs of most users when needing to authenticate with external resources. In some cases you may need to only change how the username and password credentials are checked. + +This is typically a legacy approach to validating credentials with external resources but it is possible. + +You are able to check the username and password against your own credentials store by implementing a [`IBackOfficeUserPasswordChecker`](custom-password-check.md). + +## [Sensitive data on members](sensitive-data-on-members.md) + +Marking fields as **sensitive** will hide the data in those fields for backoffice users that do not have permission to view personal data of members. + +Learn more about this in the [Sensitive Data](sensitive-data-on-members.md) article. + +## [Setup Umbraco for a Federal Information Processing Standards (FIPS) Compliant Server](setup-umbraco-for-a-fips-server.md) + +How to configure Umbraco to run on a FIPS compliant server. + +## [Reset admin password](reset-admin-password.md) + +Use this guide to [reset the password of the "admin" user](reset-admin-password.md). + +If you need to reset accounts of every other user while you still have administrative action, check this "[reset normal user password](password-reset.md)" article. + +## Other articles related to security + +* [Health Checks](../../extending/health-check/) + +*** + +## Umbraco Training + +Umbraco HQ offers a full-day training course covering an overview of Transport Layer Security (TLS), understanding threats, two-factor authentication, and more. The course targets frontend and backend developers, designers, and technical users. + +[Explore the Security Training Course](https://umbraco.com/training/course-details/security/) to learn more about the topics covered and how it can enhance your Umbraco development skills. \ No newline at end of file diff --git a/16/umbraco-cms/reference/security/api-rate-limiting.md b/16/umbraco-cms/reference/security/api-rate-limiting.md new file mode 100644 index 00000000000..bcb5d1d94e0 --- /dev/null +++ b/16/umbraco-cms/reference/security/api-rate-limiting.md @@ -0,0 +1,258 @@ +--- +description: How to take advantage of the built-in rate limiting middleware of ASP.NET Core in Umbraco. +--- + +# API rate limiting + +Since ASP.NET Core 7, you can use the [built-in rate limiting middleware](https://learn.microsoft.com/en-us/aspnet/core/performance/rate-limit) to rate limit your APIs. You can apply the `EnableRateLimiting` and `DisableRateLimiting` attributes to add rate limiting on a controller or endpoint level. In this article, we will go through how you can configure and utilize different rate limiting strategies for Umbraco APIs. + +## What is rate limiting? + +Rate limiting helps control the number of requests to an API, typically within a specified time frame or based on other parameters. This ensures the stability, availability, and security of your APIs. The key benefits are: +- Preventing server or application overload +- Improving security and protecting against DDoS attacks +- Reducing unnecessary resource usage, thereby cutting down on costs + +## Configuring rate limiting +You can configure rate limiting in Umbraco by composition. To use the middleware, you need to register the rate limiting services first: + +{% code title="ApiRateLimiterComposer.cs" %} +```csharp +public class ApiRateLimiterComposer : IComposer +{ + public void Compose(IUmbracoBuilder builder) + { + builder.Services.AddRateLimiter(rateLimiterOptions => + { + // Default is 503 (Service Unavailable) + rateLimiterOptions.RejectionStatusCode = StatusCodes.Status429TooManyRequests; + + // Write your code to configure the middleware here (rate limiting policies) + }); + } +} +``` +{% endcode %} +After that, you have to apply the `RateLimitingMiddleware` by creating an Umbraco pipeline filter. `UseRateLimiter()` must be called after `UseRouting()`, therefore we use the `PostRouting` to make sure this happens in the correct order: + +{% code title="ApiRateLimiterComposer.cs" %} +```csharp +public class ApiRateLimiterComposer : IComposer +{ + public void Compose(IUmbracoBuilder builder) + { + builder.Services.Configure(options => + { + options.AddFilter(new UmbracoPipelineFilter("UmbracoApiRateLimiter") + { + PostRouting = postRouting => + { + // Apply the RateLimitingMiddleware + // Must be called after UseRouting() + postRouting.UseRateLimiter(); + } + }); + }); + } +} +``` +{% endcode %} + +With the set-up in place, let's take a look at some limiter options. + +### Global limiter +Inside `AddRateLimiter()` we can use the `GlobalLimiter` option to set a global rate limiter for all requests: + +{% code title="ApiRateLimiterComposer.cs" %} +```csharp + // Write your code to configure the middleware here (rate limiting policies) + + // Use GlobalLimiter for all requests + rateLimiterOptions.GlobalLimiter = PartitionedRateLimiter.Create(httpContext => + { + return RateLimitPartition.GetFixedWindowLimiter( + partitionKey: httpContext.Request.Headers.Host.ToString(), + partition => new FixedWindowRateLimiterOptions + { + PermitLimit = 2, + Window = TimeSpan.FromSeconds(10), + AutoReplenishment = true + }); + }); +``` +{% endcode %} + +In the above example, we have added a `FixedWindowLimiter` and configured it to automatically replenish permitted requests and permit 2 requests per 10 seconds. There are different algorithms and techniques for implementing rate limiting, which can vary depending on your use case. For more information, check the currently supported [rate limiter algorithms](https://learn.microsoft.com/en-us/aspnet/core/performance/rate-limit#rate-limiter-algorithms). + +### Limit specific endpoints +If you want to be more granular, you can configure different rate limits for different endpoints in `AddRateLimiter()` as well: + +{% code title="ApiRateLimiterComposer.cs" %} +```csharp + // Write your code to configure the middleware here (rate limiting policies) + + // Add specific rate limiting policies + rateLimiterOptions.AddFixedWindowLimiter(policyName: "fixed1", options => + { + options.PermitLimit = 2; + options.Window = TimeSpan.FromSeconds(10); + options.AutoReplenishment = true; + }); + + rateLimiterOptions.AddFixedWindowLimiter(policyName: "fixed2", options => + { + options.PermitLimit = 5; + options.Window = TimeSpan.FromSeconds(10); + options.AutoReplenishment = true; + }); +``` +{% endcode %} + +In the code snippet above, we have configured 2 fixed window limiters with different settings, and different policy names (`"fixed1"` and `"fixed2"`). We can apply these policies to specific endpoints within Umbraco using the same `UmbracoPipelineFilter`: + +{% code title="ApiRateLimiterComposer.cs" %} +```csharp + // Applying EnableRateLimitingAttribute to specific endpoint + postRouting.Use(async (context, next) => + { + // Limit specific controller(s) + var controllerName = ControllerExtensions.GetControllerName(); + var actionName = nameof(AuthenticationController.PostRequestPasswordReset); + + var currentEndpoint = context.GetEndpoint(); + var actionDescriptor = currentEndpoint?.Metadata.GetMetadata(); + + // Apply rate limiting logic based on your conditions + if (currentEndpoint is not null && + actionDescriptor is not null && + actionDescriptor.ControllerName == controllerName && + actionDescriptor.ActionName == actionName) + { + // Apply rate limiting logic based on your conditions + + // Add Attribute to endpoint's metadata + var endpointMetadataCollection = new EndpointMetadataCollection( + new List(currentEndpoint.Metadata) + { + new EnableRateLimitingAttribute("fixed1") + }); + + // Update endpoint + var updatedEndpoint = new Endpoint( + currentEndpoint.RequestDelegate, + endpointMetadataCollection, + currentEndpoint.DisplayName); + + context.SetEndpoint(updatedEndpoint); + } + + await next.Invoke(); + }); +``` +{% endcode %} + +This part of the code shows how to apply the `fixed1` policy to the `AuthenticationController.PostRequestPasswordReset` endpoint, responsible for handling password reset requests. We can do that by dynamically modifying the endpoint's metadata - attaching the `EnableRateLimitingAttribute` with the name of the policy which needs to be applied. This enables us to enforce the defined rate limits on a particular endpoint. + +For your reference, here is the complete `ApiRateLimiterComposer.cs` implementation. + +{% code title="ApiRateLimiterComposer.cs" lineNumbers="true" %} +```csharp +using System.Threading.RateLimiting; +using Microsoft.AspNetCore.Mvc.Controllers; +using Microsoft.AspNetCore.RateLimiting; +using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Web.BackOffice.Controllers; +using Umbraco.Cms.Web.Common.ApplicationBuilder; + +namespace Umbraco.Docs.Samples; + +public class ApiRateLimiterComposer : IComposer +{ + public void Compose(IUmbracoBuilder builder) + { + // Register the rate limiting services + builder.Services.AddRateLimiter(rateLimiterOptions => + { + // Default is 503 (Service Unavailable) + rateLimiterOptions.RejectionStatusCode = StatusCodes.Status429TooManyRequests; + + // Use GlobalLimiter for all requests + // rateLimiterOptions.GlobalLimiter = PartitionedRateLimiter.Create(httpContext => + // { + // return RateLimitPartition.GetFixedWindowLimiter( + // partitionKey: httpContext.Request.Headers.Host.ToString(), + // partition => new FixedWindowRateLimiterOptions + // { + // PermitLimit = 2, + // Window = TimeSpan.FromSeconds(10), + // AutoReplenishment = true + // }); + // }); + + // Add specific rate limiting policies + rateLimiterOptions.AddFixedWindowLimiter(policyName: "fixed1", options => + { + options.PermitLimit = 2; + options.Window = TimeSpan.FromSeconds(10); + options.AutoReplenishment = true; + }); + + rateLimiterOptions.AddFixedWindowLimiter(policyName: "fixed2", options => + { + options.PermitLimit = 5; + options.Window = TimeSpan.FromSeconds(10); + options.AutoReplenishment = true; + }); + }); + + builder.Services.Configure(options => + { + options.AddFilter(new UmbracoPipelineFilter("UmbracoApiRateLimiter") + { + PostRouting = postRouting => + { + // Applying EnableRateLimitingAttribute to specific endpoint + postRouting.Use(async (context, next) => + { + // Limit specific controller(s) + var controllerName = ControllerExtensions.GetControllerName(); + var actionName = nameof(AuthenticationController.PostRequestPasswordReset); + + var currentEndpoint = context.GetEndpoint(); + var actionDescriptor = currentEndpoint?.Metadata.GetMetadata(); + + // Apply rate limiting logic based on your conditions + if (currentEndpoint is not null && + actionDescriptor is not null && + actionDescriptor.ControllerName == controllerName && + actionDescriptor.ActionName == actionName) + { + // Add EnableRateLimiting attribute to endpoint's metadata + var endpointMetadataCollection = new EndpointMetadataCollection( + new List(currentEndpoint.Metadata) + { + new EnableRateLimitingAttribute("fixed1") + }); + + // Update endpoint + var updatedEndpoint = new Endpoint( + currentEndpoint.RequestDelegate, + endpointMetadataCollection, + currentEndpoint.DisplayName); + + context.SetEndpoint(updatedEndpoint); + } + + await next.Invoke(); + }); + + // Apply the RateLimitingMiddleware + // Must be called after UseRouting() + postRouting.UseRateLimiter(); + } + }); + }); + } +} +``` +{% endcode %} \ No newline at end of file diff --git a/16/umbraco-cms/reference/security/backofficeusermanager-and-notifications.md b/16/umbraco-cms/reference/security/backofficeusermanager-and-notifications.md new file mode 100644 index 00000000000..76953930612 --- /dev/null +++ b/16/umbraco-cms/reference/security/backofficeusermanager-and-notifications.md @@ -0,0 +1,90 @@ +--- +description: "The BackOfficeUserManager is the ASP.NET Core Identity UserManager implementation in Umbraco. It exposes APIs for working with Umbraco User's via the ASP.NET Core Identity including password handling." +--- + +# BackOfficeUserManager and Events + +The `BackOfficeUserManager` is the ASP.NET Core Identity [UserManager](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.identity.usermanager-1) implementation in Umbraco. It exposes APIs for working with Umbraco Users via the ASP.NET Core Identity including password handling. + +## Extending + +The BackOfficeUserManager can be replaced during startup in order to use your own implementation. +This may be required if you want to extend the functionality of the BackOfficeUserManager for things like supporting two-factor authentication(2FA). + +### Example + +You can replace the BackOfficeUserManager in the startup class by using the `SetBackOfficeUserManager` extension on the `IUmbracoBuilder`. + +```csharp +builder.CreateUmbracoBuilder() + .AddBackOffice() + .AddWebsite() + .AddDeliveryApi() + .AddComposers() + .SetBackOfficeUserManager() + .Build(); +``` + +You can then implement your custom `BackOfficeUserManager`, like this. +Note the constructor minimum needs to inject what is required for the base `BackOfficeUserManager` class: + +```csharp + public class CustomBackOfficeUserManager : BackOfficeUserManager +{ + public CustomBackOfficeUserManager( + IIpResolver ipResolver, + IUserStore store, + IOptions optionsAccessor, + IPasswordHasher passwordHasher, + IEnumerable> userValidators, + IEnumerable> passwordValidators, + BackOfficeErrorDescriber errors, + IServiceProvider services, + IHttpContextAccessor httpContextAccessor, + ILogger logger, + IOptions passwordConfiguration, + IEventAggregator eventAggregator, + IBackOfficeUserPasswordChecker backOfficeUserPasswordChecker) + : base( + ipResolver, + store, + optionsAccessor, + passwordHasher, + userValidators, + passwordValidators, + errors, + services, + httpContextAccessor, + logger, + passwordConfiguration, + eventAggregator, + backOfficeUserPasswordChecker) + { + } + + //Override whatever you need, e.g. SupportsUserTwoFactor. + public override bool SupportsUserTwoFactor => false; +} +``` + +## Notifications + +There are [many notifications](https://apidocs.umbraco.com/v15/csharp/api/Umbraco.Cms.Web.Common.Security.BackOfficeUserManager.html) you can handle on the `BackOfficeUserManager`. +Internally these are mainly used for auditing but there are some that allow you to customize some workflows: + + + +* `SendEmailNotification` + * This is a generic notification but it has a property `EmailType` that specify the email type. This type can be `UserInvite`. + In that case, it allows you to take control over how a user in the backoffice is invited. + This might be handy if you are using an [External Login Provider](external-login-providers.md) that has the `DenyLocalLogin` option assigned + and you still want to have the user invite functionality available. + In this setup, all of your users are controlled by your external login provider so you would need to handle the user invite flow yourself by using this event and inviting the user via your external provider. + If you are using this event to replace the default functionality you will need to tell Umbraco that you've handled the invite by calling the + `SendEmailNotification.HandleEmail()` method.) +* `UserLogoutSuccessNotification` + + * This is specifically used if you have an [External Login Provider](external-login-providers.md) in use + and you want to log out of that external provider when the user is logged out of the backoffice (that is log out of everywhere). + The notification has a property `SignOutRedirectUrl`. If this property is assigned then Umbraco will redirect to that URL upon successful + backoffice sign out in order to sign the user out of the external login provider. diff --git a/16/umbraco-cms/reference/security/cookies.md b/16/umbraco-cms/reference/security/cookies.md new file mode 100644 index 00000000000..916fc77067a --- /dev/null +++ b/16/umbraco-cms/reference/security/cookies.md @@ -0,0 +1,34 @@ +--- +description: Learn about the cookies required for accessing the Umbraco Backoffice and their purposes. +--- + +# Cookies + +The cookies listed in this article are required only for accessing the Backoffice. You can include these in your own cookie policy, if you wish. + +## Necessary Cookies + +The below cookies are necessary for accessing the Umbraco Backoffice and functioning of the website. They allow you to enjoy the contents and services you request. + +| Name | Purpose | Expiration | +|----------------------------|-------------------------------------------------------------------------------------------------------------------|------------| +| UMB_PREVIEW | Allows a previewed page to act as a published page only on the browser which has initialized previewing. | Session | +| UMB-WEBSITE-PREVIEW-ACCEPT | Client-side cookie that determines whether the user has accepted to be in Preview Mode when visiting the website. | Session | +| umb_installId | Used to store the Umbraco software installer id. | Session | +| UMB_UPDCHK | Enables your system to check for the Umbraco software updates. | Session | +| UMB-XSRF-V | Used to store the backoffice antiforgery token validation value. | Session | +| TwoFactorRememberBrowser | Default authentication type used for storing that 2FA is not needed on next login | Session | +| UMB_SESSION | Preserves the visitor's session state across page requests. | Session | + +The `UMB_SESSION` cookie is secure if you are using HTTPS pages. However, if you wish to secure the cookie in your code, add the following in the `Program.cs` file after `Build();` + +```cs +builder.Services.AddSession(options => + { + options.Cookie.Name = "UMB_SESSION"; + options.Cookie.HttpOnly = true; + options.Cookie.SecurePolicy = CookieSecurePolicy.Always; + }); +``` + +For information on the rest of the cookies, see the [Constants-Web.cs](https://github.com/umbraco/Umbraco-CMS/blob/contrib/src/Umbraco.Core/Constants-Web.cs) file on GitHub. diff --git a/16/umbraco-cms/reference/security/custom-password-check.md b/16/umbraco-cms/reference/security/custom-password-check.md new file mode 100644 index 00000000000..581166a999e --- /dev/null +++ b/16/umbraco-cms/reference/security/custom-password-check.md @@ -0,0 +1,55 @@ +--- +description: You can specify your own logic to validate a username and password against a custom data store. Learn more about it in this section. +--- + +# Replacing the basic username/password check + +Having the ability to replace the logic to validate a username and password against a custom data store is important to some developers. Normally in ASP.Net Core Identity this would require you to override the `UmbracoBackOfficeUserManager.CheckPasswordAsync` implementation and then replace the `UmbracoBackOfficeUserManager` with your own class during startup. Since this is a common task we've made this process a lot easier with an interface called `IBackOfficeUserPasswordChecker`. + +Here are the steps to specify your own logic for validating a username and password for the backoffice: + +1. Create an implementation of `Umbraco.Cms.Core.Security.IBackOfficeUserPasswordChecker` + + * There is one method in this interface: `Task CheckPasswordAsync(BackOfficeIdentityUser user, string password);` + * The result of this method can be 3 things: + * ValidCredentials = The credentials entered are valid and the authorization should proceed + * InvalidCredentials = The credentials entered are not valid and the authorization process should return an error + * FallbackToDefaultChecker = This is an optional result which can be used to fallback to Umbraco's default authorization process if the credentials could not be verified by your own custom implementation + + For example, to always allow login when the user enters the password `test` you could do: + + ```csharp + using System.Threading.Tasks; + using Umbraco.Core.Models.Identity; + using Umbraco.Core.Security; + + namespace MyNamespace; + + public class MyPasswordChecker : IBackOfficeUserPasswordChecker + { + public Task CheckPasswordAsync(BackOfficeIdentityUser user, string password) + { + var result = (password == "test") + ? Task.FromResult(BackOfficeUserPasswordCheckerResult.ValidCredentials) + : Task.FromResult(BackOfficeUserPasswordCheckerResult.InvalidCredentials); + + return result; + } + } + ``` +2. Register the `MyPasswordChecker` in your `Program.cs` file: + + ```csharp + builder.CreateUmbracoBuilder() + .AddBackOffice() + .AddWebsite() + .AddDeliveryApi() + .AddComposers() + .Build(); + + builder.Services.AddUnique(); + ``` + +{% hint style="info" %} +If the username entered in the login screen does not exist in Umbraco, then `MyPasswordChecker()` does not run. Instead, Umbraco will immediately fall back to its internal checks, which is the default Umbraco behavior. +{% endhint %} diff --git a/16/umbraco-cms/reference/security/external-login-providers.md b/16/umbraco-cms/reference/security/external-login-providers.md new file mode 100644 index 00000000000..9166355acdc --- /dev/null +++ b/16/umbraco-cms/reference/security/external-login-providers.md @@ -0,0 +1,795 @@ +--- +description: >- + Umbraco supports external login providers (OAuth) for performing + authentication of your users and members. +--- + +# External login providers + +Both the Umbraco backoffice users and website members support external login providers (OAuth) for performing authentication. This could be any OpenIDConnect provider such as Entra ID/Azure Active Directory, Identity Server, Google, or Facebook. + +{% hint style="info" %} +Unlike previous major releases of Umbraco the use of the Identity Extensions package is no longer required. +{% endhint %} + +Install an appropriate Nuget package for the provider you wish to use. Some popular ones found in Nuget include: + +* [Google](https://www.nuget.org/packages/Microsoft.AspNetCore.Authentication.Google) +* [Facebook](https://www.nuget.org/packages/Microsoft.AspNetCore.Authentication.Facebook) +* [Microsoft](https://www.nuget.org/packages/Microsoft.AspNetCore.Authentication.MicrosoftAccount/) +* [Twitter](https://www.nuget.org/packages/Microsoft.AspNetCore.Authentication.Twitter/3.0.0) +* [Open ID Connect](https://www.nuget.org/packages/Microsoft.AspNetCore.Authentication.OpenIdConnect) +* [Others](https://docs.microsoft.com/en-us/aspnet/core/security/authentication/social/other-logins?view=aspnetcore-5.0) + +{% hint style="info" %} +In some cases, when using Azure AD for login, you may encounter the following error: `OpenIdConnectProtocol requires the jwt token to have an 'iss' claim`. + +Install a newer version of `Microsoft.IdentityModel.Protocols.OpenIdConnect` to solve this problem. +{% endhint %} + +## Try it out + +{% content-ref url="../../tutorials/add-microsoft-entra-id-authentication.md" %} +[add-microsoft-entra-id-authentication.md](../../tutorials/add-microsoft-entra-id-authentication.md) +{% endcontent-ref %} + +{% content-ref url="../../tutorials/add-google-authentication.md" %} +[add-google-authentication.md](../../tutorials/add-google-authentication.md) +{% endcontent-ref %} + +
+ +Umbraco OpenIdConnect Example [Community-made] + +This community-created package with a complete Umbraco solution incl. an SQLite database demonstrates how OpenID Connect can be used: [Umbraco OpenIdConnect Example](https://github.com/jbreuer/Umbraco-OpenIdConnect-Example). + +It is great for testing and for trying out the implementation before building it into your project. + +**This project is not managed or maintained by Umbraco HQ.** + +
+ +
+ +Umbraco Entra ID (Azure AD) Example [Community-made] + +This community-created package will allow you to automatically create Umbraco user accounts for users in your directory. This will then associate the Umbraco users with groups based on their AD group: [Umbraco.Community.AzureSSO](https://github.com/Gibe/Umbraco.Community.AzureSSO). + +**This project is not managed or maintained by Umbraco HQ.** + +
+ +## Extend core functionality + +When you are implementing your own custom authentication on Users and/or Members on your Umbraco CMS website, you are effectively extending existing features. + +The process requires adding a couple of new classes (`.cs` files) to your Umbraco project: + +* **Custom-named configuration** to add additional configuration for handling different options related to the authentication. [See a generic example of the configuration class to learn more.](external-login-providers.md#custom-named-configuration) +* A **composer and named** to extend on the default authentication implementation in Umbraco CMS for either Users or Members. [See a generic example to learn more.](external-login-providers.md#generic-backoffice-login-provider-composer) + +You can setup similar behavior using a [static extension class](external-login-providers.md#static-extension-class) and add them straight into the `Program.cs` file. But you will lose access to dependency injection this way, thus our helper class. + +{% hint style="info" %} +It is also possible to register the configuration class directly into the extension class. See examples of how this is done in the [generic examples for the static extension class](external-login-providers.md#static-extension-class). +{% endhint %} + +## Auto-linking + +Traditionally, a backoffice User or frontend Member will need to exist in Umbraco first. Once they exist there, they can link their user account to an external login provider. + +In many cases, however, the external login provider you install will be the source of truth for all of your users and members. + +In this case, you will want to provide a Single Sign On (SSO) approach to logging in. This would enable the creation of user accounts on the external login provider and then automatically give them access to Umbraco. This is called **auto-linking**. + +### Local logins + +When auto-linking is configured, then any auto-linked user or member will have an empty password assigned. This means that they will not be able to log in locally (via username and password). In order to log in locally, they will have to assign a password to their account in the backoffice or the edit profile page. + +For users specifically, if the `DenyLocalLogin` option is enabled, all password-changing functionality in the backoffice is disabled, and local login is not possible. + +### Transferring Claims from External identities + +In some cases, you may want to flow a Claim returned in your external login provider to the Umbraco backoffice identity's Claims. This could be the authentication cookie. Flowing Claims between the two can be done during the `OnAutoLinking` and `OnExternalLogin`. + +The reason for wanted to flow a Claim could be to store the external login provider user ID into the backoffice identity cookie. It can then be retrieved on each request to look up data in another system needing the current user ID from the external login provider. + +{% hint style="warning" %} +Do not flow large amounts of data into the backoffice identity. This information is stored in the backoffice authentication cookie and cookie limits will apply. Data like Json Web Tokens (JWT) needs to be [persisted](external-login-providers.md#storing-external-login-provider-data) somewhere to be looked up and not stored within the backoffice identity itself. +{% endhint %} + +#### Example + +This is a simplistic example of brevity including no null checks, etc. + +```csharp +OnAutoLinking = (user, loginInfo) => { + // You can customize the user before it's linked. + // i.e. Modify the user's groups based on the Claims returned + // in the externalLogin info + var extClaim = externalLogin + .Principal + .FindFirst("MyClaim"); + user.Claims.Add(new IdentityUserClaim + { + ClaimType = extClaim.Type, + ClaimValue = extClaim.Value, + UserId = user.Id + }); +}, +OnExternalLogin = (user, loginInfo) => { + // You can customize the user before it's saved whenever they have + // logged in with the external provider. + // i.e. Sync the user's name based on the Claims returned + // in the externalLogin info + var extClaim = externalLogin + .Principal + .FindFirst("MyClaim"); + user.Claims.Add(new IdentityUserClaim + { + ClaimType = extClaim.Type, + ClaimValue = extClaim.Value, + UserId = user.Id + }); + return true; +} +``` + +### Storing external login provider data + +In some cases, you may need to persist data from your external login provider like Access Tokens, etc. + +You can persist this data to the affiliated user's external login data via the `IExternalLoginWithKeyService`. The `void Save(Guid userOrMemberKey,IEnumerable tokens)` overload takes a new model of type `IEnumerable`. + +`IExternalLogin` contains a property called `UserData`. This is a blob text column which can store any arbitrary data for the external login provider. + +{% hint style="info" %} +Be aware that the local Umbraco user must already exist and be linked to the external login provider before data can be stored here. In cases where auto-linking occurs and the user isn't yet created, you need to store the data in memory first during auto-linking. Then you can persist the data to the service once the user is linked and created. +{% endhint %} + +### Auto-linking on backoffice authentication + +For some providers, it does not make sense to use auto-linking. This is especially true for public providers such as Google or Facebook. + +In those cases, it would mean that anyone who has a Google or Facebook account can log into your site. + +If auto-linking for public providers such as these was needed you would need to limit the access. This can be done by domain or other information provided in the claims using the options/callbacks specified in those provider's authentication options. + +#### Is your project hosted on Umbraco Cloud? + +Umbraco Cloud uses Umbraco ID for all authentication, including access to the Umbraco Backoffice. + +If you are working with External Login Providers on a project hosted on Umbraco Cloud, extra configuration is required. + +To disable the automatic redirect to Umbraco ID, follow these steps: + +1. Open the `umbraco-cloud.json` file in your favorite code editor. +2. Locate the `Identity` section. +3. Add a new key: `AutoRedirectLogin`. +4. Set the value to `false`. + +{% code title="umbraco-cloud.json" %} + +```json +"Identity": { + "ClientId": "0297c0f6-83ad-4481-9ae2-07a3f5475333", + "ClientSecret": "Q5~T526ixOHlj47lg7Mu7_.zN1fK.7ua.9", + "EnvironmentId": "3105e6eb-4a1e-42dd-91e9-ffdbe3dd30a8", + "LocalLoginRedirectUri": "https://redirect.identity.umbraco.com", + "AutoRedirectLogin": false + } +``` + +{% endcode %} + +### Auto-linking on Member authentication + +Auto-linking on Member authentication only makes sense if you have a public member registration already or the provider does not have public account creation. + +## Generic examples + +The following section presents a series of generic examples. + +{% hint style="warning" %} +"_Provider_" is a placeholder used to replace the names of actual external login providers. When you implement your own custom authentication, you will need to use the correct method names for the chosen provider. Otherwise, the examples will not work as intended. +{% endhint %} + +### Custom-named configuration + +The configuration file is used to configure a handful of different options for the authentication setup. A generic example of such file is shown below. + +{% tabs %} +{% tab title="User Authentication" %} +{% code title="GenericBackOfficeExternalLoginProviderOptions.cs" lineNumbers="true" %} +```csharp +using Microsoft.Extensions.Options; +using Umbraco.Cms.Api.Management.Security; +using Umbraco.Cms.Core; + +namespace MyUmbracoProject.CustomAuthentication; + +public class GenericBackOfficeExternalLoginProviderOptions : IConfigureNamedOptions +{ + public const string SchemeName = "Generic"; + public void Configure(string name, BackOfficeExternalLoginProviderOptions options) + { + if (name != Constants.Security.BackOfficeExternalAuthenticationTypePrefix + SchemeName) + { + return; + } + + Configure(options); + } + + public void Configure(BackOfficeExternalLoginProviderOptions options) + { + // The following options are relevant if you + // want to configure auto-linking on the authentication. + options.AutoLinkOptions = new ExternalSignInAutoLinkOptions( + + // Set to true to enable auto-linking + autoLinkExternalAccount: true, + + // [OPTIONAL] + // Default: "Editor" + // Specify User Group. + defaultUserGroups: new[] { Constants.Security.EditorGroupAlias }, + + // [OPTIONAL] + // Default: The culture specified in appsettings.json. + // Specify the default culture to create the User as. + // It can be dynamically assigned in the OnAutoLinking callback. + defaultCulture: null, + + // [OPTIONAL] + // Disable the ability to link/unlink manually from within + // the Umbraco backoffice. + // Set this to false if you don't want the user to unlink + // from this external provider. + allowManualLinking: false + ) + { + // [OPTIONAL] Callback + OnAutoLinking = (autoLinkUser, loginInfo) => + { + // Customize the user before it's linked. + // Modify the User's groups based on the Claims returned + // in the external login info. + }, + + // [OPTIONAL] Callback + OnExternalLogin = (user, loginInfo) => + { + // Customize the User before it is saved whenever they have + // logged in with the external provider. + // Sync the Users name based on the Claims returned + // in the external login info + + // Returns a boolean indicating if sign-in should continue or not. + return true; + } + }; + + } +} +``` +{% endcode %} +{% endtab %} + +{% tab title="Member Authentication" %} +{% code title="ProviderMembersExternalLoginProviderOptions.cs" lineNumbers="true" %} +```csharp +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core; +using Umbraco.Cms.Web.Common.Security; + +namespace MyUmbracoProject.CustomAuthentication; + +public class ProviderMembersExternalLoginProviderOptions : IConfigureNamedOptions +{ + public const string SchemeName = "OpenIdConnect"; + public void Configure(string? name, MemberExternalLoginProviderOptions options) + { + if (name != Constants.Security.MemberExternalAuthenticationTypePrefix + SchemeName) + { + return; + } + + Configure(options); + } + + public void Configure(MemberExternalLoginProviderOptions options) + { + // The following options are relevant if you + // want to configure auto-linking on the authentication. + options.AutoLinkOptions = new MemberExternalSignInAutoLinkOptions( + + // Set to true to enable auto-linking + autoLinkExternalAccount: true, + + // [OPTIONAL] + // Default: The culture specified in appsettings.json. + // Specify the default culture to create the Member as. + // It can be dynamically assigned in the OnAutoLinking callback. + defaultCulture: null, + + // [OPTIONAL] + // Specify the default "IsApproved" status. + // Must be true for auto-linking. + defaultIsApproved: true, + + // [OPTIONAL] + // Default: "Member" + // Specify the Member Type alias. + defaultMemberTypeAlias: Constants.Security.DefaultMemberTypeAlias + ) + { + // [OPTIONAL] Callback + OnAutoLinking = (autoLinkUser, loginInfo) => + { + // Customize the Member before it's linked. + // Modify the Members groups based on the Claims returned + // in the external ogin info. + }, + OnExternalLogin = (user, loginInfo) => + { + // Customize the Member before it is saved whenever they have + // logged in with the external provider. + // Sync the Members name based on the Claims returned + // in the external login info + + // Returns a boolean indicating if sign-in should continue or not. + return true; + } + }; + } +} +``` +{% endcode %} +{% endtab %} +{% endtabs %} + +Next, you need to register the button in the BackOffice. This is done by adding a manifest file to the `App_Plugins/ExternalLoginProviders` folder. + +{% code title="umbraco-package.json" lineNumbers="true" %} +```json +{ + "$schema": "../../umbraco-package-schema.json", + "name": "My Auth Package", + "allowPublicAccess": true, + "extensions": [ + { + "type": "authProvider", + "alias": "My.AuthProvider.Generic", + "name": "My Auth Provider", + "forProviderName": "Umbraco.Generic", + "meta": { + "label": "Generic", + "defaultView": { + "icon": "icon-cloud" + }, + "behavior": { + "autoRedirect": false + }, + "linking": { + "allowManualLinking": true + } + } + } + ] +} +``` +{% endcode %} + +You have a few options to configure the button: + +* `element` - Define your own custom element for the button. This is useful if you want to display something other than a button, For example: a link or an image. For more information, see the [Customizing the BackOffice Login Button](external-login-providers.md#customizing-the-backoffice-login-button) section. +* `forProviderName` - The name of the provider you are configuring. This should match the `SchemeName` in the `GenericBackOfficeExternalLoginProviderOptions` class with "Umbraco." prepended. +* `meta.label` - The label to display on the button. The user will see this text. For example: "Sign in with Generic". +* `meta.defaultView.icon` - The icon to display on the button. You can use any of the icons from the Umbraco Icon Picker. If you want to use a custom icon, you need to first register it to the [`icons` extension point](../../customizing/extending-overview/extension-types/icons.md). +* `meta.defaultView.color` - (Default: "default") The color style of the button. You can use any color style from the [Umbraco UI Library](https://uui.umbraco.com/?path=/story/uui-button--looks-and-colors). + * Default (blue) + * Positive (green) + * Warning (yellow) + * Danger (red) +* `meta.defaultView.look` - (Default: "secondary") The look of the button. You can use any of the looks from the [Umbraco UI Library](https://uui.umbraco.com/?path=/story/uui-button--looks-and-colors). + * Primary (solid background color, white text) + * Secondary (grey background, colored text) + * Outline (white background with sold grey border, colored text) + * Placeholder (white with dotted grey border, colored text) +* `meta.behavior.autoRedirect` - Automatically redirects the user to the external login provider, skipping the Umbraco login page, unless the user has specifically logged out or timed out. +* `meta.behavior.popupTarget` - (Default: "umbracoAuthPopup") The target for the popup window. This is the name of the window that will be opened when the user clicks the button. If you want to open the login page in a new tab, you can set this to "\_blank". +* `meta.behavior.popupFeatures` - (Default: "width=600,height=600,menubar=no,location=no,resizable=yes,scrollbars=yes,status=no,toolbar=no") The features of the popup window. This is a string of comma-separated key-value pairs. For example: "width=600,height=600". You can read more on the [MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/API/Window/open#features). +* `meta.linking.allowManualLinking` - Allows the user to link or unlink their account from the BackOffice. You need to allow manual linking on the `ExternalSignInAutoLinkOptions` as well. + +The button will now be displayed on the login page in the Umbraco Backoffice. + +

The login page with a Generic button shown

+ +### Generic backoffice login provider composer + +A composer and `genericAuthenticationOptions` configuration class to setup the authentication options for the generic authentication provider using dependency injection. Replace `genericAuthenticationOptions` with the Options method from the provider you are using. + +{% code title="GenericBackOfficeExternalLoginComposer.cs" lineNumbers="true" %} +```csharp +// this example uses a non existing generic OathProvider NuGet package +// but any package that uses the Microsoft.AspNetCore.Authentication.OAuth.OathOptions will work +using AspNet.Security.OAuth.Generic; +using Microsoft.Extensions.Options; +using Umbraco.Cms.Api.Management.Security; +using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Web.Common.Helpers; + +namespace MyUmbracoProject.CustomAuthentication; + +public class GenericBackOfficeExternalLoginComposer : IComposer +{ + public void Compose(IUmbracoBuilder builder) + { + // register the generic loginProvider options (auto linking, manual linking, ...) + builder.Services.ConfigureOptions(); + // register the generic authtication options (secret, callback, errorhandling, ...) + builder.Services.ConfigureOptions(); + + builder.AddBackOfficeExternalLogins(logins => + { + logins.AddBackOfficeLogin( + backOfficeAuthenticationBuilder => + { + // this Add... method will be part of the OathProvider nuget package you install + backOfficeAuthenticationBuilder.AddGenericProvider( + // replace AddGenericProvider with the Add method of the provider you are using + BackOfficeAuthenticationBuilder.SchemeForBackOffice(GenericBackOfficeExternalLoginProviderOptions + .SchemeName)!, + options => + { + // need to give an empty action here for the options pattern configuration to work 🤷 + // if you do not wish to use the umbraco default error handling and hardcode all your values instead of injecting them, + // you can set the configuration right here instead. + }); + }); + }); + } +} + +// the ...AuthenticationOptions method will be part of the OathProvider nuget package you install +// check the Add... method invoked on the backOfficeAuthenticationBuilder to figure out the correct type +public class ConfigureGenericAuthenticationOptions : IConfigureNamedOptions +{ + private readonly OAuthOptionsHelper _helper; + + public ConfigureGenericAuthenticationOptions(OAuthOptionsHelper helper) + { + _helper = helper; + } + + public void Configure(GenericAuthenticationOptions options) + { + // since we have access to dependency injection, these values can be read from the app settings using the IOptions pattern + options.CallbackPath = "/umbraco-signing-generic"; // can be anything as middleware will add this to the route table + options.ClientId = "your client id for the login provider"; + options.ClientSecret = "your client secret for the login provider"; + options.Scope.Add("user:email"); // email is needed for auto linking purposes + + // This will redirect error responses from the login provider towards the default umbraco oath login error page + // which will try to display the error state in a meaningful way. + // You can implement your own error handling by handling options.Events.OnAccessDenied & options.Events.OnRemoteFailure + _helper.SetDefaultErrorEventHandling(options, GenericBackOfficeExternalLoginProviderOptions.SchemeName); + } + + public void Configure(string? name, GenericAuthenticationOptions options) + { + // only configure the options if it is for the backend + if (name == BackOfficeAuthenticationBuilder.SchemeForBackOffice(GenericBackOfficeExternalLoginProviderOptions + .SchemeName)) + { + Configure(options); + } + } +} + +``` +{% endcode %} + +### Static extension class + +The extension class is required to extend on the default authentication implementation in Umbraco CMS. A generic example of such extension class can be seen below. + +{% tabs %} +{% tab title="User Authentication" %} +{% code title="GenericBackofficeAuthenticationExtensions.cs" lineNumbers="true" %} +```csharp +using Umbraco.Cms.Core.DependencyInjection; +using Umbraco.Extensions; +using Umbraco.Cms.Web.BackOffice.Security; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Configuration; + +namespace MyUmbracoProject.CustomAuthentication; + +public static class GenericBackofficeAuthenticationExtensions +{ + public static IUmbracoBuilder AddGenericBackofficeAuthentication(this IUmbracoBuilder builder) + { + // Register ProviderBackOfficeExternalLoginProviderOptions here rather than require it in startup + builder.Services.ConfigureOptions(); + + builder.AddBackOfficeExternalLogins(logins => + { + logins.AddBackOfficeLogin( + backOfficeAuthenticationBuilder => + { + // The scheme must be set with this method to work for the back office + // Replace GenericOfficeExternalLoginProviderOptions with the Options method from the provider you are using + var schemeName = + backOfficeAuthenticationBuilder.SchemeForBackOffice(GenericOfficeExternalLoginProviderOptions + .SchemeName); + + ArgumentNullException.ThrowIfNull(schemeName); + + // Replace AddGenericProvider with the Add method from the provider you are using + + backOfficeAuthenticationBuilder.AddGenericProvider( + schemeName, + options => + { + // Callback path: Represents the URL to which the browser should be redirected to. + // The default value is '/signin-provider'. + // The value here should match what you have configured in you external login provider. + // The value needs to be unique. + options.CallbackPath = "/umbraco-provider-signin"; + options.ClientId = "YOURCLIENTID"; // Replace with your client id generated while creating OAuth client ID + options.ClientSecret = "YOURCLIENTSECRET"; // Replace with your client secret generated while creating OAuth client ID + + // Example: Map Claims + // Relevant when using auto-linking. + options.GetClaimsFromUserInfoEndpoint = true; + options.TokenValidationParameters.NameClaimType = "name"; + + // Example: Add scopes + options.Scope.Add("email"); + }); + }); + }); + return builder; + } +} +``` +{% endcode %} +{% endtab %} + +{% tab title="Member Authentication" %} +{% code title="ProviderMembersAuthenticationExtensions.cs" lineNumbers="true" %} +```csharp +using Microsoft.Extensions.DependencyInjection; +using Umbraco.Cms.Core.DependencyInjection; +using Umbraco.Extensions; + +namespace MyUmbracoProject.CustomAuthentication; + +public static class ProviderMemberAuthenticationExtensions +{ + public static IUmbracoBuilder AddProviderMemberAuthentication(this IUmbracoBuilder builder) + { + // [OPTIONAL] + // Register ProviderMembersExternalLoginProviderOptions here rather than require it in startup + builder.Services.ConfigureOptions(); + + builder.AddMemberExternalLogins(logins => + { + logins.AddMemberLogin( + memberAuthenticationBuilder => + { + memberAuthenticationBuilder.AddProvider( + // The scheme must be set with this method to work for the back office + memberAuthenticationBuilder.SchemeForMembers(ProviderMembersExternalLoginProviderOptions.SchemeName), + options => + { + // Callback path: Represents the URL to which the browser should be redirected to. + // The default value is `/signin-oidc`. + // The value here should match what you have configured in your external login provider. + // The value needs to be unique. + options.CallbackPath = "/umbraco-provider-signin"; + + options.ClientId = "YOURCLIENTID"; + options.ClientSecret = "YOURCLIENTSECRET"; + + // Example: Save login tokens + options.SaveTokens = true; + + }); + }); + }); + return builder; + } +} +``` +{% endcode %} +{% endtab %} +{% endtabs %} + +For a more in-depth article on how to set up OAuth providers in .NET refer to the [Microsoft Documentation](https://learn.microsoft.com/en-us/aspnet/core/security/authentication/social/?view=aspnetcore-8.0\&tabs=visual-studio). + +### Customizing the BackOffice Login Button + +If you want to customize the login button, you can do so by adding a custom element to the manifest file. This is useful if you want to display something other than a button. For example, a link or an image. + +The path to the custom view is a virtual path, like this example: `"~/App_Plugins/MyPlugin/BackOffice/my-external-login.js"`. + +When a custom view is specified, it is 100% up to this module to perform all the required logic. + +The module should define a Custom Element and export it as default. Optionally, the Custom Element can declare a number of properties to be passed to it. These properties are: + +* `manifest`: The manifest object for the provider that you registered in the `umbraco-package.json` file. +* `onSubmit`: A function that is called when the form is submitted. This function will handle the form submission and redirect the user to the external login provider. +* `userLoginState`: The current view state of the user. This can be one of the following values: + * `loggingIn`: The user is on the login screen. + * `loggedOut`: The user clicked the logout button and is on the logged-out screen. + * `timedOut`: The user's session has timed out and they are on the timed-out screen. + +**TypeScript** + +If you use TypeScript, you can use this interface to define the properties: + +{% code title="login-types.ts" %} +```typescript +type UserViewState = 'loggingIn' | 'loggedOut' | 'timedOut'; + +interface IExternalLoginCustomViewElement { + displayName?: string; + providerName?: string; + externalLoginUrl?: string; + userViewState?: UserViewState; +}; +``` +{% endcode %} + +#### Examples + +The Custom Element can be implemented in a number of ways with many different libraries or frameworks. The following examples show how to make a button appear and redirect to the external login provider. You will learn how to use the `externalLoginUrl` property to redirect to the external login provider. The login form should look like this when you open Umbraco: + +![Login form with custom external login button](images/external-login-provider-javascript.jpg) + +When you click the button, the form will submit a POST request to the `externalLoginUrl` property. The external login provider will then redirect back to the Umbraco site with the user logged in. + +{% hint style="info" %} +You have access to the [Umbraco UI Library](../../customizing/ui-library.md) in the custom element. You can use the UUI components directly in your template. +{% endhint %} + +{% code title="App_Plugins/ExternalLoginProviders/umbraco-package.json" lineNumbers="true" %} +```json +{ + "$schema": "../../umbraco-package-schema.json", + "name": "My Auth Package", + "allowPublicAccess": true, + "extensions": [ + { + "type": "authProvider", + "alias": "My.AuthProvider.Generic", + "name": "My Auth Provider", + "forProviderName": "Umbraco.Generic", + "element": "/App_Plugins/ExternalLoginProviders/my-external-login.js", // This line has been added + "meta": { + "label": "Generic", + "defaultView": { + "icon": "icon-cloud" + }, + "behavior": { + "autoRedirect": false + }, + "linking": { + "allowManualLinking": true + } + } + } + ] +} +``` +{% endcode %} + +{% tabs %} +{% tab title="Vanilla (JavaScript)" %} +We have to define a template first and then the custom element itself. The template is a small HTML form with a button. The custom element will then render the template and attach an event listener for clicks on the button in the `constructor` method. + +{% code title="~/App_Plugins/ExternalLoginProviders/my-external-login.js" lineNumbers="true" %} +```javascript +const template = document.createElement('template'); +template.innerHTML = ` + +

Our Company

+

If you have signed up with Our Company, you can sign in to Umbraco by clicking the button below.

+ + + Sign in with Our Company + +`; + +/** + * This is an example how to set up a custom element as a Web Component. + */ +export default class MyCustomView extends HTMLElement { + manifest = {}; + onSubmit = () => {}; + userLoginState = ''; + + constructor() { + super(); + this.attachShadow({ mode: 'open' }); + this.shadowRoot.appendChild(template.content.cloneNode(true)); + + this.shadowRoot.getElementById('button').addEventListener('click', () => { + this.onSubmit(this.manifest.forProviderName); + }); + } +} + +customElements.define('my-custom-view', MyCustomView); +``` +{% endcode %} +{% endtab %} + +{% tab title="Lit (JavaScript)" %} +It is also possible to use a library like [Lit](https://lit.dev/) to render the custom element. The following example shows how to use Lit to render the custom element. The custom element will render a form with a button. The button will submit the form to the `externalLoginUrl` property. We do not have to perform any logic in the `constructor` method because Lit will automatically update any event listeners. Styling is also handled by Lit in the `static styles` property. + +We are using Lit version 3 in this example imported directly from a JavaScript delivery network to keep the example slim. You can also use a bundler like [Vite](https://vitejs.dev) to bundle the Lit library with your custom element. + +{% hint style="info" %} +To learn more about how to set up a project with Vite, see the [Creating your first extension](../../tutorials/creating-your-first-extension.md) tutorial. +{% endhint %} + +{% code title="~/App_Plugins/ExternalLoginProviders/my-external-login.js" lineNumbers="true" %} +```javascript +import { LitElement, css, html } from 'https://cdn.jsdelivr.net/gh/lit/dist@3/core/lit-core.min.js'; + +/** + * This is an example how to set up a LitElement component. + */ +export default class MyLitView extends LitElement { + static get properties() { + return { + manifest: { type: Object }, + onSubmit: { type: Function }, + userLoginState: { type: String, state: true } + }; + } + + get displayName() { + return this.manifest.meta?.label ?? this.manifest.forProviderName; + } + + render() { + return html` +

Our Company

+

If you have an account with Our Company, you can sign in to Umbraco by clicking the button below.

+

The user is currently: ${this.userLoginState}

+ this.onSubmit(this.manifest.forProviderName)}> + + ${this.displayName} + + `; + } + + static styles = css` + :host { + display: block; + width: 100%; + } + #button { + width: 100%; + } + `; +} + +customElements.define('my-lit-view', MyLitView); +``` +{% endcode %} +{% endtab %} +{% endtabs %} diff --git a/16/umbraco-cms/reference/security/images/2fa-login-custom-view.png b/16/umbraco-cms/reference/security/images/2fa-login-custom-view.png new file mode 100644 index 00000000000..32e6e6ae807 Binary files /dev/null and b/16/umbraco-cms/reference/security/images/2fa-login-custom-view.png differ diff --git a/16/umbraco-cms/reference/security/images/2fa-login-default-view.jpg b/16/umbraco-cms/reference/security/images/2fa-login-default-view.jpg new file mode 100644 index 00000000000..34bad2a407a Binary files /dev/null and b/16/umbraco-cms/reference/security/images/2fa-login-default-view.jpg differ diff --git a/16/umbraco-cms/reference/security/images/2fa-member-backoffice.png b/16/umbraco-cms/reference/security/images/2fa-member-backoffice.png new file mode 100644 index 00000000000..af270105742 Binary files /dev/null and b/16/umbraco-cms/reference/security/images/2fa-member-backoffice.png differ diff --git a/16/umbraco-cms/reference/security/images/2fa-members-configuration.png b/16/umbraco-cms/reference/security/images/2fa-members-configuration.png new file mode 100644 index 00000000000..bcc5c98c1a9 Binary files /dev/null and b/16/umbraco-cms/reference/security/images/2fa-members-configuration.png differ diff --git a/16/umbraco-cms/reference/security/images/configure-2fa.jpg b/16/umbraco-cms/reference/security/images/configure-2fa.jpg new file mode 100644 index 00000000000..d488ece4451 Binary files /dev/null and b/16/umbraco-cms/reference/security/images/configure-2fa.jpg differ diff --git a/16/umbraco-cms/reference/security/images/disable-2fa.jpg b/16/umbraco-cms/reference/security/images/disable-2fa.jpg new file mode 100644 index 00000000000..5a35c04db38 Binary files /dev/null and b/16/umbraco-cms/reference/security/images/disable-2fa.jpg differ diff --git a/16/umbraco-cms/reference/security/images/enable-2fa.jpg b/16/umbraco-cms/reference/security/images/enable-2fa.jpg new file mode 100644 index 00000000000..8b7a18236b9 Binary files /dev/null and b/16/umbraco-cms/reference/security/images/enable-2fa.jpg differ diff --git a/16/umbraco-cms/reference/security/images/export-member.png b/16/umbraco-cms/reference/security/images/export-member.png new file mode 100644 index 00000000000..61902aa08b7 Binary files /dev/null and b/16/umbraco-cms/reference/security/images/export-member.png differ diff --git a/16/umbraco-cms/reference/security/images/external-login-provider-javascript.jpg b/16/umbraco-cms/reference/security/images/external-login-provider-javascript.jpg new file mode 100644 index 00000000000..696bcbe5e63 Binary files /dev/null and b/16/umbraco-cms/reference/security/images/external-login-provider-javascript.jpg differ diff --git a/16/umbraco-cms/reference/security/images/foldersondisk-after.png b/16/umbraco-cms/reference/security/images/foldersondisk-after.png new file mode 100644 index 00000000000..e32b7348c89 Binary files /dev/null and b/16/umbraco-cms/reference/security/images/foldersondisk-after.png differ diff --git a/16/umbraco-cms/reference/security/images/foldersondisk-before.png b/16/umbraco-cms/reference/security/images/foldersondisk-before.png new file mode 100644 index 00000000000..165a58a08bb Binary files /dev/null and b/16/umbraco-cms/reference/security/images/foldersondisk-before.png differ diff --git a/16/umbraco-cms/reference/security/images/google-oauth-v8.png b/16/umbraco-cms/reference/security/images/google-oauth-v8.png new file mode 100644 index 00000000000..0fd07e6dcac Binary files /dev/null and b/16/umbraco-cms/reference/security/images/google-oauth-v8.png differ diff --git a/16/umbraco-cms/reference/security/images/login-2fa.png b/16/umbraco-cms/reference/security/images/login-2fa.png new file mode 100644 index 00000000000..d4d0dbfefaf Binary files /dev/null and b/16/umbraco-cms/reference/security/images/login-2fa.png differ diff --git a/16/umbraco-cms/reference/security/images/login-external.jpg b/16/umbraco-cms/reference/security/images/login-external.jpg new file mode 100644 index 00000000000..6d030c6f996 Binary files /dev/null and b/16/umbraco-cms/reference/security/images/login-external.jpg differ diff --git a/16/umbraco-cms/reference/security/images/sensitive-data-hidden-v8.png b/16/umbraco-cms/reference/security/images/sensitive-data-hidden-v8.png new file mode 100644 index 00000000000..b0264767ced Binary files /dev/null and b/16/umbraco-cms/reference/security/images/sensitive-data-hidden-v8.png differ diff --git a/16/umbraco-cms/reference/security/images/sensitive-data-hidden.png b/16/umbraco-cms/reference/security/images/sensitive-data-hidden.png new file mode 100644 index 00000000000..e393e07508d Binary files /dev/null and b/16/umbraco-cms/reference/security/images/sensitive-data-hidden.png differ diff --git a/16/umbraco-cms/reference/security/images/sensitive-data-user-group-v8.png b/16/umbraco-cms/reference/security/images/sensitive-data-user-group-v8.png new file mode 100644 index 00000000000..392cf74e23a Binary files /dev/null and b/16/umbraco-cms/reference/security/images/sensitive-data-user-group-v8.png differ diff --git a/16/umbraco-cms/reference/security/images/sensitive-data-user-group.jpg b/16/umbraco-cms/reference/security/images/sensitive-data-user-group.jpg new file mode 100644 index 00000000000..32e769a51f0 Binary files /dev/null and b/16/umbraco-cms/reference/security/images/sensitive-data-user-group.jpg differ diff --git a/16/umbraco-cms/reference/security/images/update-member-type-v8.png b/16/umbraco-cms/reference/security/images/update-member-type-v8.png new file mode 100644 index 00000000000..8ffd02dbaf5 Binary files /dev/null and b/16/umbraco-cms/reference/security/images/update-member-type-v8.png differ diff --git a/16/umbraco-cms/reference/security/images/update-member-type.png b/16/umbraco-cms/reference/security/images/update-member-type.png new file mode 100644 index 00000000000..88d13424bde Binary files /dev/null and b/16/umbraco-cms/reference/security/images/update-member-type.png differ diff --git a/16/umbraco-cms/reference/security/images/user-panel.jpg b/16/umbraco-cms/reference/security/images/user-panel.jpg new file mode 100644 index 00000000000..01db58ce5fa Binary files /dev/null and b/16/umbraco-cms/reference/security/images/user-panel.jpg differ diff --git a/16/umbraco-cms/reference/security/images/verify-disable.jpg b/16/umbraco-cms/reference/security/images/verify-disable.jpg new file mode 100644 index 00000000000..a33c3229877 Binary files /dev/null and b/16/umbraco-cms/reference/security/images/verify-disable.jpg differ diff --git a/16/umbraco-cms/reference/security/images/webconfig-after.png b/16/umbraco-cms/reference/security/images/webconfig-after.png new file mode 100644 index 00000000000..cf2af909570 Binary files /dev/null and b/16/umbraco-cms/reference/security/images/webconfig-after.png differ diff --git a/16/umbraco-cms/reference/security/images/webconfig-before.png b/16/umbraco-cms/reference/security/images/webconfig-before.png new file mode 100644 index 00000000000..c350ea7fcc8 Binary files /dev/null and b/16/umbraco-cms/reference/security/images/webconfig-before.png differ diff --git a/16/umbraco-cms/reference/security/password-reset.md b/16/umbraco-cms/reference/security/password-reset.md new file mode 100644 index 00000000000..7435ef2784e --- /dev/null +++ b/16/umbraco-cms/reference/security/password-reset.md @@ -0,0 +1,25 @@ +--- +description: Learn about the security features put in place to protect Umbraco users from unauthorized access and password breaches. +--- + +# Password reset + +It's impossible to brute force the authentication on the login screen because after `MaxFailedAccessAttemptsBeforeLockout` the account of the user will be locked, and until that account is unlocked in the Users section, no attempt will succeed. + +## Password reset on login screen + +When you submit the password reset form, an email is sent to the user with a link. This link contains a random token for this user that is valid for 24 hours. + +The settings `AllowPasswordReset` is documented in the [Umbraco Security Settings](../configuration/securitysettings.md) and e-mail configuration settings in [Backoffice Login Password Reset Section](../../fundamentals/backoffice/login.md#password-reset) + +## Password reset of a non-existing user + +If the user that is specified in the form does not exist, no e-mail will be sent and there will be no response in the form that this user does not exist. This is done to prevent leaking which users have an account. + +## Password reset of a locked user + +If a user is locked out, it is possible to do a password reset. After the e-mail with the password reset link is followed, the user will still be locked out unless the user has specified the new password, in which case the user will automatically be unlocked. + +## Reset admin user password + +If you lost the admin user password and you need to reset it, [check this article](reset-admin-password.md). diff --git a/16/umbraco-cms/reference/security/reset-admin-password.md b/16/umbraco-cms/reference/security/reset-admin-password.md new file mode 100644 index 00000000000..c2820efc93d --- /dev/null +++ b/16/umbraco-cms/reference/security/reset-admin-password.md @@ -0,0 +1,33 @@ +# Reset admin password + +There is one default admin user in any Umbraco installation. This is the first user of the system. + +## Step one: Clear the connection string status in configuration + +The first step is to clear the connection string to the database in the configuration. This is done to trigger the installation wizard. + +That means that in your appsettings configuration files it should look like this: + +```json +{ + "ConnectionStrings": { + "umbracoDbDSN": "" + } +} +``` + +{% hint style="warning" %} +**Note that configuration can be read from many sources** + +Remember to check this connection string is not provided through environment variables or other configuration sources. +{% endhint %} + +## Step Two: Run the installer + +If you now open your browser and surf to the website, you will see that the installer launches. +Enter your new details, and use the original connection string. You are good to go. + +{% hint style="warning" %} +Make sure you protect a production websites from being highjacked as anyone will be able to reset the password during the last step. +This does also work if your site is in an upgrading state. +{% endhint %} diff --git a/16/umbraco-cms/reference/security/security-hardening.md b/16/umbraco-cms/reference/security/security-hardening.md new file mode 100644 index 00000000000..25587258f45 --- /dev/null +++ b/16/umbraco-cms/reference/security/security-hardening.md @@ -0,0 +1,52 @@ +--- +description: Learn how to strengthen the security of your Umbraco installation, and reduce the risk of unauthorized access. +--- + +# Umbraco Security Hardening + +Here you find some tips and trick for hardening the security of your Umbraco installation. + +## Lock down access to your Umbraco folder (IIS) + +It’s considered a good practice to lock down the Umbraco folder to specific IP addresses and/or IP ranges to ensure this folder is not available to everyone. + +The prerequisite of this to work is that you’re using [IISRewrite](../routing/iisrewriterules.md) + +If you’ve made sure that you’ve installed this on your server we can start locking down our Umbraco folder. This can be down by following these three steps. + +1. We are going to lock down /Umbraco/, but because API-controllers and Surface-controller will use the path /umbraco/api/ and /umbraco/surface/ these will also be locked down. Our first rule in the IISRewrite.config will be used to make sure that these are not locked by IP-address. + +```xml + + + + +``` + +Some older versions of Umbraco also relied on /umbraco/webservices/ for loadbalancing purposes. If you're loadbalancing you should also add umbraco/webservices to the rule. + +```xml + + + + +``` + +1. Get the IP-addresses of your client and write these down like a regular expression. If the IP-addresses are for example 213.3.10.8 and 88.4.43.108 the regular expression would be "213.3.10.8|88.4.43.108". +2. Lock down the Umbraco folder by putting this rule into your IISRewrite-rules + +```xml + + + + + + + +``` + +{% hint style="info" %} +If your server is behind a load balancer, you should use `{HTTP_X_FORWARDED_FOR}` instead of `{REMOTE_ADDR}` as the input for the rule. +{% endhint %} + +If you now go to `/umbraco/` from a different IP-address the login screen will not be rendered. diff --git a/16/umbraco-cms/reference/security/security-settings.md b/16/umbraco-cms/reference/security/security-settings.md new file mode 100644 index 00000000000..15c977c23ec --- /dev/null +++ b/16/umbraco-cms/reference/security/security-settings.md @@ -0,0 +1,22 @@ +# Umbraco Security Settings + +## Password settings + +The settings for Umbraco passwords are configurable in appsettings. There are two different configuration objects - One for Umbraco Members and one for Users. + +For more information see the [Security Settings documentation](../configuration/securitysettings.md#user-password-settings). + +## Password reset settings + +Umbraco backend users can [reset their own password](password-reset.md), or if they try too much, have a locked out account. + +To deactivate the User password reset look at the [Umbraco Settings Security](../configuration/securitysettings.md#allow-password-reset) section. + +To configure password reset verify the [Backoffice Login Password Reset](../../fundamentals/backoffice/login.md#password-reset) section. + +## Other security settings + +* [The Umbraco timeout in minutes](../configuration/globalsettings.md#timeout) +* [disableAlternativeTemplates](../configuration/webroutingsettings.md#disable-alternative-templates) If set to false this can be used to try to render pages in a way that they are not supposed to +* [disableFindContentByIdPath](../configuration/webroutingsettings.md#disable-find-content-by-id-path) If set to false this can be used to do an enumeration of the nodes in your website and find hidden pages. +* Umbraco Forms: [AntiForgeryToken](https://docs.umbraco.com/umbraco-forms/developer/configuration#enableantiforgerytoken) and DisableFormCaching diff --git a/16/umbraco-cms/reference/security/sensitive-data-on-members.md b/16/umbraco-cms/reference/security/sensitive-data-on-members.md new file mode 100644 index 00000000000..3b857b25678 --- /dev/null +++ b/16/umbraco-cms/reference/security/sensitive-data-on-members.md @@ -0,0 +1,58 @@ +--- +description: >- + Marking fields and properties on member data as sensitive will hide the data + in those fields for backoffice users that are not privy to the data. +--- + +# Sensitive data + +In this article, you will get an overview of how you can grant and/or deny your users access to sensitive data as well as how to mark data as sensitive. + +## Grant or deny access to sensitive data + +Every new Umbraco installation ships with a default set of User Groups. One of them is the **Sensitive data User Group**. To give users in the backoffice access to view and work with sensitive data, they need to be part of the Sensitive Data User Group. + +Any users who are not part of the Sensitive Data User Group, will not be able to see the data in the properties that are marked as sensitive. Instead, they will see a generic message: "_This value is hidden. If you need access to view this value please contact your website administrator._" + +![Sensitive data hidden](../../../../10/umbraco-cms/reference/security/images/sensitive-data-hidden-v8.png) + +While not part of the Sensitive Data User Group it is also not possible to export members or member data. + +Follow these steps in order to grant a user access to sensitive data: + +1. Navigate to the **Users** section in the Umbraco backoffice. +2. Ensure that **Users** are selected from the Users tree. +3. Select the **Groups** menu in the top-right corner. +4. Choose the **Sensitive data** group. + +![Sensitive data user group](../../../../10/umbraco-cms/reference/security/images/sensitive-data-user-group-v8.png) + +5. Click **Add** in the **Users** box on the right. +6. Select the users you want to give access to the sensitive data. +7. Click **Submit**. +8. **Save** the User Group. + +The users you have added to the Sensitive Data User Group will now be able to: + +* See member data that has been marked as sensitive, +* Mark data and properties on Member Types as sensitive, and +* Export members and member data. + +## Marking data as sensitive + +Once your user is added to the Sensitive Data User Group, you have access to add and configure member properties containing sensitive data. + +Follow the steps below to mark data as sensitive. + +1. Navigate to the **Settings** section in the Umbraco backoffice. +2. Open the **Member Types** in the **Settings** tree. +3. Select the Member Type you wish to edit. +4. **Add** a property or configure an existing property. +5. Locate the **Is sensitive data** option at the bottom of the **Property settings** dialog. +6. Click to enable. +7. Click **Submit** to update the property configuration. +8. Click **Save** to save the changes on the Member Type. + +When the **Is sensitive data** option is enabled, the value and data in the property will only be visible to the users with access to sensitive data. + +![Update member type](../../../../10/umbraco-cms/reference/security/images/update-member-type-v8.png) diff --git a/16/umbraco-cms/reference/security/serverside-file-validation.md b/16/umbraco-cms/reference/security/serverside-file-validation.md new file mode 100644 index 00000000000..d00f64a42bb --- /dev/null +++ b/16/umbraco-cms/reference/security/serverside-file-validation.md @@ -0,0 +1,78 @@ +--- +meta.Title: Server-side File Validation +description: This section describes how you can implement File Validation +--- + +# Server-side file validation + +Sometimes it might be necessary to validate the contents of a file before it gets saved to disk when uploading through the backoffice. + +To help with this, Umbraco supplies a `FileStreamSecurityValidator` that runs all registered `IFileStreamSecurityAnalyzer` implementations on the file streams it receives from it's different file upload endpoints. When any of the analyzers deem the file to be unsafe, the endpoint disregards the file and shows a relevant validation message where appropriate. This all happens in memory before the stream is written to a temporary file location. + +### Implementing a FileStreamSecurityValidator + +The `IFileStreamSecurityAnalyzer` needs a single method to be implemented: + +* `IsConsideredSafe`: This method should return false if the analyzer finds a reason not to trust the file + +### Example FileStreamSecurityAnalyzer + +The following class shows how one could potentially guard against Cross-site scripting(XSS) vulnerabilities in an svg file. + +```csharp +public class SvgXssSecurityAnalyzer : IFileStreamSecurityAnalyzer + { + public bool ShouldHandle(Stream fileStream) + { + // reduce memory footprint by partially reading the file + var startBuffer = new byte[256]; + var endBuffer = new byte[256]; + fileStream.Read(startBuffer); + if (endBuffer.Length > fileStream.Length) + fileStream.Seek(0, SeekOrigin.Begin); + else + fileStream.Seek(fileStream.Length - endBuffer.Length, SeekOrigin.Begin); + fileStream.Read(endBuffer); + var startString = System.Text.Encoding.UTF8.GetString(startBuffer); + var endString = System.Text.Encoding.UTF8.GetString(endBuffer); + return startString.Contains(""); + } + + public bool IsConsideredSafe(Stream fileStream) + { + var streamReader = new StreamReader(fileStream); // do not use a using as this will dispose of the underlying stream + var fileContent = streamReader.ReadToEnd(); + return !(fileContent.Contains("")); + } + } +``` + +You can [register it during startup or with a composer](https://docs.umbraco.com/umbraco-cms/reference/using-ioc#registering-dependencies). + +This is an example of registering the class with a composer: + +```csharp +public class ServerSideValidationComposer : IComposer + { + public void Compose(IUmbracoBuilder builder) + { + builder.Services.AddSingleton(); + } + } +``` + +Then you can upload a file with the following content to the backoffice and see that it is not persisted. + +```xml + + + + + + + +``` diff --git a/16/umbraco-cms/reference/security/serverside-sanitizing.md b/16/umbraco-cms/reference/security/serverside-sanitizing.md new file mode 100644 index 00000000000..9d2b51ef552 --- /dev/null +++ b/16/umbraco-cms/reference/security/serverside-sanitizing.md @@ -0,0 +1,90 @@ +--- +description: This section describes how to sanitize the Rich Text Editor serverside +--- + +# Sanitizing the Rich Text Editor + +The Rich Text Editor is sanitized on the frontend by default, however, you may want to do this serverside as well. The libraries that are out there tend to have strict dependencies. That is why we will leave it up to you how you want to sanitize the HTML. + +## Implementing your own IHtmlSanitizer + +We have added an abstraction called `IHtmlSanitizer`, which by default does nothing. You can override it with your own implementation to handle sanitization as you see fit. This interface has a single method: `string Sanitize(string html)`. The output of this method is what will be stored in the database when you save a Rich Text Editor. + +To add your own sanitizer you must first create a class the implements the interface: + +```csharp +using Umbraco.Cms.Core.Security; + +namespace MySite.HtmlSanitization; + +public class MyHtmlSanitizer : IHtmlSanitizer +{ + public string Sanitize(string html) + { + // Sanitize the html parameter here + return "

Sanitized HTML

"; + } +} +``` + +The `Sanitize` method in this implementation is where you can use a library. You could also use your own sanitizer implementation, to sanitize the Rich Text Editor input. + +Now that you've added your own custom `IHtmlSanitizer` you must register it in the container to replace the existing NoOp sanitizer. + +You can register it directly in the `Program.cs` or use a composer. + +{% hint style="info" %} +Learn more about registering dependencies and when to use which method in the [Dependency Injection](../using-ioc.md) article. +{% endhint %} + +The extension method could look like the following: + +```csharp +using Umbraco.Cms.Core.DependencyInjection; +using Umbraco.Cms.Core.Security; +using Umbraco.Extensions; + +namespace MySite.HtmlSanitization; + +public static class BuilderExtensions +{ + public static IUmbracoBuilder AddHtmlSanitizer(this IUmbracoBuilder builder) + { + builder.Services.AddUnique(); + return builder; + } +} +``` + +The extension can then be invoked in the `CreateUmbracoBuilder()` builder chain in the `Program.cs` file: + +```csharp +builder.CreateUmbracoBuilder() + .AddBackOffice() + .AddWebsite() + .AddDeliveryApi() + .AddComposers() + .AddHtmlSanitizer() // Call you extension method here. + .Build(); +``` + +Another option is to create a composer for handling the extension method: + +```csharp +using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Core.DependencyInjection; +using Umbraco.Cms.Core.Security; +using Umbraco.Extensions; + +namespace MySite.HtmlSanitization; + +public class SanitizerComposer : IComposer +{ + public void Compose(IUmbracoBuilder builder) + { + builder.Services.AddUnique(); + } +} +``` + +With your custom sanitizer in place the Rich Text Editor will always contain the "Sanitized HTML" heading. This shows that everything is working as expected, and that whatever your sanitizer returns is what will be saved. diff --git a/16/umbraco-cms/reference/security/setup-umbraco-for-a-fips-server.md b/16/umbraco-cms/reference/security/setup-umbraco-for-a-fips-server.md new file mode 100644 index 00000000000..29ccd7da003 --- /dev/null +++ b/16/umbraco-cms/reference/security/setup-umbraco-for-a-fips-server.md @@ -0,0 +1,35 @@ +# Setup Umbraco for a FIPS Compliant Server + +_This tutorial walks through configuring Umbraco and Lucene to be FIPS compliant and serve up websites on a server with FIPS enabled._ + +{% hint style="warning" %} +FIPS should only be added for compliance. It is **not** a recommended approach for added security. For more information read [Why Microsoft is not recommending "FIPS Mode" anymore.](https://techcommunity.microsoft.com/t5/microsoft-security-baselines/why-we-8217-re-not-recommending-8220-fips-mode-8221-anymore/ba-p/701037) +{% endhint %} + +## What is FIPS? + +The Federal Information Processing Standard (FIPS) Publication 140-2, ([FIPS PUB 140-2][1]), is a U.S. government computer security standard used to define approved cryptographic modules. The FIPS 140 standard also sets forth requirements for key generation and for key management. + +Microsoft Windows has a "FIPS mode" of operation where it detects the cryptographic algorithms used by software running on it and will throw exceptions if it detects the use of non-FIPS compliant algorithms. Using MD5 hashing is generally the biggest culprit of issues running on FIPS enabled servers. + +## How can I test my site with FIPS enabled? + +FIPS can be enabled through your Local Group Policy, Registry Setting, or Network Adapter setting. For more information about how to enable FIPS mode on Windows see this tutorial: [How-to Enable FIPS on Windows][2] + +## What version of Umbraco is FIPS compliant? + +Umbraco 7.6.4+ has implemented checks for when FIPS mode is enabled on the server that it is installed on. When FIPS mode is detected, the cryptographic algorithms for hashing are changed to a FIPS compliant algorithm. When FIPS mode is disabled, then Umbraco uses backward compatible algorithms (MD5) so as not to affect existing installs. As of Umbraco version 7.6.4, the FIPS compliant cryptographic algorithm used is SHA1. + +## Umbraco 9.0.0 and key dependencies are FIPS compliant + +Since Umbraco 9, the dependency to Lucene.NET is updated to version 4+. Thereby are both Umbraco and all key dependencies FIPS compliant. + +## FAQ + +__Can I install Umbraco directly on a version of Windows with FIPS mode enabled?__ + +Installing to the FIPS server may not work. It's best to deploy an existing known working version to the FIPS server. + +[1]:https://csrc.nist.gov/publications/PubsFIPS.html#140-2 +[2]:https://www.howtogeek.com/245859/why-you-shouldnt-enable-fips-compliant-encryption-on-windows/ +[3]:../../fundamentals/setup/upgrading/ diff --git a/16/umbraco-cms/reference/security/ssl-https.md b/16/umbraco-cms/reference/security/ssl-https.md new file mode 100644 index 00000000000..6fbb9e3a981 --- /dev/null +++ b/16/umbraco-cms/reference/security/ssl-https.md @@ -0,0 +1,89 @@ +--- +description: "This article covers the recommended way of working with HTTPS and Umbraco CMS." +--- + +# HTTPS + +We highly encourage the use of HTTPS on Umbraco websites especially in production environments. By using HTTPS you greatly improve the security of your website. + +There are multiple benefits of HTTPS: + +* Trust - when your site is delivered over HTTPS your users will see that your site is secured, they are able to view the certificate assigned to your site and know that your site is legitimate +* Removing an attack vector called ["Man in the middle"](https://owasp.org/www-community/attacks/Manipulator-in-the-middle\_attack) (or network Sniffing) +* Guards against [Phishing](https://en.wikipedia.org/wiki/Phishing), an attacker will have a hard time obtaining an authentic Secure Sockets Layer (SSL) certificate +* Google likes HTTPS, it may help your site's rankings + +Another benefits of HTTPS is that you are able to use the [http2](https://en.wikipedia.org/wiki/HTTP/2) protocol if your web server and browser support it. + +## Set UseHttps configuration option + +Umbraco allows you to force HTTPS for all backoffice communications by using the following configuration: + +In Umbraco 9, set the UseHttps key in `appSettings` to true. + +```json +{ + "Umbraco": { + "CMS": { + "Global": { + "UseHttps": true + } + } + } +} +``` + +This options does multiple things when it is turned on: + +* Ensures that the backoffice authentication cookie is set to [secure only](https://owasp.org/www-community/controls/SecureCookieAttribute) (so it can only be transmitted over https) +* All non-https requests to any backoffice controller are redirected to https +* All self delivered Umbraco requests are performed over https +* All Umbraco notification emails with links generated have https links +* All authorization attempts for backoffice handlers and services will be denied if the request is not over https + +## Redirect traffic in code + +The .NET5+ way to handle this, is by adding this `HttpsRedirectionMiddleware` to your pipeline in `Program.cs`. This can be done by adding `app.UseHttpsRedirection();` before the call to `app.UseUmbraco()` in the `Configure` method: + +```cs +app.UseHttpsRedirection(); + + app.UseUmbraco() + .WithMiddleware(u => + { + u.UseBackOffice(); + u.UseWebsite(); + }) + .WithEndpoints(u => + { + u.UseBackOfficeEndpoints(); + u.UseWebsiteEndpoints(); + }); +``` + +## Redirect traffic on IIS + +Once you enable HTTPS for your site you should redirect all requests to your site to HTTPS. This can be done with an IIS rewrite rule. The IIS rewrite module needs to be installed for this to work, most hosting providers will have that enabled by default. + +In your `web.config` find or add the `` section and put the following rule in there. This rule will redirect all requests for the site `http://mysite.com` URL to the secure `https://mysite.com` URL and respond with a permanent redirect status. + +```xml + + + + + + + + +``` + +{% hint style="info" %} +The rule includes an ignore for `localhost`. If you run your local environment on a different URL than `localhost` you can add additional ignore rules. Additionally, if you have a staging environment that doesn't run on HTTPS, you can add that to the ignore rules too. +{% endhint %} + +## SSL versus Transport Layer Security (TLS) + +_In HTTPS, the communication protocol is encrypted using Transport Layer Security (TLS), or, formerly, its predecessor, Secure Sockets Layer (SSL)_ - [wikipedia](https://en.wikipedia.org/wiki/HTTPS) + +While the deprecated SSL (2.0 and 3.0) are not supported anymore by modern browsers, some of the Umbraco configuration still uses SSL. But rest assured, that is **only** the name. diff --git a/16/umbraco-cms/reference/security/two-factor-authentication.md b/16/umbraco-cms/reference/security/two-factor-authentication.md new file mode 100644 index 00000000000..821cc2a523e --- /dev/null +++ b/16/umbraco-cms/reference/security/two-factor-authentication.md @@ -0,0 +1,879 @@ +--- +description: >- + Umbraco users and members support a two-factor authentication (2FA) + abstraction for implementing a 2FA provider of your choice. +--- + +# Two-factor Authentication + +This article includes guides for implementing two-factor authentication options for both backoffice users and website members: + +* [Two-Factor Authentication for Members](#two-factor-authentication-for-members) +* [Two-Factor Authentication for Users](#two-factor-authentication-for-users) + +Two-factor authentication (2FA) for Umbraco Users and Members is activated by implementing an `ITwoFactorProvider` interface and registering the implementation. The implementation can use third-party packages to support authentication apps like the Microsoft- or Google Authentication Apps. + +{% hint style="info" %} +If you are using [Umbraco Cloud](https://umbraco.com/products/umbraco-cloud/), you can enable multi-factor authentication in Umbraco ID. For more information, see the [Multi-Factor Authentication](https://docs.umbraco.com/umbraco-cloud/set-up/multi-factor-authentication-on-cloud) article. +{% endhint %} + +## Two-factor authentication for Members + +The following guide will take you through implementing an option for your website members to enable two-factor authentication. + +{% hint style="info" %} +A setup for members needs to be implemented on your website in order for you to follow this guide. This setup should include: + +* Login and logout options. +* Public access restriction configured on at least 1 content item. + +[Learn more about setting up a members section in Umbraco.](../../tutorials/members-registration-and-login.md) +{% endhint %} + +As an example, the guide will use the [GoogleAuthenticator NuGet Package](https://www.nuget.org/packages/GoogleAuthenticator/). This package works for both Google and Microsoft authenticator apps. It can be used to generate the QR code needed to activate the app for the website. + +1. Install the GoogleAuthenticator Nuget Package on your project. +2. Create a new file in your project: `UmbracoAppAuthenticator.cs`. +3. Update the file with the following code snippet. + +{% code title="UmbracoAppAuthenticator.cs" lineNumbers="true" %} + +```csharp +using Google.Authenticator; +using System.Runtime.Serialization; +using Umbraco.Cms.Core.Security; +using Umbraco.Cms.Core.Services; + +namespace My.Website; + +/// +/// Model with the required data to setup the authentication app. +/// + +[DataContract] +public class QrCodeSetupData : ISetupTwoFactorModel +{ + /// + /// The secret unique code for the user and this ITwoFactorProvider. + /// + public string? Secret { get; init; } + + /// + /// The SetupCode from the GoogleAuthenticator code. + /// + public SetupCode? SetupCode { get; init; } +} + +/// +/// App Authenticator implementation of the ITwoFactorProvider +/// +public class UmbracoAppAuthenticator : ITwoFactorProvider +{ + /// + /// The unique name of the ITwoFactorProvider. This is saved in a constant for reusability. + /// + public const string Name = "UmbracoAppAuthenticator"; + + private readonly IMemberService _memberService; + + /// + /// Initializes a new instance of the class. + /// + public UmbracoAppAuthenticator(IMemberService memberService) + { + _memberService = memberService; + } + + /// + /// The unique provider name of ITwoFactorProvider implementation. + /// + /// + /// This value will be saved in the database to connect the member with this ITwoFactorProvider. + /// + public string ProviderName => Name; + + /// + /// Returns the required data to setup this specific ITwoFactorProvider implementation. In this case it will contain the url to the QR-Code and the secret. + /// + /// The key of the user or member + /// The secret that ensures only this user can connect to the authenticator app + /// The required data to setup the authenticator app + public Task GetSetupDataAsync(Guid userOrMemberKey, string secret) + { + var member = _memberService.GetByKey(userOrMemberKey); + + var applicationName = "testingOn15"; + var twoFactorAuthenticator = new TwoFactorAuthenticator(); + SetupCode setupInfo = twoFactorAuthenticator.GenerateSetupCode(applicationName, member.Username, secret, false); + return Task.FromResult(new QrCodeSetupData() + { + SetupCode = setupInfo, + Secret = secret + }); + } + + /// + /// Validated the code and the secret of the user. + /// + public bool ValidateTwoFactorPIN(string secret, string code) + { + var twoFactorAuthenticator = new TwoFactorAuthenticator(); + return twoFactorAuthenticator.ValidateTwoFactorPIN(secret, code); + } + + /// + /// Validated the two factor setup + /// + /// Called to confirm the setup of two factor on the user. In this case we confirm in the same way as we login by validating the PIN. + public bool ValidateTwoFactorSetup(string secret, string token) => ValidateTwoFactorPIN(secret, token); +} +``` + +{% endcode %} + +4. Update `namespace` on line 7 to match your project. +5. Customize the `applicationName` variable on line 64. +6. Create a Composer and register the `UmbracoAppAuthenticator` implementation as shown below. + +{% code title="UmbracoAppAuthenticatorComposer.cs" lineNumbers="true" %} + +```csharp +using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Core.DependencyInjection; +using Umbraco.Cms.Core.Security; + +namespace My.Website; + +public class UmbracoAppAuthenticatorComposer : IComposer +{ + public void Compose(IUmbracoBuilder builder) + { + var identityBuilder = new MemberIdentityBuilder(builder.Services); + identityBuilder.AddTwoFactorProvider(UmbracoAppAuthenticator.Name); + } +} +``` + +{% endcode %} + +At this point, the 2FA is active, but no members have set up 2FA yet. The setup of 2FA depends on the type. In the case of App Authenticator, the **view** showing the option to edit member profiles needs to be modified. + +{% hint style="info" %} +If you already have a members-only page with the edit profile options, you can skip directly to step 8. +{% endhint %} + +7. Add or choose a members-only page that should have the two-factor authentication setup. + * The page needs to be behind the public access. + * The page should **not** be using strongly types models. +8. Open the view file for the selected page. +9. Add the following code: + +{% code title="ExampleMemberPage.cshtml" lineNumbers="true" %} + +```csharp +@using Umbraco.Cms.Core.Services; +@using Umbraco.Cms.Web.Website.Controllers; +@using Umbraco.Cms.Web.Website.Models; +@using My.Website; +@inject MemberModelBuilderFactory memberModelBuilderFactory +@inject ITwoFactorLoginService twoFactorLoginService +@{ + // Build a profile model to edit + var profileModel = await memberModelBuilderFactory + .CreateProfileModel() + .BuildForCurrentMemberAsync(); + + // Show all two factor providers + var providerNames = twoFactorLoginService.GetAllProviderNames(); + if (providerNames.Any()) + { +
+ foreach (var providerName in providerNames) + { + var setupData = await twoFactorLoginService.GetSetupInfoAsync(profileModel.Key, providerName); + + // If the `setupData` is `null` for the specified `providerName` it means the provider is already set up. + // In this case, a button to disable the authentication is shown. + if (setupData is null) + { + @using (Html.BeginUmbracoForm(nameof(UmbTwoFactorLoginController.Disable))) + { + + + } + } + // If `setupData` is not `null` the type is checked and the UI for how to set up the App Authenticator is shown. + else if(setupData is QrCodeSetupData qrCodeSetupData) + { + @using (Html.BeginUmbracoForm(nameof(UmbTwoFactorLoginController.ValidateAndSaveSetup))) + { +

Setup @providerName

+ +

Scan the code above with your authenticator app
and enter the resulting code here to validate:

+ + + + + } + } + } + } +} +``` + +{% endcode %} + +10. Update the `@using` in line 4 to match the namespace of your project. +11. [Optional] Customize the text fields and buttons to match your websites tone of voice (lines 33-39). + +![The QR Code is shown along with a field to enter a value to set up the two factor authentication.](images/2fa-members-configuration.png) + +### Test the set up for Members + +1. Login to the website using a test member. +2. Navigate to the page where the QR code was added. +3. Scan the QR code and add the verification code. +4. Logout of the website. +5. Login and verify that it asks for the two factor authentication. + +You can also check that the **Two-factor Authentication** option is checked on the member in the Umbraco backoffice. + +![Check the Member profile in the Umbraco backoffice to verify whether two-factor authentication is enabeld.](images/2fa-member-backoffice.png) + +### Notification when 2FA is requested for a member + +When a 2FA login is requested for a member, the `MemberTwoFactorRequestedNotification` is published. This notification can also be used to send the member a one-time password via e-mail or phone. Even though these 2FA types are [not considered secure](https://docs.microsoft.com/en-us/aspnet/core/security/authentication/mfa?view=aspnetcore-6.0#mfa-sms) as App Authentication, it is still a massive improvement compared to no 2FA. + +## Two-factor authentication for Users + +The following guide will take you through implementing an option for backoffice users to enable two-factor authentication. + +This guide will not cover setting up the UI for user login and edits as this is handled elsewhere in the CMS. + +### Example implementation for Authenticator Apps for Users + +As an example, the guide will use the [GoogleAuthenticator NuGet Package](https://www.nuget.org/packages/GoogleAuthenticator/). This package works for both Google and Microsoft authenticator apps. It can be used to generate the QR code needed to activate the app for the website. + +1. Install the GoogleAuthenticator Nuget Package on your project. +2. Create a new file in your project: `UmbracoUserAppAuthenticator.cs`. +3. Update the file with the following code snippet. + +{% code title="UmbracoUserAppAuthenticator.cs" lineNumbers="true" %} + +```csharp +using System.Runtime.Serialization; +using Google.Authenticator; +using Umbraco.Cms.Core.Models.Membership; +using Umbraco.Cms.Core.Security; +using Umbraco.Cms.Core.Services; + +namespace My.Website; + +[DataContract] +public class TwoFactorAuthInfo : ISetupTwoFactorModel +{ + [DataMember(Name = "qrCodeSetupImageUrl")] + public string? QrCodeSetupImageUrl { get; set; } + + [DataMember(Name = "secret")] + public string? Secret { get; set; } +} + +/// +/// App Authenticator implementation of the ITwoFactorProvider +/// +public class UmbracoUserAppAuthenticator : ITwoFactorProvider +{ + /// + /// The unique name of the ITwoFactorProvider. This is saved in a constant for reusability. + /// + public const string Name = "UmbracoUserAppAuthenticator"; + + private readonly IUserService _userService; + + /// + /// Initializes a new instance of the class. + /// + public UmbracoUserAppAuthenticator(IUserService userService) + { + _userService = userService; + } + + /// + /// Gets the unique provider name of ITwoFactorProvider implementation. + /// + /// + /// This value will be saved in the database to connect the member with this ITwoFactorProvider. + /// + public string ProviderName => Name; + + /// + /// Returns the required data to setup this specific ITwoFactorProvider implementation. In this case it will contain the url to the QR-Code and the secret. + /// + /// The key of the user or member + /// The secret that ensures only this user can connect to the authenticator app + /// The required data to setup the authenticator app + public Task GetSetupDataAsync(Guid userOrMemberKey, string secret) + { + IUser? user = _userService.GetByKey(userOrMemberKey); + + ArgumentNullException.ThrowIfNull(user); + + var applicationName = "My application name"; + var twoFactorAuthenticator = new TwoFactorAuthenticator(); + SetupCode setupInfo = twoFactorAuthenticator.GenerateSetupCode(applicationName, user.Username, secret, false); + return Task.FromResult(new TwoFactorAuthInfo() + { + QrCodeSetupImageUrl = setupInfo.QrCodeSetupImageUrl, + Secret = secret + }); + } + + /// + /// Validated the code and the secret of the user. + /// + public bool ValidateTwoFactorPIN(string secret, string code) + { + var twoFactorAuthenticator = new TwoFactorAuthenticator(); + return twoFactorAuthenticator.ValidateTwoFactorPIN(secret, code); + } + + /// + /// Validated the two factor setup + /// + /// Called to confirm the setup of two factor on the user. In this case we confirm in the same way as we login by validating the PIN. + public bool ValidateTwoFactorSetup(string secret, string token) => ValidateTwoFactorPIN(secret, token); +} +``` + +{% endcode %} + +4. Update `namespace` on line 7 to match your project. +5. Customize the `applicationName` variable on line 59. +6. Create a new file in your project: `UmbracoUserAppAuthenticatorComposer.cs`. +7. Implement a new composer and register the `UmbracoUserAppAuthenticator` implementation as shown below. + +{% code title="UmbracoUserAppAuthenticatorComposer.cs" lineNumbers="true" %} + +```csharp +using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Core.Security; + +namespace My.Website; + +public class UmbracoUserAppAuthenticatorComposer : IComposer +{ + public void Compose(IUmbracoBuilder builder) + { + var identityBuilder = new BackOfficeIdentityBuilder(builder.Services); + + identityBuilder.AddTwoFactorProvider(UmbracoUserAppAuthenticator.Name); + } +} +``` + +{% endcode %} + +8. Update the `namespace` on line 4 to match your project. + +With the 2FA in place, the provider needs to be registered in the backoffice client so the user can use it. + +9. Add a new file to your project directory: `~/App_Plugins/TwoFactorProviders/umbraco-package.json`. +10. Add the following code to the new file: + +{% code title="~/App_Plugins/TwoFactorProviders/umbraco-package.json" lineNumbers="true" %} + +```json +{ + "$schema": "../../umbraco-package-schema.json", + "name": "2fa providers", + "version": "1.0.0", + "extensions": [ + { + "type": "mfaLoginProvider", + "alias": "UmbracoUserAppAuthenticator", + "name": "UmbracoUserAppAuthenticator", + "forProviderName": "UmbracoUserAppAuthenticator", + "meta": { + "label": "Google Authenticator" + } + } + ] +} +``` + +{% endcode %} + +At this point, the 2FA is active, but no users have set up 2FA yet. + +### Test the set up for Users + +Each user can now enable the configured 2FA providers on their user. + +1. Access the Umbraco backoffice. +2. Click the user avatar in the top-right corner. + +![User panel](images/user-panel.jpg) + +3. Select `Configure Two-Factor` button to get a list of all enabled two-factor providers. + +![Configure 2fa](images/configure-2fa.jpg) + +4. Select `Enable` to show the configured view. + +![Enable 2fa](images/enable-2fa.jpg) + +5. Follow the instructions to configure 2FA. + +When the authenticator is enabled correctly, a disable button is shown instead. + +![Disable 2fa](images/disable-2fa.jpg) + +To disable the two-factor authentication on your user, it is required to enter the verification code. + +![Verify disable](images/verify-disable.jpg) + +If the code is correct, the provider is disabled. + +### Notification when 2FA is requested for a user + +When a 2FA login is requested for a user, the `UserTwoFactorRequestedNotification` is published. This notification can also be used to send the user a one-time password via e-mail or phone. Even though these 2FA types are [not considered secure](https://docs.microsoft.com/en-us/aspnet/core/security/authentication/mfa?view=aspnetcore-6.0#mfa-sms) as App Authentication, it is still a massive improvement compared to no 2FA. + +### Login with 2FA enabled + +When a user with 2FA enabled logs in, they will be presented with a screen to enter the verification code: + +While the 2FA is enabled, the user will be presented with this screen after entering the username and password. + +![Default 2FA login](images/2fa-login-default-view.jpg) + +If the code is correct, the user will be logged in. If the code is incorrect, the user will be presented with an error message. + +This screen is set up to work well with 2FA providers that require a one-time code to be entered. The code field follows best practices for accessibility in terms of labeling and autocompletion. + +{% hint style="info" %} +A user can have more than one 2FA provider activated simultaneously. In this case, the user will be presented with a dropdown to choose which provider to use before entering a code. +{% endhint %} + +## Customizing the 2FA experience + +The 2FA experience can be customized in Umbraco. This can be done by creating a custom view for the activation screen and the login screen. This is useful if you have a 2FA provider that requires something else than a one-time code to be entered. + +The following examples show how to customize the 2FA activation screen and the 2FA login screen. + +The examples are using the [Lit](https://lit.dev/) library to create custom elements. This is the recommended way of creating custom elements in Umbraco. Lit is a light-weight library that augments the [Custom Elements API](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements) to provide a declarative, performant, and interoperable way to create web components. + +The examples are using the `@umbraco-cms/backoffice` package to get access to the Umbraco backoffice components and services. This package is included in Umbraco and can be used to create custom elements that look and feel like the rest of the Umbraco backoffice. + +They are written in vanilla JavaScript and C#, but the same principles can be applied to other languages. For more information about creating custom elements in Umbraco with a bundler and TypeScript, see the [Development Flow](../../customizing/development-flow/) article. + +### Customizing the 2FA activation screen + +The 2FA activation screen can be customized. This should be done if you have a 2FA provider that does not require a one-time code to be entered. + +To customize the 2FA activation screen, you need to create a JavaScript module. The module should export a default custom element to be used in the activation screen. This module should be placed in the `App_Plugins/TwoFactorProviders` folder. + +{% code title="~/App_Plugins/TwoFactorProviders/2fa-activation.js" lineNumbers="true" %} +```javascript +import { UserService } from '@umbraco-cms/backoffice/external/backend-api'; +import { css, html } from '@umbraco-cms/backoffice/external/lit'; +import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; +import { isApiError, tryExecuteAndNotify } from '@umbraco-cms/backoffice/resources'; +import { UMB_NOTIFICATION_CONTEXT } from '@umbraco-cms/backoffice/notification'; +import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; + +export default class My2faActivationElement extends UmbLitElement { + static get properties() { + return { + providerName: { type: String }, + displayName: { type: String }, + callback: { type: Function }, + close: { type: Function }, + _loading: { type: Boolean, state: true, attribute: false }, + _qrCodeSetupImageUrl: { type: String, state: true, attribute: false }, + _buttonState: { type: String, state: true, attribute: false }, + }; + } + + constructor() { + super(); + + this.consumeContext(UMB_NOTIFICATION_CONTEXT, (context) => { + this.notificationContext = context; + }); + } + + async firstUpdated() { + await this.#load(); + this._loading = false; + } + + async #load() { + if (!this.providerName) { + this.peek('Provider name is required', 'danger'); + throw new Error('Provider name is required'); + } + const { data: _data } = await tryExecuteAndNotify( + this, + UserService.getUserCurrent2FaByProviderName({ providerName: this.providerName }), + ); + const data = _data; + if (!data) { + this.peek('No data returned', 'danger'); + throw new Error('No data returned'); + } + + // Verify that there is a secret + if (!data.secret) { + this.peek('The provider did not return a secret.', 'danger'); + throw new Error('No secret returned'); + } + + this._secret = data.secret; + this._qrCodeSetupImageUrl = data.qrCodeSetupImageUrl; + } + + render() { + if (this._loading) { + return html``; + } + + return html` + +
+ +
+ +
+

+ + Scan this QR code with your authenticator app to enable two-factor authentication + +

+ ${this.localize.term('user_2faQrCodeAlt')} +
+ + + + + + +
+
+
+ + ${this.localize.term('general_close')} + + + ${this.localize.term('general_submit')} + +
+
+
+
+ `; + } + + /** + * Show a peek notification with a message. + * @param message {String} The message to show. + * @param color {"positive" | "danger" | undefined} The color of the notification. + */ + peek(message, color) { + this.notificationContext.peek(color ?? 'positive', { + data: { + headline: this.localize.term('member_2fa'), + message, + }, + }); + } + + /** + * Submit the form with the code and secret back to the opener. + * @param e {SubmitEvent} The submit event + */ + async submit(e) { + e.preventDefault(); + const codeField = this.shadowRoot.getElementById('code'); + codeField?.setCustomValidity(''); + + const form = e.target; + + if (!form.checkValidity()) return; + + const formData = new FormData(form); + const code = formData.get('code'); + + if (!code) return; + + this._buttonState = 'waiting'; + const { error } = await this.callback(this.providerName, code, this._secret); + + if (!error) { + this.peek(this.localize.term('user_2faProviderIsEnabledMsg', this.displayName ?? this.providerName)); + this._buttonState = 'success'; + this.close(); + } else { + this._buttonState = 'failed'; + if (isApiError(error)) { + if (error.body?.operationStatus === 'InvalidCode') { + codeField?.setCustomValidity(this.localize.term('user_2faInvalidCode')); + codeField?.focus(); + } else { + this.peek( + this.localize.term('user_2faProviderIsNotEnabledMsg', this.displayName ?? this.providerName), + 'warning', + ); + } + } else { + this.peek(error.message, 'warning'); + } + } + } + + static get styles() { + return [ + UmbTextStyles, + css` + #authForm { + height: 100%; + } + + #qrCode { + width: 100%; + aspect-ratio: 1; + } + + #code { + width: 100%; + max-width: 300px; + } + + .text-center { + text-align: center; + } + `, + ]; + } +} + +customElements.define('my-2fa-activation', My2faActivationElement); +``` +{% endcode %} + +This module will show a QR code and an input field for the user to enter the code from the authenticator app. When the user submits the form, the code will be sent to the server to validate. If the code is correct, the provider will be enabled. + +To replace the default activation screen with the custom view, you need to register the element in the `umbraco-package.json` file that you created before. The final form of the file should look like this: + +{% code title="~/App_Plugins/TwoFactorProviders/umbraco-package.json" lineNumbers="true" %} +```json +{ + "$schema": "../../umbraco-package-schema.json", + "name": "2fa providers", + "version": "1.0.0", + "extensions": [ + { + "type": "mfaActivationProvider", + "alias": "UmbracoUserAppAuthenticator", + "name": "UmbracoUserAppAuthenticator", + "forProviderName": "UmbracoUserAppAuthenticator", + "element": "/App_Plugins/TwoFactorProviders/2fa-activation.js", // This line is the only change + "meta": { + "label": "Google Authenticator" + } + } + ] +} +``` +{% endcode %} + +### Customizing the login screen + +The 2FA login screen can also be customized. This should be done if you have a 2FA provider that requires something else than a one-time code to be entered. + +You should only customize the 2FA login screen in certain cases, for example: + +* If you have a provider that requires a non-numeric field or additional info. +* If you have a provider that requires the user to scan a QR code, you should additionally show the QR code. +* If you need to authenticate the user in a different way than the default option. + +You need to create a JavaScript module that exports a default custom element to be used in the login screen. This module should be placed in the `App_Plugins` folder. The module should be registered using a composer. + +You can use the following code as a starting point. This will give you a view looking like this, where the user can enter a code and click a button to verify the code. This is similar to the built-in view in Umbraco. In a real world scenario, you would probably want to authenticate the user in a different way. + +![Custom 2FA login](images/2fa-login-custom-view.png) + +The following code is an example of a custom 2FA login screen using [Lit](https://lit.dev/). This is the recommended way of creating a custom 2FA login screen. Lit is a light-weight library that augments the [Custom Elements API](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements) to provide a declarative, performant, and interoperable way to create web components. + +The element registers two properties: providers and returnPath. These properties are used to render the view. The providers property is an array of strings, where each string is the name of a 2FA provider. The returnPath is the path to redirect to after a successful login. Both supplied by the login screen automatically. + +{% code title="~/App_Plugins/TwoFactorProviders/Custom2faLogin.js" lineNumbers="true" %} +```javascript +import { css, html } from '@umbraco-cms/backoffice/external/lit'; +import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; + +export default class My2faViewElement extends UmbLitElement { + static get properties() { + return { + providers: { type: Array }, + returnPath: { type: String }, + buttonState: { type: String, state: true, attribute: false } + }; + } + + get codeField() { + return this.shadowRoot.getElementById('code'); + } + + /** + * @param evt {SubmitEvent} + * @param provider {String} + * @returns {Promise} + */ + async onSubmit(evt, provider) { + evt.preventDefault(); + + this.codeField.error = false; + this.codeField.setCustomValidity(''); + this.errorMessage = ''; + + /** + * @type {HTMLFormElement} + */ + const form = evt.target; + + const isValid = form.checkValidity(); + if (!isValid) { + return; + } + + this.buttonState = 'loading'; + const formData = new FormData(form); + const code = formData.get('code'); + + const authContext = await this.getContext('UmbAuthContext'); + if (!authContext) { + this.errorMessage = 'Error: No auth context'; + this.buttonState = 'failed'; + return; + } + + const { error } = await authContext.validateMfaCode(code, provider); + + if (error) { + this.codeField.error = true; + this.codeField.errorMessage = error; + this.codeField.focus(); + this.buttonState = 'failed'; + return; + } + + this.buttonState = 'success'; + + if (this.returnPath) { + window.location.href = this.returnPath; + } + } + + renderProvider(provider) { + return html` + +
this.onSubmit(e, provider)} novalidate> +

${provider}

+

You are about to sign-in with ${provider}.

+ + Type the authentication code from your device + +
+ +
+
+
+ +
+ + + Authenticate + +
+ +
+ ${this.errorMessage} +
+
+
+ `; + } + + render() { + return html` + ${this.providers.length ? this.providers.map(provider => this.renderProvider(provider)) : html`

Error: No providers available

`} + `; + } + + static styles = css` + :host { + display: block; + width: 100%; + } + #button { + width: 100%; + } + #error { + color: red; + } + `; +} + +customElements.define('my-2fa-view', My2faViewElement); +``` +{% endcode %} + +We need to register the custom view using a composer. This can be done on the `IUmbracoBuilder` in your startup or a composer. In this case, we will add a composer to your project. This composer will overwrite the `IBackOfficeTwoFactorOptions` to use the custom view. + +{% code title="TwoFactorConfiguration.cs" lineNumbers="true" %} +```csharp +using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Web.BackOffice.Security; + +namespace My.Website; + +/// +public class TwoFactorConfiguration : IBackOfficeTwoFactorOptions +{ + /// + public string GetTwoFactorView(string username) => "/App_Plugins/TwoFactorProviders/Custom2faLogin.js"; +} + +public class TwoFactorConfigurationComposer : IComposer +{ + public void Compose(IUmbracoBuilder builder) + { + builder.Services.AddSingleton(); + } +} + +``` +{% endcode %} diff --git a/16/umbraco-cms/reference/templating/README.md b/16/umbraco-cms/reference/templating/README.md new file mode 100644 index 00000000000..707d730f68e --- /dev/null +++ b/16/umbraco-cms/reference/templating/README.md @@ -0,0 +1,27 @@ +--- +description: >- + Information on configuring Templates (Views) and Partials (Partial Views) +--- + +# Templating + +Templating in Umbraco consists of 3 larger concepts, namely Templates (Views) and Partials (Partial Views). + +* Templates are used for the HTML layout of your pages. +* Partials can be included in your templates for shared functionality across different page templates. + +## Templating technology + +Umbraco uses ASP.Net MVC Views for implementing templates. + +{% hint style="info" %} +The WebForms (masterpages) and Dynamic Razor approaches to templating are still available in Umbraco version 7 but have been removed in Umbraco version 8. +{% endhint %} + +### [Working with MVC (views, razor, etc...)](mvc/) + +Describes how to work with MVC views, the razor syntax and APIs available. It also describes how to create forms, has some step-by-step guides and other advanced techniques. + +## [Models Builder](modelsbuilder/) + +A tool that can generate a complete set of strongly-typed published content models for Umbraco. Models are available in controllers, views, anywhere. Runs either from the Umbraco UI, from the command line, or from Visual Studio. diff --git a/16/umbraco-cms/reference/templating/images/change-parent-template.png b/16/umbraco-cms/reference/templating/images/change-parent-template.png new file mode 100644 index 00000000000..67915e175c3 Binary files /dev/null and b/16/umbraco-cms/reference/templating/images/change-parent-template.png differ diff --git a/16/umbraco-cms/reference/templating/images/create-nested-template.png b/16/umbraco-cms/reference/templating/images/create-nested-template.png new file mode 100644 index 00000000000..f309dba9bef Binary files /dev/null and b/16/umbraco-cms/reference/templating/images/create-nested-template.png differ diff --git a/16/umbraco-cms/reference/templating/macros.md b/16/umbraco-cms/reference/templating/macros.md new file mode 100644 index 00000000000..7ee641e3071 --- /dev/null +++ b/16/umbraco-cms/reference/templating/macros.md @@ -0,0 +1,11 @@ +# Macros and Partial View Macros + +{% hint style="warning" %} +**Are you looking for documentation about Macros and/or Partial View Macros? + +Macros and Partial View Macros have been removed with the release of Umbraco 14. + +We recommend using using [Partial Views](../../fundamentals/design/partial-views.md) or [Blocks in the Rich Text Editor](../../fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor-tinymce/blocks.md) as a replacement. + +Learn more about the decision for remove Macros in the official announcement: [Breaking change: Macros will be removed in Umbraco 14](https://github.com/umbraco/Announcements/issues/14). +{% endhint %} diff --git a/16/umbraco-cms/reference/templating/modelsbuilder/README.md b/16/umbraco-cms/reference/templating/modelsbuilder/README.md new file mode 100644 index 00000000000..67b82ffbad8 --- /dev/null +++ b/16/umbraco-cms/reference/templating/modelsbuilder/README.md @@ -0,0 +1,15 @@ +--- +description: "Modelsbuilder reference" +--- + + +# Umbraco Models Builder + +The Models builder is a tool that can generate a complete set of strongly-typed published content models for Umbraco. Models are available in both controllers and views. + +* [Introduction](introduction.md) +* [Configuration](configuration.md) +* [Builder Modes](builder-modes.md) +* [Understand and Extend Models](understand-and-extend.md) +* [Using Interfaces](using-interfaces.md) +* [Tips and Tricks](coolthingswithmodels.md) diff --git a/16/umbraco-cms/reference/templating/modelsbuilder/builder-modes.md b/16/umbraco-cms/reference/templating/modelsbuilder/builder-modes.md new file mode 100644 index 00000000000..3eff1f95d99 --- /dev/null +++ b/16/umbraco-cms/reference/templating/modelsbuilder/builder-modes.md @@ -0,0 +1,47 @@ +--- +description: "Modelsbuilder modes" +--- + +# Builder Modes + +Models Builder can be used in different modes: + +* InMemory models +* SourceCode models + +The mode is indicated by the `Umbraco:CMS:ModelsBuilder:ModelsMode` key in the configuration (`appsettings.json` files). + +## In memory + +Corresponds to the `InMemoryAuto` setting value. + +With **InMemory** models, models are generated and compiled on the fly, in memory, at runtime. They are available in views exclusively. + +This is for a setup that exclusively uses the Umbraco backoffice, and do not use custom code such as controllers. Whenever a content type is modified, models are updated without restarting Umbraco (in the same way .cshtml views are recompiled). + +Generation *can* fail for various reasons, in which case Umbraco will run without models (and front-end views fail to render). Umbraco's log file should contain all details about what prevented the generation, but it is probably faster to check the Models Builder dashboard, which should report the last error that was encountered, if any. + +Models Builder maintains some files in `~/umbraco/Data/TEMP/InMemoryAuto`: + +* `models.generated.cs` contains the generated models code +* `all.generated.cs` contains the compiled code (models merged with non-generated files) +* `models.hash` contains a hash code of the content types +* `all.dll.path` contains the path to the compiled DLL file containing all the models +* `Compiled/generated.cs{GUID}.dll` the dll containing all the generated models +* `models.err` contains the last generation error information, if any + +The `models.hash` file is used when Umbraco restarts, to figure out whether models have changed and need to be re-generated. Otherwise, the local `models.generated.cs` file is reused. + +## SourceCode Models + +Corresponds to the `SourceCodeManual` and `SourceCodeAuto` setting values. + +With **SourceCode** models, models are generated in the `~/umbraco/models` directory, and that is all. It is then up to you to decide how to compile the models (e.g. by including them in a Visual Studio solution). + +Generation *can* fail for various reasons, in which case no models are generated. Umbraco's log file should contain all details about what prevented the generation, but it is probably faster to check the Models Builder dashboard, which should report the last error that was encountered, if any. + +The modelsbuilder works much in the same way whether using `SourceCodeManual` or `SourceCodeAuto`. The only real difference between the two are that with `SourceCodeManual` you must manually trigger the generation of the models from the models builder dashboard, whereas with `SourceCodeAuto` the models are automatically generated whenever content types change. + +## API models and Dll Models + +These modes are not available in the embedded version of Models Builder. See the full version of [ModelsBuilder](https://github.com/zpqrtbnk/Zbu.ModelsBuilder). diff --git a/16/umbraco-cms/reference/templating/modelsbuilder/configuration.md b/16/umbraco-cms/reference/templating/modelsbuilder/configuration.md new file mode 100644 index 00000000000..32f6a124c5c --- /dev/null +++ b/16/umbraco-cms/reference/templating/modelsbuilder/configuration.md @@ -0,0 +1,49 @@ +--- +description: Explanation of how to configure models builder +--- + +# Configuration + +The following configuration option can be set in the application settings (in the `appsettings.json` file): + +* `Umbraco.CMS.ModelsBuilder.ModelsMode` determines how Models Builder generates models. Valid values are: + * `Nothing`: Do not generate models. + * `InMemoryAuto`(default): Generate models in a dynamic in-memory assembly. + * `SourceCodeManual`: Generate models in `~/umbraco/models` (but do not compile them) whenever the user clicks the "Generate models" button on the Models Builder dashboard in the Settings section. + * `SourceCodeAuto`: Generate models in `~/umbraco/models` (but do not compile them) anytime a content type changes. +* `Umbraco.CMS.ModelsBuilder.ModelsNamespace` (string, default is `Umbraco.Cms.Web.Common.PublishedModels`) specifies the generated models' namespace. +* `Umbraco.CMS.ModelsBuilder.FlagOutOfDateModels` (bool, default is `true`) indicates whether out-of-date models (for example after a content type or Data Type has been modified) should be flagged. +* `Umbraco.CMS.ModelsBuilder.ModelsDirectory` (string, default is `~/umbraco/models`) indicates where to generate models and manage all files. Has to be a virtual directory (starting with `~/`) below the website root (see also: `AcceptUnsafeModelsDirectory` below). +* `Umbraco.CMS.ModelsBuilder.AcceptUnsafeModelsDirectory` (bool, default is `false`) indicates that the directory indicated in `ModelsDirectory` is allowed to be outside the website root (e.g. `~/../../some/place`). Due to this being a potential security risk, it is not allowed by default. +* `Umbraco.CMS.ModelsBuilder.DebugLevel` (int, default is zero) indicates the debug level. Set to greater than zero to enable detailed logging. For internal / development use. + +## Example Configuration + +The example below shows an example configuration using the SourceCodeManual mode. + +```json +{ + "$schema": "https://json.schemastore.org/appsettings.json", + "Umbraco": { + "CMS": { + "ModelsBuilder": { + "ModelsMode": "SourceCodeManual" + } + } + } +} +``` + +{% hint style="info" %} +It is recommended to generate models in your development environment only and change the ModelsMode to `Nothing` for your staging and production environments. +{% endhint %} + +## Models Builder Dashboard + +Models Builder ships with a dashboard in the _Settings_ section of Umbraco's backoffice. The dashboard does three things: + +* Details on how Models Builder is configured +* Provides a way to generate models (in SourceCodeManual mode only) +* Reports the last error (if any) that would have prevented models from being properly generated + +![Models Builder Dashboard](images/ModelsBuilderDashboard-v14.png) diff --git a/16/umbraco-cms/reference/templating/modelsbuilder/coolthingswithmodels.md b/16/umbraco-cms/reference/templating/modelsbuilder/coolthingswithmodels.md new file mode 100644 index 00000000000..f9571a6761e --- /dev/null +++ b/16/umbraco-cms/reference/templating/modelsbuilder/coolthingswithmodels.md @@ -0,0 +1,33 @@ +--- +description: "Cool things you can do with models" +--- +# Cool things you can do with strongly-typed models + +It's possible with Razor to define functions for rendering HTML. We can leverage our strongly typed models when doing this, and even provide overloads for different types of models. That will automatically be called for different models using `dynamic` + +```csharp +@functions +{ + // Declare how to render a news item + void RenderContent(NewsItem item) + { +
News! @item.Title
+ } + + // Declare how to render a product + void RenderContent(Product item) + { +
Product! @product.Name cheap at @product.Price
+ } +} + +@{ + RenderContent((dynamic) Model); +} +``` + +It's not recommended to create a template and doing all the rendering via razor function, but it can be nifty for rendering search results. + +A thing that's important to note here is that `RenderContent` is called from a codeblock, and not as `@RenderContent((dynamic) Model);` the reason for this is that if you try to use the latter, razor will expect for the function to return something for it to render. + +By casting the strongly typed to a dynamic when calling the **RenderContent** method, you tell C# to do late runtime binding. You also tell it to pick the proper **RenderContent** implementation depending on the actual Common Language Runtime (CLR) type of the **content** object. Using dynamic here is OK and will not pollute the rest of the code. diff --git a/16/umbraco-cms/reference/templating/modelsbuilder/images/ModelsBuilderDashboard-v14.png b/16/umbraco-cms/reference/templating/modelsbuilder/images/ModelsBuilderDashboard-v14.png new file mode 100644 index 00000000000..07969cbfdba Binary files /dev/null and b/16/umbraco-cms/reference/templating/modelsbuilder/images/ModelsBuilderDashboard-v14.png differ diff --git a/16/umbraco-cms/reference/templating/modelsbuilder/images/ModelsBuilderDashboard-v9.png b/16/umbraco-cms/reference/templating/modelsbuilder/images/ModelsBuilderDashboard-v9.png new file mode 100644 index 00000000000..b3c9375cfba Binary files /dev/null and b/16/umbraco-cms/reference/templating/modelsbuilder/images/ModelsBuilderDashboard-v9.png differ diff --git a/16/umbraco-cms/reference/templating/modelsbuilder/images/ModelsBuilderDashboard.png b/16/umbraco-cms/reference/templating/modelsbuilder/images/ModelsBuilderDashboard.png new file mode 100644 index 00000000000..1cf34344627 Binary files /dev/null and b/16/umbraco-cms/reference/templating/modelsbuilder/images/ModelsBuilderDashboard.png differ diff --git a/16/umbraco-cms/reference/templating/modelsbuilder/introduction.md b/16/umbraco-cms/reference/templating/modelsbuilder/introduction.md new file mode 100644 index 00000000000..87c7d4a7ab7 --- /dev/null +++ b/16/umbraco-cms/reference/templating/modelsbuilder/introduction.md @@ -0,0 +1,123 @@ +--- +description: Modelsbuilder introduction +--- + +# Introduction + +Models Builder is a tool that can generate a complete set of strongly-typed published content models for Umbraco. By default, a slimmed down version of Models Builder is embedded with the main Umbraco distribution. + +Models can be used anywhere that content is retrieved from the content cache, i.e. in MVC views, controllers, etc. In other words, when using the Models Builder, the content cache does not return `IPublishedContent` objects anymore, but strongly typed models, implementing `IPublishedContent`. + +For each content, media and member type in the Umbraco setup, the generator creates a `*.generated.cs` file, corresponding to the type. For instance, a document type with a textstring property named Title, and a rich text editor named BodyText will look like this: + +{% include "../../../.gitbook/includes/obsolete-warning-snapshot-publishedcache.md" %} + +```csharp +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Umbraco.ModelsBuilder.Embedded v9.0.0-beta003+b07f6519e7a1c890b534502982612ce6b3fea293 +// +// Changes to this file will be lost if the code is regenerated. +// +//------------------------------------------------------------------------------ + +using System; +using System.Linq.Expressions; +using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.PublishedCache; +using Umbraco.Cms.Infrastructure.ModelsBuilder; +using Umbraco.Cms.Core; +using Umbraco.Extensions; + +namespace Umbraco.Cms.Web.Common.PublishedModels +{ +/// NewsItem +[PublishedModel("newsItem")] +public partial class NewsItem : PublishedContentModel +{ + // helpers +#pragma warning disable 0109 // new is redundant + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Umbraco.ModelsBuilder.Embedded", "9.0.0-beta003+b07f6519e7a1c890b534502982612ce6b3fea293")] + public new const string ModelTypeAlias = "newsItem"; + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Umbraco.ModelsBuilder.Embedded", "9.0.0-beta003+b07f6519e7a1c890b534502982612ce6b3fea293")] + public new const PublishedItemType ModelItemType = PublishedItemType.Content; + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Umbraco.ModelsBuilder.Embedded", "9.0.0-beta003+b07f6519e7a1c890b534502982612ce6b3fea293")] + public new static IPublishedContentType GetModelContentType(IPublishedSnapshotAccessor publishedSnapshotAccessor) + => PublishedModelUtility.GetModelContentType(publishedSnapshotAccessor, ModelItemType, ModelTypeAlias); + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Umbraco.ModelsBuilder.Embedded", "9.0.0-beta003+b07f6519e7a1c890b534502982612ce6b3fea293")] + public static IPublishedPropertyType GetModelPropertyType(IPublishedSnapshotAccessor publishedSnapshotAccessor, Expression> selector) + => PublishedModelUtility.GetModelPropertyType(GetModelContentType(publishedSnapshotAccessor), selector); +#pragma warning restore 0109 + + private IPublishedValueFallback _publishedValueFallback; + + // ctor + public NewsItem(IPublishedContent content, IPublishedValueFallback publishedValueFallback) + : base(content, publishedValueFallback) + { + _publishedValueFallback = publishedValueFallback; + } + + // properties + + /// + /// BodyText + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Umbraco.ModelsBuilder.Embedded", "9.0.0-beta003+b07f6519e7a1c890b534502982612ce6b3fea293")] + [ImplementPropertyType("bodyText")] + public virtual global::Umbraco.Cms.Core.Strings.IHtmlEncodedString BodyText => this.Value(_publishedValueFallback, "bodyText"); + + /// + /// Title + /// + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Umbraco.ModelsBuilder.Embedded", "9.0.0-beta003+b07f6519e7a1c890b534502982612ce6b3fea293")] + [ImplementPropertyType("title")] + public virtual string Title => this.Value(_publishedValueFallback, "title"); + } +} +``` + +Now since this is an automatically generated file, it's a bit messy, the important part is that it has all the properties defined and strongly typed: + +``` +public virtual global::Umbraco.Cms.Core.Strings.IHtmlEncodedString BodyText => this.Value(_publishedValueFallback, "bodyText"); +``` + +And: + +``` +public virtual string Title => this.Value(_publishedValueFallback, "title"); +``` + +Umbraco's content cache returns these objects _natively_: No need to map, convert or anything; the following code runs: + +```csharp +@using ContentModels = Umbraco.Cms.Web.Common.PublishedModels; +@inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage +

@Model.Title

+

@Model.BodyText

+``` + +{% hint style="info" %} +If your view inherits from `UmbracoViewPage` then the model is the content item itself and the syntax is `@Model.Title`. +{% endhint %} + +Models Builder respects the content types' inheritance tree, i.e. models inherit from each other if required, and mixins (content type compositions) are represented by interfaces. + +Models Builder is a "_code-after_\*\_" solution. It only generates code from content types that already exist in Umbraco. It is not a "_code-first_" solution - code-first is a much more complex question. + +And once you are using strongly typed models, there are some [cool things](coolthingswithmodels.md) that you can do. + +## Installing + +The Models Builder is by default embedded in Umbraco. If you need more complex features than what is provided, you still need to add the full package. However, as of right now the package is not updated to be able to handle NetCore or Umbraco V9. + +Check [the official releases on the Models Builder GitHub repository](https://github.com/zpqrtbnk/Zbu.ModelsBuilder/releases) for more details. + +## Documentation + +At the core of the strongly typed models "experience" is the `IPublishedModelFactory` interface. This interface is part of the Umbraco core codebase. It is responsible for mapping the internal `IPublishedContent` implementations returned by the content cache, to strongly typed models. There is a default factory shipped with Umbraco and it is possible to replace this by custom implementations. When using the default factory, models do _not_ necessarily need to be generated by Models Builder. + +Models Builder is one way to generate models for the default, built-in factory. Models can be generated automatically or straight from the Settings section of the Umbraco backoffice, for more info see [builder modes](builder-modes.md). diff --git a/16/umbraco-cms/reference/templating/modelsbuilder/understand-and-extend.md b/16/umbraco-cms/reference/templating/modelsbuilder/understand-and-extend.md new file mode 100644 index 00000000000..c3d7b89d21e --- /dev/null +++ b/16/umbraco-cms/reference/templating/modelsbuilder/understand-and-extend.md @@ -0,0 +1,237 @@ +--- +description: Understanding and Extending ModelsBuilder in Umbraco +--- + + +# 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. + +## 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" %} + +```csharp +/// TextPage +[PublishedModel("textPage")] +public partial class TextPage : PublishedContentModel +{ + //static helpers + public new const string ModelTypeAlias = "textPage"; + + public new const PublishedItemType ModelItemType = PublishedItemType.Content; + + public new static IPublishedContentType GetModelContentType(IPublishedSnapshotAccessor publishedSnapshotAccessor) + => PublishedModelUtility.GetModelContentType(publishedSnapshotAccessor, ModelItemType, ModelTypeAlias); + + public static IPublishedPropertyType GetModelPropertyType(IPublishedSnapshotAccessor publishedSnapshotAccessor, Expression> selector) + => PublishedModelUtility.GetModelPropertyType(GetModelContentType(publishedSnapshotAccessor), selector); + + private IPublishedValueFallback _publishedValueFallback; + + //constructor + public TextPage(IPublishedContent content, IPublishedValueFallback publishedValueFallback) + : base(content, publishedValueFallback) + { + _publishedValueFallback = publishedValueFallback; + } + + // properties + + /// + /// Header + /// + [ImplementPropertyType("header")] + public virtual string Header => this.Value(_publishedValueFallback, "header"); +} +``` + +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 +``` + +## Composition and Inheritance + +### 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, 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 +public partial interface IMetaInfo : IPublishedContent +{ + public string Author { get; } + public IEnumerable Keywords { get; } +} + +// The composition class +public partial class MetaInfo : PublishedContentModel +{ + // the "static mixin getter" for the property + public static string GetAuthor(IMetaInfo that) + { + return that.GetPropertyValue("author"); + } + + public string Author { get { return MetaInfo.GetAuthor(this, _publishedValueFallback); } } +} +``` + +And the `TextPage` model would be generated as: + +```csharp +public partial class TextPage : PublishedContentModel, IMetaInfo +{ + // get the property value from the "static mixin getter" + public string Author { get { return MetaInfo.GetAuthor(this, _publishedValueFallback); } } +} +``` + +### Inheritance + +In addition to composition, content types can have a parent-child relationship. In the Umbraco backoffice, a content type appears underneath its parent. + +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 +public partial class AboutPage : TextPage +{ + ... +} +``` + +## Extending Models + +Since models are partial classes, developers can extend them by adding additional properties. + +For Example: + +```csharp +public partial class TextPage +{ + public string WrappedHeader => $"[{Header}]"; +} +``` + +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. + +Overloaded constructors will not be used because models are always instantiated using the default constructor. + +For more complex customizations, use the full version of [Models Builder](https://github.com/zpqrtbnk/Zbu.ModelsBuilder). + +### Custom Model Generation with IModelsGenerator + +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 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. + +### 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: + +```csharp + public partial class Post + { + public string DisplayDate + { + get + { + if(!TextDate.IsNullOrWhiteSpace()) + { + return TextDate; + } + + if (ActualDate != default) + { + return ActualDate.ToString(); + } + + return UpdateDate.ToString(); + } + } + } +``` + +Simplified view: + +```csharp +
+
@Model.Title
+
@Model.DisplayDate
+
+``` + +### 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 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: + +```csharp +public class TextPageViewModel +{ + public TextPage Content; // The content model + public HomePage HomePage; // The home page content model + public IEnumerable Menu; // The menu content models +} +``` + +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 +``` + +### Ugly practices + +The model's scope and lifecycle are *unspecified*. It may exist only for your request or be cached and shared across all requests. + +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; +public HomePageDocument HomePage +{ + get + { + if (_homePage is null) + { + _homePage = this.AncestorOrSelf(1); + } + return _homePage; + } +} +``` + +As a rule of thumb, models should never reference and cache other models. diff --git a/16/umbraco-cms/reference/templating/modelsbuilder/using-interfaces.md b/16/umbraco-cms/reference/templating/modelsbuilder/using-interfaces.md new file mode 100644 index 00000000000..25f268711a9 --- /dev/null +++ b/16/umbraco-cms/reference/templating/modelsbuilder/using-interfaces.md @@ -0,0 +1,34 @@ +--- +description: "Using interfaces with modelsbuilder" +--- + +# Using Interfaces + +When using compositions, Models Builder generates an interface for the composed model, which enables us to not have to switch back to using `Value()` for the composed properties. + +A common use-case for this is if you have a separate composition for the "SEO properties" `Page Title` and `Page Description`. + +You would usually use this composition on both your `Home` and `Textpage` document types. Since both `Home` and `Textpage` will implement the generated `ISeoProperties` interface, you will still be able to use the simpler models builder syntax (e.g. `Model.PageTitle`). + +However, you won't be able to use the nice models builder syntax on any master template, since a master template needs to be bound to a generic `IPublishedContent`. So you'd have to resort to the *ever-so-slightly* clumsier `Model.Value("pageTitle")` syntax to render these properties. It is possible to solve this issue of master templating, by using partial views, to render the SEO specific properties. + +## Render with a partial + +If you create a partial and change the first line to use the *interface name* for the model binding, you can use the nice Models Builder syntax when rendering the properties, like this: + +```csharp +@inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage +@Model.PageTitle + +``` + +You can then render the partial from your Master Template with something like this (assuming the partial is named `Metatags.cshtml`): + +```csharp + + @Html.Partial("Metatags") + +@RenderBody() +``` + +It's important to note though, that this master template will only work for content types that use the Seo Properties composition. diff --git a/16/umbraco-cms/reference/templating/mvc/README.md b/16/umbraco-cms/reference/templating/mvc/README.md new file mode 100644 index 00000000000..134a98ed7ad --- /dev/null +++ b/16/umbraco-cms/reference/templating/mvc/README.md @@ -0,0 +1,23 @@ +--- +description: "How to work with MVC templates in Umbraco." +--- + +# Working with MVC in Umbraco + +## [Views](views.md) + +Working with MVC Views and Razor syntax in Umbraco + +[See some quick examples here](examples.md) + +## [Partial Views](partial-views.md) + +Documentation covering how to use Partial Views. This documentation relates to using native MVC partial views within Umbraco. + +## [ViewComponents](viewcomponents.md) + +Using MVC Child Actions in Umbraco + +## [Querying](querying.md) + +How to query for published data in your Views diff --git a/16/umbraco-cms/reference/templating/mvc/examples.md b/16/umbraco-cms/reference/templating/mvc/examples.md new file mode 100644 index 00000000000..e4b4151d910 --- /dev/null +++ b/16/umbraco-cms/reference/templating/mvc/examples.md @@ -0,0 +1,28 @@ +# View/Razor Examples + +_Lots of examples of using various techniques to render data in a view_ + +## Rendering the raw value of a field from IPublishedContent + +```csharp +@Model.Value("bodyContent") +``` + +## Rendering the converted value of a field from IPublishedContent + +```csharp +@Model.Value("amount") +@Model.Value("bodyContent") +``` + +## Rendering some member data + +```csharp +@if(Members.IsLoggedIn()){ + var profile = Members.GetCurrentMemberProfileModel(); + var umbracomember = Members.GetByUsername(profile.UserName); + +

@umbracomember.Name

+

@umbracomember.Value("bio")

+} +``` diff --git a/16/umbraco-cms/reference/templating/mvc/forms.md b/16/umbraco-cms/reference/templating/mvc/forms.md new file mode 100644 index 00000000000..56453691ef9 --- /dev/null +++ b/16/umbraco-cms/reference/templating/mvc/forms.md @@ -0,0 +1,8 @@ +# Creating Forms + +Creating an HTML form to submit data with MVC in Umbraco is possible in a few steps. + +If you want to create a Form: + +1. Using view models, views, controllers, and a handy HtmlHelper extension method called BeginUmbracoForm, see the [Creating Forms](../../../fundamentals/code/creating-forms.md) article. +2. Using Umbraco Forms, see the [Umbraco Forms Documentation](https://docs.umbraco.com/umbraco-forms/) article. diff --git a/16/umbraco-cms/reference/templating/mvc/partial-views.md b/16/umbraco-cms/reference/templating/mvc/partial-views.md new file mode 100644 index 00000000000..23e682f93b1 --- /dev/null +++ b/16/umbraco-cms/reference/templating/mvc/partial-views.md @@ -0,0 +1,142 @@ +# Using MVC Partial Views in Umbraco + +This section will show you how to use MVC Partial Views in Umbraco. + +{% hint style="warning" %} +Please note, this is documentation relating to the use of native MVC partial views +{% endhint %} + +Partial views allow you to reuse components between your views (templates). + +## View Locations + +The locations to store Partial Views when rendering in the Umbraco pipeline is: + +``` +~/Views/Partials +``` + +The standard MVC partial view locations will also work: + +``` +~/Views/Shared +~/Views/Render +``` + +The `~/Views/Render` location is valid because the controller that performs the rendering in the Umbraco codebase is the: `Umbraco.Cms.Web.Common.Controllers.RenderController` + +If however you are [Hijacking an Umbraco route](../../routing/custom-controllers.md) and specifying your own controller to do the execution, then your partial view location can also be: + +``` +~/Views/{YourControllerName} +``` + +## Example + +A quick example of a content item that has a template that renders out a partial view template for each of its child documents: + +The MVC template markup for the document: + +```csharp +@inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage +@{ + Layout = null; +} + + + +@foreach(var page in Model.Children().Where(x => x.IsVisible())) + { +
+ @Html.Partial("ChildItem", page) +
+ } + + +``` + +The partial view (located at: `~/Views/Partials/ChildItem.cshtml`) + +```csharp +@model IPublishedContent +@Model.Name +``` + +## Strongly typed Partial Views + +Normally you would create a partial view by using the `@model MyModel` syntax. However, inside of Umbraco you will probably want to have access to the handy properties available on your normal Umbraco views like the Umbraco helper: `@Umbraco` and the Umbraco context: `@UmbracoContext`. The good news is that this is possible. Instead of using the `@model MyModel` syntax, you need to inherit from the correct view class, so do this instead: + +```csharp +@inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage +``` + +By inheriting from this view, you'll have instant access to those handy properties and have your view created with a strongly typed custom model. + +Another case you might have is that you want your Partial View to be strongly typed with the same model type (`IPublishedContent`) as a normal template if you are passing around instances of IPublishedContent. To do this, have your partial view inherit from `Umbraco.Cms.Web.Common.Views.UmbracoViewPage` (like your normal templates). When you render your partial, a neat trick is that you can pass it an instance of `IPublishedContent`. For example: + +```csharp +@foreach(var child in Model.Children()) +{ + @Html.Partial("MyPartialName", child) +} +``` + +## Caching + +You don't normally need to cache the output of Partial views, like you don't normally need to cache the output of User Controls. However, there are times when this is necessary and so we provide caching output of partial views. This is done by using an HtmlHelper extension method: + +```csharp +@await Html.CachedPartialAsync("ChildItem", page, TimeSpan.FromHours(1)) +``` + +The above will cache the output of your partial view for one hour when not running Umbraco in `debug` mode. Additionally, there are a few optional parameters you can specify to this method. Here is the full method signature: + +```csharp +Task CachedPartialAsync( + this IHtmlHelper htmlHelper, + string partialViewName, + object model, + TimeSpan cacheTimeout, + bool cacheByPage = false, + bool cacheByMember = false, + ViewDataDictionary? viewData = null, + Func? contextualKeyBuilder = null) +``` + +So you can specify to cache by member and/or by page and also specify additional view data to your partial view. \* _However_\*, if your view data is dynamic (meaning it could change per page request) the cached output will still be returned. This same principle applies if the model you are passing in is dynamic. Please be aware of this: if you have a different model or viewData for any page request, the result will be the cached result of the first execution. If this is not desired you can generate your own cache key to differentiate cache instances using the contextualKeyBuilder parameter + +To create multiple versions based on one or more viewData parameters you can do something like this: + +```csharp +@await Html.CachedPartialAsync("ChildItem", Model, TimeSpan.FromHours(1), true, false, new ViewDataDictionary(ViewData) +{ + { "year", Context.Request.Query["year"] } +}, (model, viewData) => viewData?["year"] + viewData?["Parameter2"]?.ToString() ) +``` + +Or using a custom helper function: + +```csharp +@functions{ + private static Func? CacheBy(params string[] keys) + { + return (model, viewData) => String.Join("", keys.Select(s => viewData?[s]?.ToString() ?? string.Empty)); + } +} + +@await Html.CachedPartialAsync("MediaGallery", Model, TimeSpan.FromHours(1), true, false, new ViewDataDictionary(ViewData) + { + { "year", Context.Request.Query["year"] } + }, CacheBy("year", "Parameter2")) +``` + +Or even based on a property on the Model (though if Model is the current page then `cacheByPage` should be used instead): + +```csharp + @await Html.CachedPartialAsync("MediaGallery", Model, TimeSpan.FromHours(1), true, false, new ViewDataDictionary(ViewData) { }, + (model, viewData) => (model is IPublishedContent pc ? pc.Name : null) ?? string.Empty) +``` + +Regardless of the complexity here the contextualKeyBuilder function needs to return a single string value. + +Caching is only enabled when your application has `debug="false"`. When `debug="true"` caching is disabled. Also, the cache of all CachedPartials is emptied on Umbraco publish events. diff --git a/16/umbraco-cms/reference/templating/mvc/querying.md b/16/umbraco-cms/reference/templating/mvc/querying.md new file mode 100644 index 00000000000..5f4f8538747 --- /dev/null +++ b/16/umbraco-cms/reference/templating/mvc/querying.md @@ -0,0 +1,122 @@ +# Querying & Traversal + +_This section will describe how you can render content from other nodes besides the current page in your MVC Views_ + +## Querying for content and media by id + +The easiest way to get some content by Id is to use the following syntax (where 1234 is the content id you'd like to query for): + +```csharp +// to return IPublishedContent +@Umbraco.Content(1234) +``` + +You can also query for multiple content items using multiple ids: + +```csharp +// to return the strongly typed (IEnumerable) collection +@Umbraco.Content(1234, 4321, 1111, 2222) +``` + +This syntax will support an unlimited number of Ids passed to the method. + +You can also retrieve content using the `Guid` Id. In the example "ca4249ed-2b23-4337-b522-63cabe5587d1" is the key of the content. + +```csharp +// to return the Umbraco.Core.Models.IPublishedContent +@Umbraco.Content(Guid.Parse("ca4249ed-2b23-4337-b522-63cabe5587d1")) +``` + +You can also pass a [Udi](../../querying/udi-identifiers.md) to retrieve the content. + +```csharp +// to return the Umbraco.Core.Models.IPublishedContent +@Umbraco.Content(Udi.Create("document", Guid.Parse("ca4249ed-2b23-4337-b522-63cabe5587d1"))) +``` +The same query structures apply to media: + +```csharp +@Umbraco.Media(9999) +@Umbraco.Media(9999,8888,7777) +@Umbraco.Media(9999) +@Umbraco.Media(9999,8888,7777) +@Umbraco.Content(Guid.Parse("ca4249ed-2b23-4337-b522-63cabe5587d1")) +@Umbraco.Content(Udi.Create("media", Guid.Parse("ca4249ed-2b23-4337-b522-63cabe5587d1"))) +``` + +## Traversing + +All of these extension methods are available on `Umbraco.Core.Models.IPublishedContent` so you can have strongly typed access to all of them with intellisense for both content and media. The following methods return `IEnumerable` + +```csharp +Children() // this is the same as using the Children property on the content item. +Ancestors() +Ancestors(int level) +Ancestors(string nodeTypeAlias) +AncestorsOrSelf() +AncestorsOrSelf(int level) +AncestorsOrSelf(string nodeTypeAlias) +Descendants() +Descendants(int level) +Descendants(string nodeTypeAlias) +DescendantsOrSelf() +DescendantsOrSelf(int level) +DescendantsOrSelf(string nodeTypeAlias) +Siblings() +SiblingsAndSelf() +``` + + +Additionally there are other methods that will return a single `IPublishedContent` + +```csharp +Ancestor() +AncestorOrSelf() +AncestorOrSelf(int level) +AncestorOrSelf(string nodeTypeAlias) +AncestorOrSelf(Func func) +``` + +## Complex querying (Where) + +With the `IPublishedContent` model we support strongly typed LINQ queries out of the box so you will have intellisense for that. + +### Some examples + +#### Where children are visible + +```csharp +@Model.Children().Where(x => x.IsVisible()) +``` + +#### Traverse for sitemap + +```csharp +var items = @Model.Children().Where(x => x.IsVisible() && x.Level <= 4) +``` +{% hint style="info" %} +The two examples below have not been verified for Umbraco 9 and 10 yet. + +therefore they might not work on the latest versions of Umbraco. +{% endhint %} + +#### Content sub menu + +```csharp +@Model.AncestorOrSelf(1).Children().Where(x => x.DocumentTypeAlias == "DatatypesFolder").First().Children() +``` + +#### Complex query + +With the strongly typed `IPublishedContent` you can do complex queries. + +```csharp +// This example gets the top level ancestor for the current node, and then gets +// the first node found that contains "1173" in the array of comma delimited +// values found in a property called 'selectedNodes'. + +var result = @Model.Ancestors().OrderBy(x => x.Level) + .Single() + .Descendants() + .FirstOrDefault(x => x.GetPropertyValue("selectedNodes", "").Split(',').Contains("1173")); +``` diff --git a/16/umbraco-cms/reference/templating/mvc/viewcomponents.md b/16/umbraco-cms/reference/templating/mvc/viewcomponents.md new file mode 100644 index 00000000000..511acb28b57 --- /dev/null +++ b/16/umbraco-cms/reference/templating/mvc/viewcomponents.md @@ -0,0 +1,101 @@ +# Using View Components + +In the previous versions of MVC, we used Child Actions to build reusable components/widgets consisting of both Razor markup and backend logic. The backend logic was implemented as a controller action and marked with a *[ChildActionOnly]* attribute. Child Actions are no longer supported in ASP.NET Core MVC. Instead, we will use the *View Component* feature. + +## View Component Overview + +View components replace the traditional Controller (SurfaceController)/Partial View relationship and instead offers a modular approach of separating your views in to several smaller units. View Components are self-contained objects that consistently render HTML from a Razor view. + +View components are: + +- Generated from a C# class +- Derived from the base class ViewComponent and +- Associated with a Razor file (*.cshtml) to generate markup. + +[View components](https://docs.microsoft.com/en-us/aspnet/core/mvc/views/view-components?view=aspnetcore-5.0) are similar to partial views but they are much more powerful compared to the partial views. View components do not use model binding, instead they work with the data provided when calling it. + +View Components can be implemented in any part of the web application. There are some possibilities to duplicate code like Header, Navigation Pane, Login Panel, Menu, Shopping Cart, Footer, BlockList Items and so on. View Components behave like a web part containing both business logic and UI design. This is because they create a package which can be reused in multiple parts of the web application. + +A view component code consists of two parts: + +- The View Component class derived from the `ViewComponent` class: + + ```csharp + [ViewComponent(Name = "Employee")] + public class EmployeeViewComponent : ViewComponent + {} + ``` + +- Returns a Task object as `IViewComponentResult`: + + ```csharp + public IViewComponentResult Invoke() + { + return Content("Hi I'm an Employee Component"); + } + ``` + +### Create a class for a ViewComponent + +In this example, let's create a ViewComponent for a Product List and render it on the *HomePage* of the website. + +Create a folder named **ProductView**. In this folder, create a new class named **ProductViewViewComponent.cs** as below: + +```csharp +using System.Collections.Generic; +using Microsoft.AspNetCore.Mvc; + +namespace Umbraco.Docs.Samples.Web.Components.ProductView; + +public class ProductViewViewComponent : ViewComponent +{ + public IViewComponentResult Invoke() + { + List products = new List() { + "Product 1", "Product 2", "Product 3", "Product 4", "Product 5" + }; + + return View(products); + } +} +``` + +### Create a View for ViewComponent + +In **Views** folder, create new folders at `Views\Shared\Components\ProductView`. In the **ProductView** folder, create a new file named **Default.cshtml** as below: + +```csharp +

Welcome to your Home Page

+

Products List

+
    + @foreach (var product in Model) + { +
  • @product
  • + } +
+``` + +#### UmbracoHelper in a ViewComponent + +Adding the following declaration will give access to the UmbracoHelper object inside the ViewComponent View + +```csharp +@inject Umbraco.Cms.Web.Common.UmbracoHelper Umbraco +``` + +### Invoking a View Component + +You can invoke a ViewComponent from anywhere (even from within a Controller or another ViewComponent). Since this is our Product List, we want it rendered on the Home page - so we’ll invoke it from our HomePage.cshtml file using: + +```csharp + @(await Component.InvokeAsync("ProductView")) +``` + +You can read about different ways of invoking your view component in the [View components in ASP.NET Core](https://docs.microsoft.com/en-us/aspnet/core/mvc/views/view-components?view=aspnetcore-5.0#invoking-a-view-component) section of the Microsoft Documentation.view=aspnetcore-5.0) + +## View Component Locations + +By default, the framework searches for the Component View path in the following areas: + +- `/Views/{Controller Name Folder}/Components/{View Component Name Folder}/{View Name}` +- `/Views/Shared/Components/{View Component Name Folder}/{View Name}` diff --git a/16/umbraco-cms/reference/templating/mvc/views.md b/16/umbraco-cms/reference/templating/mvc/views.md new file mode 100644 index 00000000000..289385f13c9 --- /dev/null +++ b/16/umbraco-cms/reference/templating/mvc/views.md @@ -0,0 +1,89 @@ +# Working with MVC Views + +_Working with MVC Views and Razor syntax in Umbraco_ + +## Properties available in Views + +All Umbraco views inherit from `Umbraco.Cms.Web.Common.Views.UmbracoViewPage` along with the using statement `@using ContentModels = Umbraco.Cms.Web.Common.PublishedModels;`. This exposes many properties that are available in razor. The properties on the Document Type can be accessed in a number of ways: + +* @Model (of type `Umbraco.Web.Mvc.ContentModel`) -> the model for the view which contains the standard list of IPublishedContent properties but also gives you access to the typed current page (of type whatever type you have added in the angled brackets). +* @Umbraco (of type `UmbracoHelper`) -> contains many helpful methods, from rendering fields to retrieving content based on an Id and tons of other helpful methods. [See UmbracoHelper Documentation](../../querying/umbracohelper.md) +* @Html (of type `HtmlHelper`) -> the same HtmlHelper you know and love from Microsoft but we've added a bunch of handy extension methods like @Html.BeginUmbracoForm +* @UmbracoContext (of type `Umbraco.Cms.Web.Common.UmbracoContext`) + +## Rendering a field in a strongly typed view + +This is probably the most used method which renders the contents of a field using the alias of the content item. + +```csharp +@Model.Value("bodyContent") +``` + +If you're using the method from within a partial view then be aware that you will need to inherit the context so the method knows which type to get the desired value from. You'd do this at the top of partial view and so strongly typed properties can then be accessed in the partial view. For instance you can pass "HomePage" like this: + +```csharp +@inherits UmbracoViewPage +... +@Model.Value("title") +``` + +You will also need to pass the "Context" to the @Model.Value() method if you're looping over a selection like this where we pass the "item" variable. + +Looping over a selection works in a similar way. If you have a property that contains, for instance, an IEnumberable collection, you can access the individual items using a foreach loop. Below illustrates how you might do that using "item" as a variable. + +```csharp +@{ + var collection = Model.ItemList; +} + +
    + @foreach(var item in collection) + { +

    @item.Name

    + } +
+``` +If you want to convert a type and it's possible, you can do that by typing a variable and assigning the value from your property to it. This could look like the example below. +```csharp +@foreach (TeamMember person in Model.TeamMembers) +{ + +

@person.Name

+
+} + ``` + +In this example, we are looping through a list of items with the custom made type TeamMember assigned. This means we are able to access the strongly typed properties on the TeamMember item. + +[UmbracoHelper Documentation](../../querying/umbracohelper.md) + +## Accessing Member data + +`IMemberManager` is the gateway to everything related to members when templating your site. [IMemberManager Documentation](../../querying/imembermanager.md) + +```csharp +@using Umbraco.Cms.Core.Security; +@inject IMemberManager _memberManager; + +@if(_memberManager.IsLoggedIn()) +{ +

A Member is logged in

+} +else +{ +

No member is logged in

+} +``` + +## Models Builder + +Models Builder allows you to use strongly typed models in your views. +Properties created on your document types can be accessed with this syntax: + +```csharp +@Model.BodyText +``` + +When Models Builder resolve your properties it will also try to use value converters to convert the values of your data into more convenient models. This allows you to access nested objects as strong types instead of having to rely on dynamics and risking having a lot of potential errors when working with these. + +[Models Builder documentation](../modelsbuilder/) diff --git a/16/umbraco-cms/reference/umbraco-flavored-markdown.md b/16/umbraco-cms/reference/umbraco-flavored-markdown.md new file mode 100644 index 00000000000..7bb6f77394d --- /dev/null +++ b/16/umbraco-cms/reference/umbraco-flavored-markdown.md @@ -0,0 +1,197 @@ +# Umbraco Flavored Markdown + +{% hint style="info" %} +**Are you looking for Label Property Configuration?** +With the removal of AngularJS, advanced label rendering is now handled using Umbraco Flavored Markdown. +{% endhint %} + +Umbraco Flavored Markdown (UFM) is the dialect of Markdown, used to support property descriptions and advanced labels within the Umbraco CMS backoffice. These can be used with Block editors (Block Grid, Block List) and Collection View columns (in Grid and Table views). + +{% hint style="info" %} +If you are not familiar with Markdown, you can read more about its philosophy and syntax on the [Daring Fireball website](https://daringfireball.net/projects/markdown/syntax). +{% endhint %} + +Using Markdown for labels provides basic text formatting. It natively supports the use of HTML, enabling web components for complex label templating scenarios. + +UFM is built on top of [GitHub Flavored Markdown](https://github.github.com/gfm/) and [CommonMark](https://spec.commonmark.org/) specifications. The implementation for Umbraco 14 has been developed as an extension to the [Marked library](https://marked.js.org/). + +## Syntax + +The essence of the UFM syntax is curly brackets with an alias prefix delimited with a colon. + +```markdown +{: } +``` + +For clarity... + +- The opening token is `{` Left Curly Bracket +- The alias prefix can be any valid Unicode character(s), including emojis +- Followed by `:` Colon, (not part of the alias prefix itself) +- The contents within the curly brackets can include any Unicode characters, including whitespace +- The closing token is `}` Right Curly Bracket + +An example of this syntax to render a value of a property by its alias is: `{umbValue: bodyText}`. + +The curly brackets indicate that the UFM syntax should be processed. The `umbValue` alias prefix indicates which UFM component should be rendered, and the `bodyText` contents are the parameter that is passed to that UFM component. + +With this example, the syntax `{umbValue: bodyText}` would be processed and rendered as the following markup: + +```js + +``` + +The internal working of the `ufm-label-value` component would then be able to access the property's value using the [Context API](../customizing/foundation/working-with-data/context-api.md). + + +### Filters + +In addition, a filter syntax can be applied to UFM contents. This can be useful for formatting or transforming a value without needing to develop your own custom UFM component. + +The syntax for UFM filters uses a pipe character `|` (Vertical Line). Multiple filters may be applied, and the value from the previous filter is passed onto the next. + +To display a rich text value, stripping out the HTML markup and limiting it to the first 15 words could use the following filters: + +```markdown +{umbValue: bodyText | strip-html | word-limit:15} +``` + +The following UFM filters are available to use. + +| Name | Alias | Example syntax | +| ---------- | ------------ | -------------------------------------- | +| Lowercase | `lowercase` | `{umbValue: headline \| lowercase}` | +| Strip HTML | `strip-html` | `{umbValue: bodyText \| strip-html}` | +| Title Case | `title-case` | `{umbValue: headline \| title-case}` | +| Truncate | `truncate` | `{umbValue: intro \| truncate:30:...}` | +| Uppercase | `uppercase` | `{umbValue: headline \| uppercase}` | +| Word Limit | `word-limit` | `{umbValue: intro \| word-limit:15}` | + + +## UFM components + +### Available UFM components + +The following UFM components are available to use. + +- Label Value +- Localize +- Content Name + +More UFM components will be available in upcoming Umbraco releases. + + +#### Label Value + +The Label Value component will render the current value of a given property alias. + +The alias prefix is `umbValue`. An example of the syntax is `{umbValue: bodyText}`, which would render the component as ``. + +For brevity and backwards-compatibility, the `=` marker prefix can be used, e.g. `{=bodyText}`. + + +#### Localize + +The Localize component will render a localization for a given term key. + +The alias prefix is `umbLocalize`. An example of the syntax is `{umbLocalize: general_name}`, which would render the component as ``. + +Similarly, for brevity and backwards-compatibility, the `#` marker prefix can be used, e.g. `{#general_name}`. + + +#### Content Name + +The Content Name component will render the name of a content item, (either Document, Media or Member), from the value of a given property alias. Multiple values will render the names as a comma-separated list. + +The alias prefix is `umbContentName` An example of the syntax is `{umbContentName: pickerAlias}`, which would render the component as ``. + +The Content Name component supports content-based pickers, such as the Document Picker, Content Picker (formerly known as Multinode Treepicker), and Member Picker. Support for the advanced Media Picker will be available in an upcoming Umbraco release. + + +### Custom UFM components + +If you wish to develop your own custom UFM component, you can use the `ufmComponent` extension type: + +```json +{ + type: 'ufmComponent', + alias: 'My.CustomUfmComponent', + name: 'My Custom UFM Component', + api: () => import('./components/my-custom.component.js'), + meta: { + alias: 'myCustom' + } +} +``` + +The corresponding JavaScript/TypeScript API would contain a method to render the custom label/markup. + +```js +import { UmbUfmComponentBase } from '@umbraco-cms/backoffice/ufm'; + +export class MyCustomUfmComponentApi extends UmbUfmComponentBase { + render(token: Tokens.Generic) { + // You could do further regular expression/text processing here! + return ``; + } +} + +export { MyCustomUfmComponentApi as api }; +``` + +Using the `{myCustom: myCustomText}` syntax would render the following markup: ``. Inside the `ufm-custom-component` component code, you can perform any logic to render your required markup. + + +### Custom UFM filters + +If you wish to develop custom UFM filter, you can use the `ufmFilter` extension type: + +```json +{ + type: 'ufmFilter', + alias: 'My.UfmFilter.Reverse', + name: 'Reverse UFM Filter', + api: () => import('./reverse.filter.js'), + meta: { + alias: 'reverse' + } +} +``` + +The corresponding JavaScript/TypeScript API would contain a function to transform the value. + +```js +import { UmbUfmFilterBase } from '@umbraco-cms/backoffice/ufm'; + +class UmbUfmReverseFilterApi extends UmbUfmFilterBase { + filter(str?: string) { + return str?.split("").reverse().join(""); + } +} + +export { UmbUfmReverseFilterApi as api }; +``` + +Using the `{umbValue: headline | reverse}` syntax where `headline` having a value of `Hello world` would be transformed to `dlrow olleH`. + + +## Post-processing and sanitization + +When the markdown has been converted to HTML, the markup will be run through post-processing sanitization to ensure security and consistency within the backoffice. + +As of Umbraco 14, the [DOMPurify library](https://github.com/cure53/DOMPurify) is used to sanitize the markup and prevent Cross-site scripting (XSS) attacks. + +The sanitized markup will be... + +- Valid HTML +- Anchor links will have their target set to `_blank` +- Only web components that have a prefix of `ufm-`, `umb-` or `uui-` will be allowed to render + + +## Rendering UFM in custom components + +If you would like to render UFM within your own web components in the Umbraco CMS backoffice, you can use the `umb-ufm-render` component: + +```js + +``` diff --git a/16/umbraco-cms/reference/using-ioc.md b/16/umbraco-cms/reference/using-ioc.md new file mode 100644 index 00000000000..c7b08b48384 --- /dev/null +++ b/16/umbraco-cms/reference/using-ioc.md @@ -0,0 +1,471 @@ +--- +description: Inversion of Control/Dependency Injection in Umbraco +--- + +# Inversion of Control / Dependency injection + +Umbraco supports dependency injection out of the box using the [ASP.NET Core built-in dependency injection](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection). This means that working with dependencies in Umbraco is similar to working with them in ASP.NET Core. + +`IUmbracoBuilder` is a Umbraco-specific abstraction on top of the `IServiceCollection`. Its purpose is to aid in adding and replacing Umbraco-specific services, such as notification handlers, filesystems, server role accessors, and so on. You can access the `IServiceCollection` directly to add custom services through the `Services` property. See below for a concrete example: + +```csharp +IUmbracoBuilder.Services +``` + +## Registering dependencies + +There are different strategies for registering your dependencies and not one strategy is better than the other. + +In this article, we will cover the following three strategies: + +* [Registering dependencies in the `Program.cs` file](#registering-dependencies-in-the-programcs-file) +* [Registering dependencies in a composer](#registering-dependencies-in-a-composer) +* [Registering dependencies in bundles](#registering-dependencies-in-bundles) + +Which strategy to choose depends on the scenario requiring dependency registration. + +### Choosing a strategy for registering dependencies + +Are you **[working directly on your site](#registering-dependencies-in-the-programcs-file)**? You can choose whichever strategy you prefer working with. + +Are you **[building a package](#registering-dependencies-in-a-composer)** and do not have access to the `Program.cs` file? In this case, you have the option to register the dependencies in a composer. + +Are you in a situation where you need to **[register more than a few dependencies](#registering-dependencies-in-bundles)**? You can bundle your dependencies in custom extension methods and register them in a single call. + +### Registering dependencies in the `Program.cs` file + +When working with your Umbraco site, dependencies can be registered within the `Program.cs` file. + +In the example below, a custom notification handler is added to the `CreateUmbracoBuilder()` builder chain: + +{% code title="Program.cs" %} + +```csharp +builder.CreateUmbracoBuilder() + .AddBackOffice() + .AddWebsite() + .AddDeliveryApi() + .AddComposers() + // When you need to add something Umbraco-specific, do it in the "AddUmbraco" builder chain, using the IUmbracoBuilder extension methods. + .AddNotificationHandler() + .Build(); +``` + +{% endcode %} + +{% hint style="info" %} +Learn more about the uses of the `Program.cs` file in [the official ASP.NET Core Fundamentals documentation](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/?view=aspnetcore-8.0&tabs=windows). +{% endhint %} + +### Registering dependencies in a composer + +When working with packages, you do not have access to the `Program.cs` file. Instead, you can use a [composer](../implementation/composing.md) to register your dependencies. + +Below is an example of a composer using the `Services` property of the `IUmbracoBuilder`: + +{% code title="MyComposer.cs" %} + +```csharp +using IOCDocs.NotificationHandlers; +using IOCDocs.Services; +using Microsoft.Extensions.DependencyInjection; +using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Core.DependencyInjection; +using Umbraco.Cms.Core.Notifications; + +namespace IOCDocs; + +public class MyComposer : IComposer +{ + public void Compose(IUmbracoBuilder builder) + { + builder.AddNotificationHandler(); + builder.Services.AddSingleton(); + } +} +``` + +{% endcode %} + +{% hint style="info" %} +To access the `IUmbracoBuilder`, you need to add `Umbraco.Cms.Core.DependencyInjection` and `Microsoft.Extensions.DependencyInjection` as using statements when registering your services. This, in turn, will also give you access to the `IUmbracoBuilder` extension methods as well as the Microsoft `IServiceProvider`. +{% endhint %} + +### Registering dependencies in bundles + +Depending on your scenario, you may have a lot of dependencies you need to register. In this case, your `Program.cs` or Composer can become cluttered and hard to manage. + +You can manage multiple services in one place by creating your custom extension methods for the `IUmbracoBuilder`. This way you can bundle similar dependencies in extension methods and register them all in a single call. + +In the following code sample two dependencies, `RegisterCustomNotificationHandlers` and `RegisterCustomServices` are bundled together in a custom `AddCustomServices` extension method. + +{% code title="MyCustomBuilderExtensions.cs" %} + +```csharp +using IOCDocs.NotificationHandlers; +using IOCDocs.Services; +using Microsoft.Extensions.DependencyInjection; +using Umbraco.Cms.Core.DependencyInjection; +using Umbraco.Cms.Core.Notifications; + +namespace IOCDocs; + +public static class MyCustomBuilderExtensions +{ + // The first dependency is registered + public static IUmbracoBuilder RegisterCustomNotificationHandlers(this IUmbracoBuilder builder) + { + builder.AddNotificationHandler(); + {...} + return builder; + } + + // The second dependency is registered + public static IUmbracoBuilder RegisterCustomServices(this IUmbracoBuilder builder) + { + builder.Services.AddSingleton(); + {...} + return builder; + } + + // The two dependencies are bundled together + public static IUmbracoBuilder AddCustomServices(this IUmbracoBuilder builder) + { + builder.RegisterCustomNotificationHandlers(); + builder.RegisterCustomServices(); + return builder; + } +} +``` + +{% endcode %} + +{% hint style="info" %} +It is not required to have an interface registering your dependencies: + +```csharp +services.AddSingleton(); +``` + +{% endhint %} + +With the dependencies bundled together, you can call the `AddCustomServices` method in either the `Program.cs` file or your composer: + +{% tabs %} +{% tab title="Program.cs" %} + +{% code title="Program.cs" %} + +```csharp +builder.CreateUmbracoBuilder() + .AddBackOffice() + .AddWebsite() + .AddDeliveryApi() + .AddComposers() + // Register all custom dependencies in one go using the custom extension method + .AddCustomServices() + .Build(); +``` + +{% endcode %} + +{% endtab %} +{% tab title="Composer" %} + +{% code title="MyComposer.cs" %} + +```csharp +using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Core.DependencyInjection; + +namespace IOCDocs; + +public class MyComposer : IComposer +{ + public void Compose(IUmbracoBuilder builder) + { + // Register all custom dependencies in one go using the custom extension method + builder.AddCustomServices(); + } +} +``` + +{% endcode %} + +{% endtab %} +{% endtabs %} + +## Service lifetime + +During registration of your dependencies, you have to define the lifetime of your service: + +```csharp +IServiceCollection.AddTransient(); +IServiceCollection.AddScoped(); +IServiceCollection.AddSingleton(); +``` + +There are three possible lifetimes: + +| Name | Lifetime | Description | +|---|---|---| +| **Transient** | Creates a new instance | A new instance will be created each time it's injected. | +| **Scoped** | One unique instance per web request (connection) | Scoped services are disposed of at the end of the request. Be careful not to resolve a scoped service from a singleton, as it may lead to an incorrect state in subsequent requests. | +| **Singleton** | One unique instance for the whole web application | The single instance will be shared across all web requests. | + +For more information, read the official [Microsoft documentation on dependency injections](https://docs.microsoft.com/en-us/dotnet/core/extensions/dependency-injection#service-lifetimes). + +## Injecting dependencies + +Once you have registered the dependencies inject them into your project where needed. + +### Injecting dependencies into a class + +If you need to inject your service into a controller or another service, you will do so through the class. + +{% code title="FooController.cs" %} + +```csharp +using IOCDocs.Services; +using Microsoft.AspNetCore.Mvc; + +namespace IOCDocs.Controllers; + +[ApiController] +[Route("/umbraco/api/foo")] +public class FooController : Controller +{ + private readonly IFooBar _fooBar; + + public FooController(IFooBar fooBar) + { + _fooBar = fooBar; + } + + [HttpGet("foo")] + public string Foo() + { + var bar = _fooBar.Foo(); + return bar; + } +} +``` + +{% endcode %} + +If you place a breakpoint on `var bar = _foobar.Foo()`, open `/Umbraco/Api/foo/foo` in your browser and inspect the variable, you'll see that the value is `bar`. This is what you would expect as all the `Foobar.Foo()` method does is to return `Bar` as a string: + +{% code title="Foobar.cs" %} + +```csharp +namespace IOCDocs.Services; + +public class Foobar : IFooBar +{ + public string Foo() => "Bar"; +} +``` + +{% endcode %} + +### Injecting dependencies into a View or Template + +In some cases you might need to use services within your templates or view files. Services can be injected directly into your views using the `@inject` keyword. This means that you can inject the `Foobar` from above into a view like shown below: + +{% code title="Home.cshtml" %} + +```html +@using Umbraco.Cms.Web.Common.PublishedModels; +@inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage +@using ContentModels = Umbraco.Cms.Web.Common.PublishedModels; + +@* Add a using statement for the namespace of the service *@ +@using IOCDocs.Services +@* Inject the service *@ +@inject IFooBar _fooBar + +@{ + Layout = null; +} + +

@_fooBar.Foo()

+``` + +{% endcode %} + +When loading a page using the template above, you will see the "Bar" heading which is retrieved from the service. + +{% hint style="info" %} +To use the service a using statement for the namespace of the service needs to be added. +{% endhint %} + +## Other things you can inject + +In this section, you can find examples of what you can inject when working with Umbraco. + +### UmbracoHelper + +[Read more about the UmbracoHelper](querying/umbracohelper.md) + +The `UmbracoHelper` is a scoped service, which means you can only use it in services that are also scoped or transient. To get the UmbracoHelper you must inject `IUmbracoHelperAccessor` and use that to resolve it: + +{% code title="MyCustomScopedService.cs" %} + +```csharp +using System.Collections.Generic; +using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Web.Common; + +namespace IOCDocs.Services; + +// This service must be scoped +public class MyCustomScopedService +{ + private readonly IUmbracoHelperAccessor _umbracoHelperAccessor; + + public MyCustomScopedService(IUmbracoHelperAccessor umbracoHelperAccessor) + { + _umbracoHelperAccessor = umbracoHelperAccessor; + } + + public IEnumerable GetContentAtRoot() + { + // Try and get the Umbraco helper + var success = _umbracoHelperAccessor.TryGetUmbracoHelper(out var umbracoHelper); + if (success is false) + { + // Failed to get UmbracoHelper, probably because it was accessed outside of a scoped/transient service. + return null; + } + + public IEnumerable GetContentAtRoot() + { + // Try and get the Umbraco helper + var success = _umbracoHelperAccessor.TryGetUmbracoHelper(out var umbracoHelper); + if (success is false) + { + // Failed to get UmbracoHelper, probably because it was accessed outside of a scoped/transient service. + return null; + } + + // We got Umbraco helper, now we can do something with it. + return umbracoHelper.ContentAtRoot(); + } + } +} +``` + +{% endcode %} + +{% hint style="info" %} +Using the UmbracoHelper is only possible when there is an instance of the UmbracoContext. [You can read more in the implementation article about services](../implementation/services/). +{% endhint %} + +### ExamineManager + +[Read more about the ExamineManager in the Searching articles](searching/examine/). + +{% include "../.gitbook/includes/obsolete-warning-publishedsnapshot.md" %} + +{% code title="SearchService.cs" %} + +```csharp +using System; +using System.Collections.Generic; +using Examine; +using Umbraco.Cms.Core; +using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.Web; +using Umbraco.Cms.Infrastructure.Examine; +using Umbraco.Extensions; + +namespace IOCDocs.Services; + +// This service must be scoped. +public class SearchService : ISearchService +{ + private readonly IExamineManager _examineManager; + private readonly IUmbracoContextAccessor _umbracoContextAccessor; + + public SearchService(IExamineManager examineManager, IUmbracoContextAccessor umbracoContextAccessor) + { + _examineManager = examineManager; + _umbracoContextAccessor = umbracoContextAccessor; + } + + public IEnumerable Search(string searchTerm) + { + if (_examineManager.TryGetIndex(Constants.UmbracoIndexes.ExternalIndexName, out var index) is false) + { + throw new InvalidOperationException($"No index found by name {Constants.UmbracoIndexes.ExternalIndexName}"); + } + + if (!(index is IUmbracoIndex umbracoIndex)) + { + if (_examineManager.TryGetIndex(Constants.UmbracoIndexes.ExternalIndexName, out var index) is false) + { + throw new InvalidOperationException($"No index found by name {Constants.UmbracoIndexes.ExternalIndexName}"); + } + + if (!(index is IUmbracoIndex umbracoIndex)) + { + throw new InvalidOperationException("Could not cast"); + } + + // Do stuff with the index + if (_umbracoContextAccessor.TryGetUmbracoContext(out var umbracoContext) is false) + { + throw new InvalidOperationException("Could not get Umbraco context"); + } + + return umbracoIndex.Searcher.Search(searchTerm).ToPublishedSearchResults(umbracoContext.PublishedSnapshot.Content); + } + + // Do stuff with the index + if (_umbracoContextAccessor.TryGetUmbracoContext(out var umbracoContext) is false) + { + throw new InvalidOperationException("Could not get Umbraco context"); + } + + return umbracoIndex.Searcher.Search(searchTerm).ToPublishedSearchResults(umbracoContext.PublishedSnapshot.Content); + } +} +``` + +{% endcode %} + +### ILogger + +[Read more about logging in the debugging section](../fundamentals/code/debugging/logging.md) + +{% code title="Foobar.cs" %} + +```csharp +using System; +using Microsoft.Extensions.Logging; + +namespace IOCDocs.Services; + +public class Foobar : IFooBar +{ + private readonly ILogger _logger; + + public Foobar(ILogger logger) + { + _logger = logger; + } + + public void Foo() + { + _logger.LogInformation("Method Foo called at {DateTime}", DateTime.UtcNow); + } +} +``` + +{% endcode %} + +## Using DI in Services and Helpers + +In the [Services and Helpers documentation](../implementation/services/), you can find more examples of using dependency injection and gaining access to the different services and helpers. + +You will also find information about creating custom services and helpers to inject and use in your Umbraco project. diff --git a/16/umbraco-cms/reference/webhooks/README.md b/16/umbraco-cms/reference/webhooks/README.md new file mode 100644 index 00000000000..e6629910ac6 --- /dev/null +++ b/16/umbraco-cms/reference/webhooks/README.md @@ -0,0 +1,224 @@ +--- +description: Umbraco webhooks enable seamless integration and real-time updates by notifying external services about content changes and events within the Umbraco CMS +--- + +# Webhooks + +Webhooks provide real-time, event-driven communication within Umbraco. They enable external services to react to content changes instantly by sending HTTP requests when specific events occur. This allows you to integrate with third-party services, automate workflows, and synchronize data effortlessly. + +## Getting Started + +To manage webhooks, navigate to **Settings > Webhooks** in the Umbraco backoffice. + +![Webhooks section](images/webhook-section-v14.png) + +To create a webhook, click **Create**. This opens the webhook creation screen where you can configure the necessary details. + +![Creating a webhook](images/create-webhook-v14.png) + +## Configuring a Webhook + +### URL + +The `Url` is the endpoint where the webhook will send an HTTP request when the selected event is triggered. Ensure this endpoint is publicly accessible and capable of handling incoming requests. + +### Events + +Webhooks are triggered by specific events in Umbraco. By default, the following events are available: + +| Event Name | Description | +|--------------------|--------------------------------------------------| +| Content Published | Fires when content is published. | +| Content Unpublished | Fires when content is unpublished. | +| Content Deleted | Fires when content is deleted. | +| Media Deleted | Fires when a media item is deleted. | +| Media Saved | Fires when a media item is saved. | + +### Content Type Filtering + +For **Content** or **Media** events, you can specify whether the webhook should trigger for all content types or only specific ones. This is useful when you only need webhooks for certain document types, such as blog posts or products. + +### Custom Headers + +You can define custom HTTP headers that will be included in the webhook request. Common use cases include: + +- Specifying request format: `Accept: application/json` +- Adding authentication tokens: `Authorization: Bearer ` +- Including security headers + +## Default Behavior of Umbraco Webhooks + +Umbraco webhooks come with predefined settings and behaviors. + +### JSON Payload + +Each webhook event sends a JSON payload. For example, the `Content Published` event includes full content details: + +```json +{ + "Name": "Root", + "CreateDate": "2023-12-11T12:02:38.9979314", + "UpdateDate": "2023-12-11T12:02:38.9979314", + "Route": { + "Path": "/", + "StartItem": { + "Id": "c1922956-7855-4fa0-8f2c-7af149a92135", + "Path": "root" + } + }, + "Id": "c1922956-7855-4fa0-8f2c-7af149a92135", + "ContentType": "root", + "Properties": {} +} +``` + +The `Content Deleted` event sends only the content ID: + +```json +{ + "Id": "c1922956-7855-4fa0-8f2c-7af149a92135" +} +``` + +### Default Headers + +Webhook requests include the following headers by default: + +| Header Name | Description | +|-------------|-------------| +| `user-agent: Umbraco-Cms/{version}` | Identifies the Umbraco version sending the webhook. | +| `umb-webhook-retrycount: {number}` | Indicates the retry count for a webhook request. | +| `umb-webhook-event: {event}` | Specifies the event that triggered the request. Example: `umb-webhook-event: Umbraco.ContentPublished`. | + +## Extending Webhooks + +### Adding Custom Events + +You can extend the list of webhook events using `IUmbracoBuilder` and `IComposer`. Here’s an example of how to add custom webhook events: + +```csharp +using Umbraco.Cms.Core.Composing; + +public class CustomWebhookComposer : IComposer +{ + public void Compose(IUmbracoBuilder builder) + { + builder.WebhookEvents() + .Clear() + .AddCms(cmsBuilder => + { + // Add custom events + cmsBuilder + .AddDefault() + .AddContent() + .AddContentType() + .AddDataType() + .AddDictionary() + .AddDomain() + .AddFile() + .AddHealthCheck() + .AddLanguage() + .AddMedia() + .AddMember() + .AddPackage() + .AddPublicAccess() + .AddRelation() + .AddRelationType() + .AddUser(); + }); + } +} +``` + +To enable all available events, use: + +```csharp +builder.WebhookEvents().Clear().AddCms(false); +``` + +### Replacing Webhook Events + +You can modify existing webhook events, such as changing the payload format, by creating a custom implementation: + +```csharp +[WebhookEvent("Content Published", Constants.WebhookEvents.Types.Content)] +public class MyCustomContentPublishedWebhookEvent : WebhookEventContentBase +{ + private readonly IPublishedSnapshotAccessor _publishedSnapshotAccessor; + private readonly IApiContentBuilder _apiContentBuilder; + + public MyCustomContentPublishedWebhookEvent( + IWebhookFiringService webhookFiringService, + IWebhookService webhookService, + IOptionsMonitor webhookSettings, + IServerRoleAccessor serverRoleAccessor, + IPublishedSnapshotAccessor publishedSnapshotAccessor, + IApiContentBuilder apiContentBuilder) + : base(webhookFiringService, webhookService, webhookSettings, serverRoleAccessor) + { + _publishedSnapshotAccessor = publishedSnapshotAccessor; + _apiContentBuilder = apiContentBuilder; + } + + public override string Alias => "Umbraco.ContentPublish"; + protected override IEnumerable GetEntitiesFromNotification(ContentPublishedNotification notification) => notification.PublishedEntities; + + protected override object? ConvertEntityToRequestPayload(IContent entity) + { + if (_publishedSnapshotAccessor.TryGetPublishedSnapshot(out IPublishedSnapshot? publishedSnapshot) is false || publishedSnapshot!.Content is null) + { + return null; + } + + IPublishedContent? publishedContent = publishedSnapshot.Content.GetById(entity.Key); + + return new + { + CustomData = "Your data", + PublishedContent = publishedContent is null ? null : _apiContentBuilder.Build(publishedContent) + }; + } +} +``` + +To replace the default Umbraco webhook with your custom implementation: + +```csharp +public class MyComposer : IComposer +{ + public void Compose(IUmbracoBuilder builder) + { + builder.WebhookEvents().Replace(); + } +} +``` + +## Webhook Settings + +Webhook settings are configured in `appsettings.*.json` under `Umbraco::CMS`: + +```json +"Umbraco": { + "CMS": { + "Webhook": { + "Enabled": true, + "MaximumRetries": 5, + "Period": "00:00:10", + "EnableLoggingCleanup": true, + "KeepLogsForDays": 30 + } + } +} +``` + +| Setting | Description | +|---------|-------------| +| `Enabled` | Enables or disables webhooks. | +| `MaximumRetries` | Sets the maximum number of retry attempts. | +| `Period` | Defines the retry interval. | +| `EnableLoggingCleanup` | Enables automatic cleanup of logs. | +| `KeepLogsForDays` | Determines how long webhook logs are retained. | + +## Testing Webhooks + +Use [Beeceptor](https://beeceptor.com/) or [RequestBin](https://pipedream.com/requestbin) to test your event trigger integrations before deploying them to production. diff --git a/16/umbraco-cms/reference/webhooks/expanding-webhook-events.md b/16/umbraco-cms/reference/webhooks/expanding-webhook-events.md new file mode 100644 index 00000000000..9292a82db81 --- /dev/null +++ b/16/umbraco-cms/reference/webhooks/expanding-webhook-events.md @@ -0,0 +1,185 @@ +--- +description: Explore new webhook event options, detailed setup, specific content triggers, and improved logging and retry mechanisms +--- + +# Creating Own Webhook Events + +## Introduction + +With Umbraco, you can create your own webhook events. + +This documentation guides you through the process of implementing your webhook events using the `WebhookEventBase` base class. + +## Creating an Event with the WebhookEventBase + +The `WebhookEventBase` class serves as the foundation for creating custom webhook events. Here's a brief overview of its key components: + +- **Alias**: The property that must be overridden to provide a unique identifier for your webhook event. +- **EventName**: A property that represents the name of the event. It is automatically set based on the provided alias unless explicitly specified. +- **EventType**: A property that categorizes the event type. It defaults to "Others" but can be customized using the `WebhookEventAttribute`. +- **WebhookSettings**: The property containing the current webhook settings. +- **ProcessWebhooks**: The method responsible for processing webhooks for a given notification. +- **ShouldFireWebhookForNotification**: The method determining whether webhooks should be fired for a specific notification. +- **ConvertNotificationToRequestPayload**: An optional method allowing customization of the notification payload before sending it to webhooks. + +### Creating a Custom Webhook Event + +To create a custom webhook event, follow these steps: + +1. **Derive from `WebhookEventBase`**: + + ```csharp + public class YourCustomEvent : WebhookEventBase + { + // Constructor and required overrides go here + } + ``` + +2. **Override the Alias Property**: + + Provide a unique identifier for your event using the `Alias` property: + + ```csharp + public override string Alias => "YourUniqueAlias"; + ``` + +3. **Apply `WebhookEventAttribute` (Optional)**: + + You can use the `WebhookEventAttribute` to specify the event name and type. Apply this attribute to your custom event class: + + ```csharp + [WebhookEvent("Your Event Name", "YourEventType")] + public class YourCustomEvent : WebhookEventBase + { + // Constructor and required overrides go here + } + ``` + + Umbraco already has some types as constants, which you can find at `Constants.WebhookEvents.Types`. + If you do not specify this attribute, the event name will default to your alias, and the type will default to `Other`. + +4. **Implement Notification Handling**: + + If needed, customize the handling of the notification in the `HandleAsync` method. + +5. **Register Your Webhook Event**: + + Ensure that Umbraco is aware of your custom event by registering it in a composer: + + ```csharp + using Umbraco.Cms.Core.Composing; + + public class CustomWebhookComposer : IComposer + { + public void Compose(IUmbracoBuilder builder) + { + builder.WebhookEvents().Add(); + } + } + ``` + +6. **Implement Optional Overrides**: + Depending on your requirements, you can override methods such as `ConvertNotificationToRequestPayload` and `ShouldFireWebhookForNotification` to customize the behavior of your webhook event. + +### Sample Implementation + +Here's a basic example of a custom webhook event: + +```csharp +[WebhookEvent("Your Custom Event", "CustomEventType")] +public class YourCustomEvent : WebhookEventBase +{ + public YourCustomEvent(IWebhookFiringService webhookFiringService, IWebhookService webhookService, IOptionsMonitor webhookSettings, IServerRoleAccessor serverRoleAccessor) + : base(webhookFiringService, webhookService, webhookSettings, serverRoleAccessor) + { + } + + public override string Alias => "YourCustomAlias"; + + // Additional optional overrides + public override object? ConvertNotificationToRequestPayload(YourNotificationType notification) + { + // Custom conversion logic + } + + public override async Task ProcessWebhooks(YourNotificationType notification, IEnumerable webhooks, CancellationToken cancellationToken) + { + // Custom processing of webhook logic + } + + public override bool ShouldFireWebhookForNotification(YourNotificationType notificationObject) + { + // Custom logic for figuring out if the webhook should fire for a given notification. + } +} +``` + +## Creating an Event with the WebhookEventContentBase + +For scenarios where your webhook event is content-specific, Umbraco provides another base class: `WebhookEventContentBase`. This class is an extension of the generic `WebhookEventBase` and introduces content-related functionalities. + +The `WebhookEventContentBase` class is designed for content-specific webhook events, where `TEntity` is expected to be a type that implements the `IContentBase` interface. + +### Usage + +To leverage the `WebhookEventContentBase` class, follow these steps: + +1. **Derive from `WebhookEventContentBase`**: + + ```csharp + public class YourContentWebhookEvent : WebhookEventContentBase + { + } + ``` + +2. **Override the Required Methods**: + + - **GetEntitiesFromNotification**: Implement this method to extract content entities from the notification. + + - **ConvertEntityToRequestPayload**: Implement this method to customize the content entity payload before sending it to webhooks. + + If we take a look at the `ContentPublishedWebhookEvent`, we can see how these methods are overriden. + +{% include "../../.gitbook/includes/obsolete-warning-ipublishedsnapshotaccessor.md" %} + + ```csharp + protected override IEnumerable GetEntitiesFromNotification(ContentPublishedNotification notification) => notification.PublishedEntities; + + protected override object? ConvertEntityToRequestPayload(IContent entity) + { + if (_publishedSnapshotAccessor.TryGetPublishedSnapshot(out IPublishedSnapshot? publishedSnapshot) is false || publishedSnapshot!.Content is null) + { + return null; + } + + IPublishedContent? publishedContent = publishedSnapshot.Content.GetById(entity.Key); + return publishedContent is null ? null : _apiContentBuilder.Build(publishedContent); + } + ``` + +3. **ProcessWebhooks Implementation**: + + The `ProcessWebhooks` method in this class has been enhanced to iterate through content entities obtained from the notification. It checks the content type of each entity against the specified webhook's content type keys, firing webhooks only for matching entities. + + ```csharp + public override async Task ProcessWebhooks(TNotification notification, IEnumerable webhooks, CancellationToken cancellationToken) + { + foreach (IWebhook webhook in webhooks) + { + if (!webhook.Enabled) + { + continue; + } + + foreach (TEntity entity in GetEntitiesFromNotification(notification)) + { + if (webhook.ContentTypeKeys.Any() && !webhook.ContentTypeKeys.Contains(entity.ContentType.Key)) + { + continue; + } + + await WebhookFiringService.FireAsync(webhook, Alias, ConvertEntityToRequestPayload(entity), cancellationToken); + } + } + } + ``` diff --git a/16/umbraco-cms/reference/webhooks/images/create-webhook-v14.png b/16/umbraco-cms/reference/webhooks/images/create-webhook-v14.png new file mode 100644 index 00000000000..6d902061380 Binary files /dev/null and b/16/umbraco-cms/reference/webhooks/images/create-webhook-v14.png differ diff --git a/16/umbraco-cms/reference/webhooks/images/create-webhook.png b/16/umbraco-cms/reference/webhooks/images/create-webhook.png new file mode 100644 index 00000000000..cc440dea37c Binary files /dev/null and b/16/umbraco-cms/reference/webhooks/images/create-webhook.png differ diff --git a/16/umbraco-cms/reference/webhooks/images/webhook-section-v14.png b/16/umbraco-cms/reference/webhooks/images/webhook-section-v14.png new file mode 100644 index 00000000000..1bf4ebe5873 Binary files /dev/null and b/16/umbraco-cms/reference/webhooks/images/webhook-section-v14.png differ diff --git a/16/umbraco-cms/reference/webhooks/images/webhook-section.png b/16/umbraco-cms/reference/webhooks/images/webhook-section.png new file mode 100644 index 00000000000..533780ac101 Binary files /dev/null and b/16/umbraco-cms/reference/webhooks/images/webhook-section.png differ diff --git a/16/umbraco-cms/release-candidate-guide.md b/16/umbraco-cms/release-candidate-guide.md new file mode 100644 index 00000000000..fa2551781a5 --- /dev/null +++ b/16/umbraco-cms/release-candidate-guide.md @@ -0,0 +1,95 @@ +--- +description: >- + Learn how to start testing the Release Candidate for the latest version of + Umbraco CMS, and find information about new and updated documentation. +hidden: true +--- + +# Release Candidate Guide + +## How to test the Release Candidate + +{% hint style="warning" %} +To install the Umbraco 15 Release Candidate you need the latest [.NET SDK 9.0.0](https://dotnet.microsoft.com/en-us/download/dotnet/9.0). +{% endhint %} + +The[ release candidate is available on NuGet](https://www.nuget.org/packages/Umbraco.Cms/15.0.0-rc2). + +1. Install the Umbraco dotnet template for the Release Candidate. + +``` +dotnet new install Umbraco.Templates::15.0.0-rc* +``` + +2. Create a new Umbraco project. + +``` +dotnet new umbraco -n MyCustomUmbracoProject +``` + +3. Navigate to the newly created folder. + +``` +cd MyCustomUmbracoProject +``` + +4. Build the project. + +``` +dotnet build +``` + +5. Run the project. + +``` +dotnet run +``` + +This will boot the project, and write the log to the console. The website is now running on your local machine and will be available on the ports written in the console. + +{% hint style="info" %} +Alternatively, you can install and run the Umbraco project using your favorite IDE (Integrated Development Environment). +{% endhint %} + +### What should you focus on when testing? + +Read the [Release Candidate blog post](https://umbraco.com/blog/umbraco-15-release-candidate/) to learn more about notable features and changes added to the upcoming version. + +The blog post will mention if there are any specific features or workflows that the Umbraco HQ team needs feedback on. + +## New and updated documentation + +Here is a list of all the articles that are new to this version or have been updated. + +### New articles + +* [Tutorial: Extending the Help Menu](tutorials/extending-the-help-menu.md) +* [Running Umbraco in Docker using Docker Compose](fundamentals/setup/install/running-umbraco-on-docker-locally.md) +* [Creating a Custom Seed Key Provider](extending/creating-custom-seed-key-provider.md) +* [Cache Settings](reference/configuration/cache-settings.md) +* [Cache Seeding](reference/cache/cache-seeding.md) +* [API Users](fundamentals/data/users/api-users.md) +* [External Access](reference/management-api/external-access.md) +* New UI for the Rich Text Editor: Tiptap + * [Rich Text Editor](fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor/) + * [Configuration](fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor/configuration.md) + * [Blocks](fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor/blocks.md) + * [Change Rich Text Editor UI](fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/rich-text-editor/change-rich-text-editor-ui.md) + +### Updated articles + +* Changes made based on the removal of the UmbracoAPIController + * [Common Pitfalls: Static references to scoped references](reference/common-pitfalls.md#static-references-to-scoped-instances-such-as-umbracohelper) + * [Creating a custom database table](extending/database.md) + * [Image Cropper](fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/image-cropper.md) + * [UmbracoMapper](reference/mapping.md) + * [Depencency Injection / IoC](reference/using-ioc.md) + * [Working with Caching: Tags example](reference/cache/examples/tags.md) + * [Unit Testing](implementation/unit-testing.md) + * [Querying: ITagQuery](reference/querying/itagquery.md) + * [UmbracoContext helper](reference/querying/umbraco-context.md) + * [Block Grid](fundamentals/backoffice/property-editors/built-in-umbraco-property-editors/block-editor/block-grid-editor.md) +* Replacing the deprecated GetAll() method + * [Working with Caching: Tags example](reference/cache/examples/tags.md) + * [Using Services: ContentTypeService](reference/management/using-services/contenttypeservice.md) + * [Request Pipeline: IContentFinder](reference/routing/request-pipeline/icontentfinder.md) diff --git a/16/umbraco-cms/tutorials/add-google-authentication.md b/16/umbraco-cms/tutorials/add-google-authentication.md new file mode 100644 index 00000000000..4d87aae4c6d --- /dev/null +++ b/16/umbraco-cms/tutorials/add-google-authentication.md @@ -0,0 +1,363 @@ +--- +description: >- + A tutorial on setting up Google authentication for the Umbraco CMS backoffice + users. +--- + +# Add Google Authentication (Users) + +The Umbraco Backoffice supports external login providers (OAuth) for performing authentication of your users. This could be any OpenIDConnect provider such as Entra ID/Azure Active Directory, Identity Server, Google, or Facebook. + +In this tutorial, we will take you through the steps of setting up a Google login for the Umbraco CMS backoffice. + +## What is a Google Login? + +When you log in to the Umbraco Backoffice, you need to enter your username and password. Integrating your website with Google authentication adds a button that you can click to log in with your Google account. + +![Google login screen](images/googleLoginScreen.jpg) + +## Why? + +We are sure a lot of content editors and implementors of your Umbraco sites would love to have one less password to remember. Click **Sign in with Google** and if you are already logged in with your Google account, it will log you in directly. + +### What the tutorial covers + +1. [Setting up a Google OAuth API](add-google-authentication.md#id-1.-setting-up-a-google-oauth-api) +2. [Integrating Google Auth in in your project](add-google-authentication.md#id-2.-integrating-google-auth-in-your-project) +3. [Configuring the solution to allow Google logins](add-google-authentication.md#id-3.-configuring-the-solution-to-allow-google-logins) + +### Prerequisites + +For this tutorial, you need: + +* [Visual Studio](https://visualstudio.microsoft.com/) installed. +* A [Google](https://myaccount.google.com/) account. +* A working [Umbraco solution](https://umbraco.com/products/umbraco-cloud/). + +## 1. Setting up a Google OAuth API + +The first thing to do is set up a Google API. To do this, you need to go to [https://console.developers.google.com/](https://console.developers.google.com/) and log in with your Google account. + +### Setup a Google Console Project + +1. Click the project dropdown and select **New Project**. + + ![Project dropdown list](images/Project_dropdown_list_v13.png) +2. Enter a **Project name**, **Organization**, and **Location**. +3. Click **Create**. + +### Enable the Google+ API + +1. Open the newly created project from the project dropdown. +2. Click **Enable APIs and Services**. + + ![Enable APIs](images/Enable_Apis_v13.png) +3. Type **Google+ API** in the **Search** field. +4. Select it and then **Enable** it. + + ![Enable Google APIs](images/Enable_Google_API_v13.png) + +### Set up an OAuth Consent Screen + +Before you can create the credentials, you need to configure your consent screen. + +1. Click **OAuth consent screen** from the left-side navigation menu. +2. Choose the **User Type** that fits your setup. +3. Click **Create**. + + ![Select User Type](images/User_Type_v13.png) +4. Fill in the required information: + * App name + * User support email + * Developer contact information +5. Click **Save and Continue**. +6. Select the scopes your project needs. +7. Click **Save and Continue**. +8. Verify the details you have provided. +9. Click **Back to Dashboard** to complete creating the Consent screen. + +### Create credentials + +1. Click **Credentials** from the left-side navigation menu. +2. Click **Create Credentials**. +3. Select **OAuth Client ID** from the dropdown. + + ![Select OAuth Client ID](images/OAuth_Client_Id_v13.png) +4. Select **Web Application** from the **Application type** dropdown. +5. Enter the following details: + + * Application **Name** + * **Authorized JavaScript origins** + * **Authorized redirect URIs** + + ![Credentials](images/credentials_v13.png) +6. Click **Create**. + +A popup appears displaying the **Client Id** and **Client Secret**. You will need these values later while configuring your solution. + +{% hint style="info" %} +The **Client Id** and **Client Secret** can always be accessed from the **Credentials** tab in the **APIs & Services** menu. +{% endhint %} + +## 2. Integrating Google Auth in your project + +Once the Google API is set up it is time to install the Google Auth provider on the Umbraco project. + +If you are working with a Cloud project, see the [Working locally](https://docs.umbraco.com/umbraco-cloud/set-up/working-locally) article to complete this step. + +### Installing a Nuget Package + +You can install and manage packages in a project. + +1. Navigate to your project/solution folder. + +{% hint style="info" %} +If you have cloned down an Umbraco Project, you will need to navigate to the `src` folder where you can see a `.csproj` file. +{% endhint %} + +2. Open a command-line of your choice such as "Command Prompt" at the mentioned location. +3. Run the following command to install the `Microsoft.AspNetCore.Authentication.Google` package. + + ```cli + dotnet add package Microsoft.AspNetCore.Authentication.Google + ``` +4. Once the package is installed, open the **.csproj** file to ensure if the package reference is added: + + ```js + + + + ``` + +{% hint style="info" %} +You can check the [latest version of the package](https://www.nuget.org/packages/Microsoft.AspNetCore.Authentication.Google) before installing it. +{% endhint %} + +For more information on installing and using a package with the .Net CLI, see [Microsoft Documentation](https://learn.microsoft.com/en-us/nuget/quickstart/install-and-use-a-package-using-the-dotnet-cli). + +## 3. Configuring the Solution to allow Google Logins + +To use an external login provider such as Google on your Umbraco CMS project, you have to implement a couple of new classes: + +* A custom-named `BackOfficeExternalLoginProviderOptions` configuration class. +* A custom-named `GoogleOptions` configuration class. +* A Composer to tie it all together. +* An Umbraco backoffice manifest declaration. + +You can create these files in a location of your choice. In this tutorial, the files will be added to an `ExternalUserLogin/GoogleAuthentication` folder for the C# classes. You will also need an `\App_Plugins\my-auth-providers` folder location for the frontend registration. + +1. Create a new class:`GoogleBackOfficeExternalLoginProviderOptions.cs`. +2. Add the following code to the file: + +{% code title="GoogleBackOfficeExternalLoginProviderOptions.cs" lineNumbers="true" %} +```csharp +using Microsoft.Extensions.Options; +using Umbraco.Cms.Api.Management.Security; +using Umbraco.Cms.Core; + +namespace MyCustomUmbracoProject.ExternalUserLogin.GoogleAuthentication; + +public class GoogleBackOfficeExternalLoginProviderOptions : IConfigureNamedOptions +{ + public const string SchemeName = "Google"; + + public void Configure(string? name, BackOfficeExternalLoginProviderOptions options) + { + if (name != Constants.Security.BackOfficeExternalAuthenticationTypePrefix + SchemeName) + { + return; + } + + Configure(options); + } + + public void Configure(BackOfficeExternalLoginProviderOptions options) + { + options.AutoLinkOptions = new ExternalSignInAutoLinkOptions( + // must be true for auto-linking to be enabled + autoLinkExternalAccount: true, + + // Optionally specify default user group, else + // assign in the OnAutoLinking callback + // (default is editor) + defaultUserGroups: new[] { Constants.Security.EditorGroupAlias }, + + // Optionally specify the default culture to create + // the user as. If null it will use the default + // culture defined in the web.config, or it can + // be dynamically assigned in the OnAutoLinking + // callback. + defaultCulture: null, + // Optionally you can disable the ability to link/unlink + // manually from within the back office. Set this to false + // if you don't want the user to unlink from this external + // provider. + allowManualLinking: true + ) + { + // Optional callback + OnAutoLinking = (autoLinkUser, loginInfo) => + { + // You can customize the user before it's linked. + // i.e. Modify the user's groups based on the Claims returned + // in the externalLogin info + + // see https://github.com/umbraco/Umbraco-CMS/issues/12487 + autoLinkUser.IsApproved = true; + }, + OnExternalLogin = (user, loginInfo) => + { + // You can customize the user before it's saved whenever they have + // logged in with the external provider. + // That is to sync the user's name based on the Claims returned + // in the externalLogin info + + return true; //returns a boolean indicating if sign-in should continue or not. + }, + }; + } +} +``` +{% endcode %} + +{% hint style="info" %} +The code used here, enables [auto-linking](../reference/security/external-login-providers.md#auto-linking) with the external login provider. This enables the option for users to login to the Umbraco backoffice prior to having a backoffice User. + +Set the `autoLinkExternalAccount` to `false` in order to disable auto-linking in your implementation. +{% endhint %} + +3. Create a new class: `GoogleBackOfficeAuthenticationOptions`. +4. Add the following code to the file: + +{% code title="GoogleBackOfficeAuthenticationOptions.cs" lineNumbers="true" %} +```csharp +using Microsoft.AspNetCore.Authentication.Google; +using Microsoft.Extensions.Options; +using Umbraco.Cms.Api.Management.Security; +using Umbraco.Cms.Web.Common.Helpers; +using Umbraco.Cms.Web.UI.Custom; + +namespace MyCustomUmbracoProject.ExternalUserLogin.GoogleAuthentication; + +public class GoogleBackOfficeAuthenticationOptions : IConfigureNamedOptions +{ + private readonly OAuthOptionsHelper _helper; + + public GoogleBackOfficeAuthenticationOptions(OAuthOptionsHelper helper) + { + _helper = helper; + } + + public void Configure(GoogleOptions options) + { + // since we have access to dependency injection, these values can be read from the app settings using the IOptions pattern + options.CallbackPath = "/umbraco-google-signin"; // can be anything as middleware will add this to the route table + options.ClientId = "your client id for the google login provider"; + options.ClientSecret = "your client secret for the google login provider"; + options.Scope.Add("https://www.googleapis.com/auth/userinfo.email"); // email is needed for auto linking purposes + + // This will redirect error responses from the login provider towards the default umbraco oath login error page + // which will try to display the error state in a meaningful way. + // You can implement your own error handling by handling options.Events.OnAccessDenied & options.Events.OnRemoteFailure + _helper.SetDefaultErrorEventHandling(options, GoogleBackOfficeExternalLoginProviderOptions.SchemeName); + } + + public void Configure(string? name, GoogleOptions options) + { + // only configure the options if it is for the backend + if (name == BackOfficeAuthenticationBuilder.SchemeForBackOffice(GoogleBackOfficeExternalLoginProviderOptions + .SchemeName)) + { + Configure(options); + } + } +} + +``` +{% endcode %} + +5. Replace **YOURCLIENTID** and **YOURCLIENTSECRET** with the values from the **OAuth Client Ids Credentials** window. Or use the [IOptions pattern](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/configuration/options) to read the values from app settings (or other sources). +6. Register both `ConfigureNameOptions` into a composer and add the provider to Umbraco + +{% code title="GoogleBackOfficeExternalLoginComposer.cs" lineNumbers="true" %} +```csharp +using Umbraco.Cms.Api.Management.Security; +using Umbraco.Cms.Core.Composing; +using Umbraco.Cms.Web.UI.Custom; + +namespace MyCustomUmbracoProject.ExternalUserLogin.GoogleAuthentication; + +public class GoogleBackOfficeExternalLoginComposer : IComposer +{ + public void Compose(IUmbracoBuilder builder) + { + builder.Services.ConfigureOptions(); + builder.Services.ConfigureOptions(); + + builder.AddBackOfficeExternalLogins(logins => + { + logins.AddBackOfficeLogin( + backOfficeAuthenticationBuilder => + { + // this Add... method will be part of the OathProvider nuget package you install + backOfficeAuthenticationBuilder.AddGoogle( + BackOfficeAuthenticationBuilder.SchemeForBackOffice( + GoogleBackOfficeExternalLoginProviderOptions + .SchemeName)!, + options => + { + // need to give an empty action here for the options pattern configuration to work + // if you do not wish to use the umbraco default error handling and hardcode all your values instead of injecting them, + // you can set the configuration right here instead. You can then remove the `GoogleBackOfficeAuthenticationOptions` class + }); + }); + }); + } +} +``` +{% endcode %} + +7. Register the provider with the backoffice client by adding the following file to the manifest file in `/App_Plugins/my-auth-providers/umbraco-package.json`: + +{% code title="/App_Plugins/my-auth-providers/umbraco-package.json" lineNumbers="true" %} +```json +{ + "$schema": "../../umbraco-package-schema.json", + "name": "My Auth Package", + "allowPublicAccess": true, + "extensions": [ + { + "type": "authProvider", + "alias": "My.AuthProvider.Google", + "name": "My Google Auth Provider", + "forProviderName": "Umbraco.Google", + "meta": { + "label": "Login with Google" + } + } + ] +} + +``` +{% endcode %} + +8. Build and run the website. +9. Log in to the backoffice using the Google Authentication option. + +{% hint style="info" %} +If auto-linking is disabled, the user will need to follow these steps in order to be able to use Google Authentication: + +1. Login to the backoffice using Umbraco credentials. +2. Select your user profile in the top-right corner. +3. Click **Link your Google account** under External login providers. +4. Choose the account you wish to link. + +For future backoffice logins, the user will be able to use Google Authentication. +{% endhint %} + +![Google login screen](images/googleLoginScreen.jpg) + +## Related Links + +* [External login providers](../reference/security/external-login-providers.md) +* [Linking External Login Provider accounts](../reference/security/external-login-providers.md#auto-linking) diff --git a/16/umbraco-cms/tutorials/add-microsoft-entra-id-authentication.md b/16/umbraco-cms/tutorials/add-microsoft-entra-id-authentication.md new file mode 100644 index 00000000000..7e333357c48 --- /dev/null +++ b/16/umbraco-cms/tutorials/add-microsoft-entra-id-authentication.md @@ -0,0 +1,198 @@ +--- +description: >- + Learn how to use Microsoft Entra ID (Azure Active Directory) credentials to login to Umbraco as a + member. +--- + +# Add Microsoft Entra ID (Azure Active Directory) authentication (Members) + +This tutorial takes you through configuring Microsoft Entra ID (Azure Active Directory/Azure AD) for the member login on your Umbraco CMS website. + +{% hint style="warning" %} +Entra ID conflicts with Umbraco ID which is the main authentication method used on all Umbraco Cloud projects. + +Due to this, we **highly recommend not using Azure AD for backoffice authentication on your Umbraco Cloud projects**. + +It is still possible to use other [External Login Providers](../reference/security/external-login-providers.md) like Google Auth and OpenIdConnect, with Umbraco Cloud. +{% endhint %} + +## Prerequisites + +* A project with a setup for Members. +* Visual Studio, or another Integrated Development Environment (IDE). + +## Step 1: Configure Entra ID + +Before your applications can interact with Entra ID, they must be registered with a tenant that you manage. This can be either an Entra ID (Azure AD) tenant, or an Entra ID B2C (Azure AD B2C) tenant. For more information on creating an Azure AD B2C tenant, see [Microsoft's Tutorial: Create an Azure Active Directory B2C tenant](https://learn.microsoft.com/en-us/azure/active-directory-b2c/tutorial-create-tenant). + +## Step 2: Install the NuGet package + +You need to install the `Microsoft.AspNetCore.Authentication.MicrosoftAccount` NuGet package. There are two approaches to installing the packages: + +1. Use your favorite Integrated Development Environment (IDE) and open up the **NuGet Package Manager** to search and install the packages. +2. Use the command line to install the package. + +## Step 3: Implement the Entra ID Authentication + +1. Create a new class for custom configuration options: `EntraIDB2CMembersExternalLoginProviderOptions.cs`. + +{% code title="EntraIDB2CMembersExternalLoginProviderOptions.cs" lineNumbers="true" %} + +```csharp +using Microsoft.Extensions.Options; +using Umbraco.Cms.Core; +using Umbraco.Cms.Web.Common.Security; + +namespace MyApp; + +public class EntraIDB2CMembersExternalLoginProviderOptions : IConfigureNamedOptions +{ + public const string SchemeName = "ActiveDirectoryB2C"; + + public void Configure(string? name, MemberExternalLoginProviderOptions options) + { + if (name != Constants.Security.MemberExternalAuthenticationTypePrefix + SchemeName) + { + return; + } + + Configure(options); + } + + public void Configure(MemberExternalLoginProviderOptions options) + { + // The following options are relevant if you + // want to configure auto-linking on the authentication. + options.AutoLinkOptions = new MemberExternalSignInAutoLinkOptions( + + // Set to true to enable auto-linking + autoLinkExternalAccount: true, + + // [OPTIONAL] + // Default: The culture specified in appsettings.json. + // Specify the default culture to create the Member as. + // It can be dynamically assigned in the OnAutoLinking callback. + defaultCulture: null, + + // [OPTIONAL] + // Specify the default "IsApproved" status. + // Must be true for auto-linking. + defaultIsApproved: true, + + // [OPTIONAL] + // Default: "Member" + // Specify the Member Type alias. + defaultMemberTypeAlias: Constants.Security.DefaultMemberTypeAlias + + ) + { + // [OPTIONAL] Callbacks + OnAutoLinking = (autoLinkUser, loginInfo) => + { + // Customize the Member before it's linked. + // Modify the Members groups based on the Claims returned + // in the external login info. + }, + OnExternalLogin = (user, loginInfo) => + { + // Customize the Member before it is saved whenever they have + // logged in with the external provider. + // Sync the Members name based on the Claims returned + // in the external login info + + // Returns a boolean indicating if sign-in should continue or not. + return true; + } + }; + } +} +``` + +{% endcode %} + +2. Create a new static extension class called `MemberAuthenticationExtensions.cs`. + +{% code title="MemberAuthenticationExtensions.cs" lineNumbers="true" %} + +```csharp +namespace MyApp; + +public static class MemberAuthenticationExtensions +{ + public static IUmbracoBuilder ConfigureAuthenticationMembers(this IUmbracoBuilder builder) + { + builder.Services.ConfigureOptions(); + builder.AddMemberExternalLogins(logins => + { + builder.Services.ConfigureOptions(); + builder.AddMemberExternalLogins(logins => + { + logins.AddMemberLogin( + membersAuthenticationBuilder => + { + membersAuthenticationBuilder.AddMicrosoftAccount( + + // The scheme must be set with this method to work for the external login. + membersAuthenticationBuilder.SchemeForMembers(EntraIDB2CMembersExternalLoginProviderOptions.SchemeName), + options => + { + // Callbackpath: Represents the URL to which the browser should be redirected to. + // The default value is /signin-oidc. + // This needs to be unique. + options.CallbackPath = "/umbraco-b2c-members-signin"; + + //Obtained from the ENTRA ID B2C WEB APP + options.ClientId = "YOURCLIENTID"; + //Obtained from the ENTRA ID B2C WEB APP + options.ClientSecret = "YOURCLIENTSECRET"; + + // If you are using single-tenant app registration (e.g. for an intranet site), you must specify the Token Endpoint and Authorization Endpoint: + //options.TokenEndpoint = $"https://login.microsoftonline.com/{tenantId}/oauth2/v2.0/token"; + //options.AuthorizationEndpoint = $"https://login.microsoftonline.com/{tenantId}/oauth2/v2.0/authorize"; + + options.SaveTokens = true; + }); + }); + }); + }); + return builder; + }); + } +} +``` + +{% endcode %} + +{% hint style="info" %} +Ensure to replace `YOURCLIENTID` and `YOURCLIENTSECRET` in the code with the values from the Entra ID tenant. If Entra ID is configured to use accounts in the organizational directory only (single tenant registration), you must specify the Token and Authorization endpoint. For more information on the differences between single and multi tenant registration, refer to [Microsoft's identity platform documentation](https://learn.microsoft.com/en-us/entra/identity-platform/howto-modify-supported-accounts). +{% endhint %} + +4. Add the Members authentication configuration in the `Program.cs` file: + +{% code title="Program.cs" lineNumbers="true" %} + +```csharp +builder.CreateUmbracoBuilder() + .AddBackOffice() + .AddWebsite() + .AddDeliveryApi() + .AddComposers() + //Add Members ConfigureAuthentication + .ConfigureAuthenticationMembers() + .Build(); +``` + +{% endcode %} + +{% hint style="info" %} +Are you building a package for Umbraco? + +Then you will not have access to the `Program.cs` file. Instead you need to create a composer in order to register your extension method. + +Learn more about this in the [Dependency Injection](../reference/using-ioc.md) article. +{% endhint %} + +5. Build the project. +6. Run the website. + +![Entra ID Login Screen](<../../../10/umbraco-cms/reference/security/images/AD\_Login\_Members (1).png>) diff --git a/16/umbraco-cms/tutorials/connecting-umbraco-forms-and-zapier.md b/16/umbraco-cms/tutorials/connecting-umbraco-forms-and-zapier.md new file mode 100644 index 00000000000..308c87f4fea --- /dev/null +++ b/16/umbraco-cms/tutorials/connecting-umbraco-forms-and-zapier.md @@ -0,0 +1,154 @@ +# Connecting Umbraco Forms and Zapier + +This guide takes you through the steps of connecting your Umbraco Forms to Zapier. + +## What is Zapier + +[Zapier](https://zapier.com/) is an “if this, then that” tool that allows you to automate workflows between the different web apps you use. Zapier has integrations to more than 2,000 web apps and lets you connect to your accounts with a few clicks without any code. + +## Why use Zapier for your Umbraco Forms data + +Umbraco Forms stores entries in the backoffice. It has a set of default workflow types that you can use when a new form entry is submitted. To use this data in other applications, such as your Customer Relationship Management (CRM) or marketing automation platform, you need integrations to those platforms. Integration with Zapier can be done using the default workflows in Umbraco Forms. All without having to write any additional code. + +This enables marketers and editors to make automated workflows that pass data between the web apps they use without having to involve a developer. + +## Who is this tutorial for + +This tutorial is for all users of Umbraco and does not require any particular skills to be performed. It is especially useful for marketers who get the freedom to make integrations and automate tasks in a simpler and faster way. + +## Prerequisites + +Here is what you will need for this tutorial: + +* A paid Zapier account (Premium apps are unavailable in the free plan) +* Umbraco Forms +* A Google account (only necessary if you want to follow the last example in the guide) + +## Creating a Zapier webhook trigger + +The first step is to generate the webhook URL that your Umbraco Forms has to send data to. This is done by logging into your Zapier account and clicking “Make a Zap” + +![Zapier make a zap](../../../10/umbraco-cms/tutorials/images/zapierMakeZap.png) + +The next thing to do is pick an app in the “When this happens…” box. This is your trigger and determines when your Zap will start. Select the “Webhooks by Zapier” app + +![Zapier find webhooks trigger](../../../10/umbraco-cms/tutorials/images/zapierFindWebhooksTrigger.png) + +Now select the “Catch Hook” trigger event and click continue. + +![Zapier webhooks catch hook](../../../10/umbraco-cms/tutorials/images/zapierWebhooksCatchHook.png) + +Now you will get a “Custom Webhook URL” that you will need for your Umbraco Forms. + +![Zapier custom webhook URL](../../../10/umbraco-cms/tutorials/images/zapierCustomWebhookURL.png) + +Copy this URL and have it ready for later. You will need it when you set up your Umbraco Forms workflow. Now we have to go into the Umbraco backoffice, but keep the Zap you created open. We will get back to it later to finish setup. + +## Creating your Umbraco form + +Now it’s time to login to the Umbraco backoffice so you can create your form. If you already have a form you want to connect you can skip to the next step. + +To create a form you can follow this tutorial with step-by-step instructions: [Creating a form in Umbraco Forms](https://docs.umbraco.com/umbraco-forms/editor/creating-a-form). + +Once you have created your form you are ready to set up the workflow. + +## Setting up the “Send form to URL” workflow + +Once you have set up your form, it is time to add the “Send form to URL” workflow to your form. This will allow you to post your data to the Zapier webhook URL you generated earlier. + +Go to your form in the backoffice and click on “configure workflow”. In the “On Submit” workflow you click “Add workflow”. + +![Umbraco Forms Add Workflow](../../../10/umbraco-cms/tutorials/images/umbracoFormsAddWorkflow.png) + +Now choose the workflow “Send form to URL”. + +![Umbraco Forms Send form to URL workflow](../../../10/umbraco-cms/tutorials/images/umbracoFormsSendFormToURLWorkflow.png) + +After giving the workflow a descriptive name, you paste in the webhook URL from Zapier in the “Url” field and choose POST in “Method”. Leave “Fields”, “User” and “Password” blank. + +![Umbraco Forms Send form to URL webhook](../../../10/umbraco-cms/tutorials/images/umbracoFormsSendFormToURLWebhook.png) + +Now your workflow is ready. Submit your changes and save your form. + +## Submit an entry to the form + +Now your form is ready to send data to Zapier and any entry submitted will be posted to the Zapier webhook URL. + +To set up field mapping and actions in Zapier your form needs an entry. If this is a new form, add it to a page and submit an entry ([guide to adding your form to a page](https://docs.umbraco.com/umbraco-forms/editor/creating-a-form)). + +Here is the form and the fields that were submitted for this tutorial. + +![Umbraco Forms Fields Submitted](../../../10/umbraco-cms/tutorials/images/umbracoFormsFieldsSubmitted.png) + +Once you have an entry in your form you are done in the Umbraco backoffice. Now it is time to go back to Zapier and finish setting up your automation. + +## Find your form entry data in Zapier + +In Zapier, open up the Zap you started setting up in the first step of this guide. In that Zap we are now ready to continue the setup of our webhook trigger. Start by clicking continue and get to the “Find Data” step. + +![Zapier Find Webhook Data](../../../10/umbraco-cms/tutorials/images/zapierFindWebhookData.png) + +Now click on “Test trigger”. If there is an entry to the form it should look like this. + +![Zapier Webhook Data Found](../../../10/umbraco-cms/tutorials/images/zapierWebhookDataFound.png) + +Now you have data to further map in your Zap and can continue choosing the action for the Zap. If Zapier does not find any data, try to submit a new form entry. + +After clicking “continue” you are now starting on the “Do this…” action of your Zap. + +## Choose an action for your Zap + +Up until now, the steps will be the same for everyone. Now that you have connected Umbraco Forms and Zapier it is up to you to decide where you want to send the data. + +In this tutorial you will get an example of how you can send your data to Google Sheets. This allows you to see how a finalized Zap looks like. + +## Example: Sending data to Google Sheets + +As a “Do this…” action you want to send data to Google Sheets. Before doing so in your Zap you need to ensure that you have created a Google Sheet to receive the data. You also need to connect your Zapier account to your Google Sheets account. + +Once you have done these steps it is time to finish the setup of the Zap. + +First thing to do is find the “Google Sheets” app in the “Do this…” action. + +![Zapier Google Sheets App](../../../10/umbraco-cms/tutorials/images/zapierGoogleSheetsApp.png) + +Now choose the Action event “Create a spreadsheet row” and continue. + +![Zapier Google Sheets Action Event](../../../10/umbraco-cms/tutorials/images/zapierGoogleSheetsActionEvent.png) + +Now you need to choose the Google Sheets account you want to connect to. If you have not set this up yet, you will be prompted to do so. Once connected you choose that account and click continue. + +![Zapier Google Sheets Account](../../../10/umbraco-cms/tutorials/images/zapierGoogleSheetsAccount.png) + +Now you can choose which Google Drive to use, find the spreadsheet and choose the worksheet that you want to send the data to. After doing so, you will get a list of possible fields that you can post your data to. + +![Zapier Google Sheets Possible Fields](../../../10/umbraco-cms/tutorials/images/zapierGoogleSheetsPossibleFields.png) + +The fields showing are all columns in your spreadsheet that have a name in row 1. To map the input data to the different fields in the spreadsheet follow these steps: + +* Select the “Type or insert…” field. +* Choose which data to put in the field. + +![Zapier Google Sheets Webhook Data](../../../10/umbraco-cms/tutorials/images/zapierGoogleSheetsWebhookData.png) + +The data that was caught by the webhook will all be dynamic and you will want to pick the fields here. That way, when a new entry comes in, the field will dynamically insert the data they submitted for that field. + +![Zapier Google Sheets Data Mapped](../../../10/umbraco-cms/tutorials/images/zapierGoogleSheetsDataMapped.png) + +Once you have mapped all of the fields it is time to click “continue” and test if the data is sent correctly to our spreadsheet. + +![Zapier Google Sheets Test](../../../10/umbraco-cms/tutorials/images/zapierGoogleSheetsTest.png) + +Click “Test & Continue” and wait for it to process. Once it is done, go to the spreadsheet you created and see if the data is there. + +![Google Sheets Data](../../../10/umbraco-cms/tutorials/images/googleSheetsData.png) + +Tada! Now your Zap is ready and can be activated to automatically add form entries to Google Sheets. To activate the Zap you go back to your Zap and change the toggle from “Off” to “On”. Now it will be waiting for new form entries and be ready to send them to Google Sheets. + +## Connecting multiple Umbraco Forms to Zapier + +If you want to connect multiple forms to Zapier you can follow the above steps to do so. Remember to add individual Zaps to each form if the data being sent is different or you want to use different actions for the data. + +You can reuse the same Zap and webhook URL if all data sent to Zapier is formatted in the same way. Otherwise errors will occur. + +That’s it. Now you are ready to connect Umbraco Forms to Zapier and can use it to send data to the tools you choose. diff --git a/16/umbraco-cms/tutorials/create-a-custom-maintenance-page.md b/16/umbraco-cms/tutorials/create-a-custom-maintenance-page.md new file mode 100644 index 00000000000..8d61d3ca970 --- /dev/null +++ b/16/umbraco-cms/tutorials/create-a-custom-maintenance-page.md @@ -0,0 +1,46 @@ +--- +description: >- + Learn how to make your site visitors aware of any ongoing maintenance on the + project. +--- + +# Create a custom maintenance page + +A maintenance page will be shown when an Umbraco project is running an upgrade. This prevents visitors from landing on an upgrade page or seeing content meant for project maintainers. + +![The default maintenance page making site visitors aware of the state of the site.](../../../10/umbraco-cms/tutorials/images/maintenancePage.png) + +## Customize the maintenance page + +The following guide will take you through the steps to customize and brand the default maintenance page. + +1. Go to the root of your Umbraco project files. +2. Create a new folder called `UmbracoWebsite`, and open it. +3. Add a new file called `maintenance.cshtml`. +4. Add your custom markup to the file. + +{% hint style="warning" %} +Keeping the Umbraco project in Upgrade mode for a longer time is not recommended. Most migrations can be executed while the website continues to work. +{% endhint %} + +## Disable the maintenance page + +As most upgrades can be done without the website having to restart or go down, the maintenance page can be disabled. + +1. Open the project `appSettings.json` file. +2. Add the following configuration: + +{% code title="appSettings.json" %} +```json +{ + "Umbraco": { + "CMS": { + "Global": { + "ShowMaintenancePageWhenInUpgradeState": false + } + } + } +} +``` +{% endcode %} + diff --git a/16/umbraco-cms/tutorials/creating-a-backoffice-api/README.md b/16/umbraco-cms/tutorials/creating-a-backoffice-api/README.md new file mode 100644 index 00000000000..f18ae2e40ed --- /dev/null +++ b/16/umbraco-cms/tutorials/creating-a-backoffice-api/README.md @@ -0,0 +1,386 @@ +# Creating a backoffice API + +In this article, you will learn how to create your own API in the Umbraco backoffice. This is a great way to extend the functionality of the Umbraco backoffice and create custom endpoints for your use. + +The end result for this article is to create a custom API called "My item API" in the Management API found at `/umbraco/swagger/`. + +{% hint style="info" %} +The Umbraco Backoffice API is also known as the Management API. Thus, a Backoffice API Controller is often referred to as a Management API Controller. +{% endhint %} + +## Creating the class + +To create a custom API, you need to create a class that inherits from `Umbraco.Cms.Web.BackOffice.Controllers.ManagementApiControllerBase`. + +The `ManagementApiControllerBase` serves as the foundation for your custom API class. It provides essential functionalities and utilities required for managing APIs within the Umbraco backoffice environment. + +We also use the `VersionedApiBackOfficeRoute` attribute to define the route for our API. This attribute takes a string parameter that defines the route for the API. This route will be appended to the base route for the backoffice API. + +{% code title="MyItemApiController.cs" %} +```csharp +[VersionedApiBackOfficeRoute("my/item")] +[ApiExplorerSettings(GroupName = "My item API")] +public class MyItemApiController : ManagementApiControllerBase +{ +} +``` +{% endcode %} + +## Retrieving all items + +Now that we have our class set up, we can add an action to get all items. We will use the `HttpGet` attribute to define the `HTTP` method and route for the action. + +The `AllItems` field is an in-memory list of items to simulate the use of a repository. We use the `skip` & `take` parameters here, so users of this endpoint can implement paging. We also use the `PagedViewModel` to return the given items (10 by default), and then the total number of items. + +{% code title="MyItemApiController.cs" %} +```csharp +[HttpGet] +public IActionResult GetAllItems(int skip = 0, int take = 10) + => Ok( + new PagedViewModel + { + Items = AllItems.Skip(skip).Take(take), + Total = AllItems.Count + } + ); +``` +{% endcode %} + +AllItems is a local list: + +{% code title="MyItemApiController.cs" %} +```csharp +private static readonly List AllItems = Enumerable.Range(1, 100) + .Select(i => new MyItem($"My Item #{i}")) + .ToList(); +``` +{% endcode %} + +The model for `MyItem` is a basic class with an `Id` and a `Value` property. + +{% code title="MyItem.cs" %} +```csharp +public class MyItem(string value) +{ + public Guid Id { get; } = Guid.NewGuid(); + + public string Value { get; set; } = value; +} +``` +{% endcode %} + +## Retrieving a single item + +We can now create some logic to return a response based on the `ID`. The route parameter `{id:guid}` specifies that the `id` parameter should be a `GUID`. Here we're creating a local in-memory list of items and returning the item with the matching `ID`. + +To note here is the use of the `OperationStatusResult` method. This method allows you to return a response with a status code and a body. This is useful for returning error responses with additional information. + +The method also needs an `enum` operationStatus, as it will be attached to the response. This is a basic example, however, this `OperationStatus` would be returned from your service layer, based on the error in the service layer method. + +{% code title="MyItemApiController.cs" %} +```csharp +[HttpGet("{id:guid}")] +public IActionResult GetItem(Guid id) +{ + MyItem? item = AllItems.FirstOrDefault(item => item.Id == id); + + return item is not null + ? Ok(item) + : OperationStatusResult( + MyItemOperationStatus.NotFound, + builder => NotFound( + builder + .WithTitle("That thing wasn't there") + .WithDetail("Maybe look for something else?") + .Build() + ) + ); +} +``` +{% endcode %} + +{% code title="MyItemOperationStatus.cs" %} +```csharp +public enum MyItemOperationStatus +{ + NotFound, + InvalidValue, + DuplicateValue +} +``` +{% endcode %} + +## Creating a new item + +We can now add an action to create a new item. We use the `HttpPost` attribute to define the `HTTP` method and route for the action. Here we can see some validation logic. + +If the value does not start with "New", we return a `BadRequest` response with an error message. This highlights why we use the `OperationStatusResult` method. We can return a detailed response. + +We also use `CreatedAtId`, a helper method to create a response with a `201 Created` status code and a `Location` header. + +{% code title="MyItemApiController.cs" %} +```csharp +[HttpPost] +public IActionResult CreateItem(string value) +{ + if (value.StartsWith("New") is false) + { + return OperationStatusResult( + MyItemOperationStatus.InvalidValue, + builder => BadRequest( + builder + .WithTitle("That was invalid") + .Build() + ) + ); + } + + if (AllItems.Any(item => item.Value.InvariantEquals(value))) + { + return OperationStatusResult( + MyItemOperationStatus.DuplicateValue, + builder => BadRequest( + builder + .WithTitle("No duplicate values, please.") + .Build() + ) + ); + } + + var newItem = new MyItem(value); + AllItems.Add(newItem); + return CreatedAtId( + ctrl => nameof(ctrl.GetItem), + newItem.Id + ); +} +``` +{% endcode %} + +## Updating an item + +Now we can add an action to update an item. We can use the `HttpPut` attribute to define the `HTTP` method and route for the action. + +{% code title="MyItemApiController.cs" %} +```csharp +[HttpPut("{id:guid}")] +public IActionResult UpdateItem(Guid id, string value) +{ + MyItem? item = AllItems.FirstOrDefault(item => item.Id == id); + if (item is null) + { + return OperationStatusResult( + MyItemOperationStatus.NotFound, + builder => NotFound( + builder + .WithTitle("That thing wasn't there") + .WithDetail("Maybe look for something else?") + .Build() + ) + ); + } + + if (AllItems.Any(i => i.Value.InvariantEquals(value))) + { + return OperationStatusResult( + MyItemOperationStatus.DuplicateValue, + builder => BadRequest( + builder + .WithTitle("You have been DUPED") + .Build() + ) + ); + } + + item.Value = value; + return Ok(); +} +``` +{% endcode %} + +## Deleting an item + +Finally, we can add an action to delete an item. We can use the `HttpDelete` attribute to define the `HTTP` method and route for the action. + +{% code title="MyItemApiController.cs" %} +```csharp +[HttpDelete("{id:guid}")] +public IActionResult DeleteItem(Guid id) +{ + MyItem? item = AllItems.FirstOrDefault(item => item.Id == id); + + if (item is null) + { + return OperationStatusResult( + MyItemOperationStatus.NotFound, + builder => NotFound( + builder + .WithTitle("That thing wasn't there") + .WithDetail("Maybe look for something else?") + .Build() + ) + ); + } + + AllItems.Remove(item); + return Ok(); +} +``` +{% endcode %} + +Now we have created the custom API for our Umbraco project. Below you can see the full example of the implementation. + +## Full implementation + +{% code title="MyItemApiController.cs" %} +```csharp +using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Api.Common.Attributes; +using Umbraco.Cms.Api.Common.ViewModels.Pagination; +using Umbraco.Cms.Api.Management.Controllers; +using Umbraco.Cms.Api.Management.Routing; + +namespace UmbracoDocs.Samples; + +[VersionedApiBackOfficeRoute("my/item")] +[ApiExplorerSettings(GroupName = "My item API")] +public class MyItemApiController : ManagementApiControllerBase +{ + private static readonly List AllItems = Enumerable.Range(1, 100) + .Select(i => new MyItem($"My Item #{i}")) + .ToList(); + + [HttpGet] + public IActionResult GetAllItems(int skip = 0, int take = 10) + => Ok( + new PagedViewModel + { + Items = AllItems.Skip(skip).Take(take), + Total = AllItems.Count + } + ); + + [HttpGet("{id:guid}")] + public IActionResult GetItem(Guid id) + { + MyItem? item = AllItems.FirstOrDefault(item => item.Id == id); + + return item is not null + ? Ok(item) + : OperationStatusResult( + MyItemOperationStatus.NotFound, + builder => NotFound( + builder + .WithTitle("That thing wasn't there") + .WithDetail("Maybe look for something else?") + .Build() + ) + ); + } + + [HttpPost] + public IActionResult CreateItem(string value) + { + if (value.StartsWith("New") is false) + { + return OperationStatusResult( + MyItemOperationStatus.InvalidValue, + builder => BadRequest( + builder + .WithTitle("That was invalid") + .Build() + ) + ); + } + + if (AllItems.Any(item => item.Value.InvariantEquals(value))) + { + return OperationStatusResult( + MyItemOperationStatus.DuplicateValue, + builder => BadRequest( + builder + .WithTitle("No duplicate values, please.") + .Build() + ) + ); + } + + var newItem = new MyItem(value); + AllItems.Add(newItem); + return CreatedAtId( + ctrl => nameof(ctrl.GetItem), + newItem.Id + ); + } + + [HttpPut("{id:guid}")] + public IActionResult UpdateItem(Guid id, string value) + { + MyItem? item = AllItems.FirstOrDefault(item => item.Id == id); + if (item is null) + { + return OperationStatusResult( + MyItemOperationStatus.NotFound, + builder => NotFound( + builder + .WithTitle("That thing wasn't there") + .WithDetail("Maybe look for something else?") + .Build() + ) + ); + } + + if (AllItems.Any(i => i.Value.InvariantEquals(value))) + { + return OperationStatusResult( + MyItemOperationStatus.DuplicateValue, + builder => BadRequest( + builder + .WithTitle("You have been DUPED") + .Build() + ) + ); + } + + item.Value = value; + return Ok(); + } + + [HttpDelete("{id:guid}")] + public IActionResult DeleteItem(Guid id) + { + MyItem? item = AllItems.FirstOrDefault(item => item.Id == id); + + if (item is null) + { + return OperationStatusResult( + MyItemOperationStatus.NotFound, + builder => NotFound( + builder + .WithTitle("That thing wasn't there") + .WithDetail("Maybe look for something else?") + .Build() + ) + ); + } + + AllItems.Remove(item); + return Ok(); + } +} + +public class MyItem(string value) +{ + public Guid Id { get; } = Guid.NewGuid(); + + public string Value { get; set; } = value; +} + +public enum MyItemOperationStatus +{ + NotFound, + InvalidValue, + DuplicateValue +} +``` +{% endcode %} diff --git a/16/umbraco-cms/tutorials/creating-a-backoffice-api/access-policies.md b/16/umbraco-cms/tutorials/creating-a-backoffice-api/access-policies.md new file mode 100644 index 00000000000..d15e0100bbd --- /dev/null +++ b/16/umbraco-cms/tutorials/creating-a-backoffice-api/access-policies.md @@ -0,0 +1,70 @@ +--- +description: How to apply access policies for Management APIs +--- + +# Access policies + +A Management API is by default available to any authorized Umbraco backoffice user. + +To further restrict access we can apply access policies using the `[Authorize]` attribute. + +## Built-in access policies + +Umbraco maintains a set of built-in access policies we can leverage in our own APIs. The policy names are defined in `Umbraco.Cms.Web.Common.Authorization.AuthorizationPolicies`. + +For example, the following makes the API accessible only to users with Content section access: + +{% code title="MyItemApiController.cs" %} +```csharp +using Umbraco.Cms.Web.Common.Authorization; +... + +[Authorize(AuthorizationPolicies.SectionAccessContent)] +public class MyItemApiController : ManagementApiControllerBase +``` +{% endcode %} + +## Custom access policies + +We can also define our own access policies. Custom access policies are a great way of keeping access control in sync across multiple endpoints, as projects evolve over time. + +A custom access policy is defined by means of composition. + +The following access policy definition requires the user to be a member of both the Umbraco Administrators group and a custom defined group: + +{% code title="SampleCustomPolicyComposer.cs" %} +```csharp +public class SampleCustomPolicyComposer : IComposer +{ + public void Compose(IUmbracoBuilder builder) + => builder.Services.AddAuthorization(options => + options.AddPolicy(SiteConstants.CustomPolicyName, policy => + { + policy.AuthenticationSchemes.Add(OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme); + policy.RequireRole(SiteConstants.CustomGroupAlias); + policy.RequireRole(Constants.Security.AdminGroupAlias); + }) + ); +} +``` +{% endcode %} + +{% code title="SiteConstants.cs" %} +```csharp +public static class SiteConstants +{ + public const string CustomPolicyName = "Site.CustomPolicy"; + + public const string CustomGroupAlias = "customGroup"; +} +``` +{% endcode %} + +With the policy defined, we can apply it to the API controller: + +{% code title="MyItemApiController.cs" %} +```csharp +[Authorize(SiteConstants.CustomPolicyName)] +public class MyItemApiController : ManagementApiControllerBase +``` +{% endcode %} diff --git a/16/umbraco-cms/tutorials/creating-a-backoffice-api/adding-a-custom-swagger-document.md b/16/umbraco-cms/tutorials/creating-a-backoffice-api/adding-a-custom-swagger-document.md new file mode 100644 index 00000000000..2cbd04ad292 --- /dev/null +++ b/16/umbraco-cms/tutorials/creating-a-backoffice-api/adding-a-custom-swagger-document.md @@ -0,0 +1,68 @@ +--- +description: Adding a custom Swagger document for a custom Management API +--- + +# Adding a custom Swagger document + +By default, all controllers based on ManagementApiControllerBase will be included in the default Management API Swagger document. + +When building custom Management API controllers, sometimes it's preferable to have a dedicated Swagger document for them. Doing so is a three-step process: + +1. Register the Swagger document with Swagger UI. +2. Instruct Swagger UI to utilize Umbraco authentication for the Swagger document. +3. Move the controllers to the Swagger document. + +The following code exemplifies how to achieve the first two steps; + +{% code title="MyItemApiComposer.cs" %} +```csharp +using Microsoft.Extensions.Options; +using Microsoft.OpenApi.Models; +using Swashbuckle.AspNetCore.SwaggerGen; +using Umbraco.Cms.Api.Management.OpenApi; +using Umbraco.Cms.Core.Composing; + +namespace UmbracoDocs.Samples; + +public class MyItemApiComposer : IComposer +{ + public void Compose(IUmbracoBuilder builder) + { + builder.Services.ConfigureOptions(); + } +} + +public class MyItemApiSwaggerGenOptions : IConfigureOptions +{ + public void Configure(SwaggerGenOptions options) + { + // register the custom Swagger document "my-item-api" + options.SwaggerDoc( + "my-item-api", + new OpenApiInfo { Title = "My item API", Version = "1.0" } + ); + + // enable Umbraco authentication for the "my-item-api" Swagger document + options.OperationFilter(); + } +} + +public class MyItemApiOperationSecurityFilter : BackOfficeSecurityRequirementsOperationFilterBase +{ + protected override string ApiName => "my-item-api"; +} +``` +{% endcode %} + +With this in place, the last step is to annotate the relevant API controllers with the MapToApi attribute: + +{% code title="MyItemApiController.cs" %} +```csharp +[MapToApi("my-item-api")] +public class MyItemApiController : ManagementApiControllerBase +``` +{% endcode %} + +Now when we visit the Swagger UI, "My item API" has its own Swagger document: + +![My item API in Swagger UI](images/my-item-api-swagger-ui.png) diff --git a/16/umbraco-cms/tutorials/creating-a-backoffice-api/documenting-your-controllers.md b/16/umbraco-cms/tutorials/creating-a-backoffice-api/documenting-your-controllers.md new file mode 100644 index 00000000000..6e6460e8a4b --- /dev/null +++ b/16/umbraco-cms/tutorials/creating-a-backoffice-api/documenting-your-controllers.md @@ -0,0 +1,113 @@ +# Documenting your controllers + +Documenting your API controllers using Swagger in Umbraco Version 14 simplifies the creation of detailed and interactive API documentation. Adding Swagger attributes automatically generates comprehensive information about routes, parameters, and response types. This will enhance the developer experience and ensure clarity and consistency in your API documentation. + +## ApiExplorerSettings + +With the `ApiExplorerSettings` attribute, we can put all our endpoints into a given group. This is a nice way of organizing our endpoints in the Swagger UI. + +{% code title="MyItemApiController.cs" %} + +```csharp +[ApiExplorerSettings(GroupName = "My item API")] +public class MyItemApiController : ManagementApiControllerBase +``` + +{% endcode %} + +## ProducesResponseType Attribute + +Use [ProducesResponseType] to specify the possible responses for each action method. This helps Swagger generate accurate documentation for your API. +For example, in the GetItem method: + +{% code title="MyItemApiController.cs" %} + +```csharp +[HttpGet("{id:guid}")] +[ProducesResponseType(StatusCodes.Status200OK)] +[ProducesResponseType(StatusCodes.Status404NotFound)] +public IActionResult GetItem(Guid id) +{ +// Method implementation +} +``` + +{% endcode %} + +Here, `[ProducesResponseType]` specifies that a 200 OK response will return a MyItem, and a 404 Not Found response will return a ProblemDetails. + +## Example Documentation for Each Controller Method + +To get an idea of how to document each controller method, below are some examples of how to document each operation for an API controller. +The controller is from the [Creating your own API article](./README.md) + +### GetAllItems + +{% code title="MyItemApiController.cs" %} + +```csharp + +[HttpGet] +[ProducesResponseType>(StatusCodes.Status200OK)] +public IActionResult GetAllItems(int skip = 0, int take = 10) +``` + +{% endcode %} + +### GetItem + +{% code title="MyItemApiController.cs" %} + +```csharp +[HttpGet("{id:guid}")] +[ProducesResponseType(StatusCodes.Status200OK)] +[ProducesResponseType(StatusCodes.Status404NotFound)] +public IActionResult GetItem(Guid id) +``` + +{% endcode %} + +### CreateItem + +{% code title="MyItemApiController.cs" %} + +```csharp +[HttpPost] +[ProducesResponseType(StatusCodes.Status201Created)] +[ProducesResponseType(StatusCodes.Status400BadRequest)] +public IActionResult CreateItem(string value) +``` + +{% endcode %} + +### UpdateItem + +{% code title="MyItemApiController.cs" %} + +```csharp +[HttpPut("{id:guid}")] +[ProducesResponseType(StatusCodes.Status200OK)] +[ProducesResponseType(StatusCodes.Status400BadRequest)] +[ProducesResponseType(StatusCodes.Status404NotFound)] +public IActionResult UpdateItem(Guid id, string value) +``` + +{% endcode %} + +### DeleteItem + +{% code title="MyItemApiController.cs" %} + +```csharp +[HttpDelete("{id:guid}")] +[ProducesResponseType(StatusCodes.Status200OK)] +[ProducesResponseType(StatusCodes.Status404NotFound)] +public IActionResult DeleteItem(Guid id) +``` + +{% endcode %} + +## Verifying the changes + +Run your application and navigate to the Swagger UI (typically found at /swagger). +Verify that your API documentation is correctly displaying the routes, parameters, and response types. diff --git a/16/umbraco-cms/tutorials/creating-a-backoffice-api/images/my-item-api-swagger-ui.png b/16/umbraco-cms/tutorials/creating-a-backoffice-api/images/my-item-api-swagger-ui.png new file mode 100644 index 00000000000..7926fe84e62 Binary files /dev/null and b/16/umbraco-cms/tutorials/creating-a-backoffice-api/images/my-item-api-swagger-ui.png differ diff --git a/16/umbraco-cms/tutorials/creating-a-backoffice-api/polymorphic-output-in-the-management-api.md b/16/umbraco-cms/tutorials/creating-a-backoffice-api/polymorphic-output-in-the-management-api.md new file mode 100644 index 00000000000..698ea8151b8 --- /dev/null +++ b/16/umbraco-cms/tutorials/creating-a-backoffice-api/polymorphic-output-in-the-management-api.md @@ -0,0 +1,113 @@ +--- +description: How to support polymorphic outputs from custom Management APIs +--- + +# Polymorphic output in the Management API + +For security reasons, the `System.Text.Json` serializer will not serialize types that are not explicitly referenced at compile time. + +This can be a challenge when dealing with polymorphic API outputs. As a workaround, the Management API provides two options for enabling polymorphic outputs. + +## Polymorphism by interface + +This approach requires that all output models implement the same interface - for example: + +{% code title="IMyItem.cs" %} +```csharp +public interface IMyItem +{ + Guid Id { get; } + + string Value { get; set; } +} +``` +{% endcode %} + +{% code title="MyItem.cs" %} +```csharp +public class MyItem(string value) : IMyItem +{ + public Guid Id { get; } = Guid.NewGuid(); + + public string Value { get; set; } = value; +} +``` +{% endcode %} + +{% code title="MyOtherItem.cs" %} +```csharp +public class MyOtherItem(string value, int otherValue) : IMyItem +{ + public Guid Id { get; } = Guid.NewGuid(); + + public string Value { get; set; } = value; + + public int OtherValue { get; } = otherValue; +} +``` +{% endcode %} + +The `ProducesResponseType` annotation on the endpoints must also be updated to use the interface: + +{% code title="MyItemApiController.cs" %} +```csharp +... +[ProducesResponseType>(StatusCodes.Status200OK)] +public IActionResult GetAllItems(int skip = 0, int take = 10) +... +[ProducesResponseType(StatusCodes.Status200OK)] +public IActionResult GetItem(Guid id) +... +``` +{% endcode %} + +## Polymorphism by annotation + +This approach requires that all output models implement a common base class. The base class will define all its derived types by annotation - for example: + +{% code title="MyItemBase.cs" %} +```csharp +[JsonDerivedType(typeof(MyItem), nameof(MyItem))] +[JsonDerivedType(typeof(MyOtherItem), nameof(MyOtherItem))] +public abstract class MyItemBase(string value) +{ + public Guid Id { get; } = Guid.NewGuid(); + + public string Value { get; set; } = value; +} +``` +{% endcode %} + +{% code title="MyItem.cs" %} +```csharp +public class MyItem(string value) : MyItemBase(value) +{ +} + +``` +{% endcode %} + +{% code title="MyOtherItem.cs" %} +```csharp +public class MyOtherItem(string value, int otherValue) : MyItemBase(value) +{ + public int OtherValue { get; } = otherValue; +} + +``` +{% endcode %} + +The `ProducesResponseType` annotation on the endpoints must also be updated to use the base class: + + +{% code title="MyItemApiController.cs" %} +```csharp +... +[ProducesResponseType>(StatusCodes.Status200OK)] +public IActionResult GetAllItems(int skip = 0, int take = 10) +... +[ProducesResponseType(StatusCodes.Status200OK)] +public IActionResult GetItem(Guid id) +... +``` +{% endcode %} diff --git a/16/umbraco-cms/tutorials/creating-a-backoffice-api/umbraco-schema-and-operation-ids.md b/16/umbraco-cms/tutorials/creating-a-backoffice-api/umbraco-schema-and-operation-ids.md new file mode 100644 index 00000000000..d69941068df --- /dev/null +++ b/16/umbraco-cms/tutorials/creating-a-backoffice-api/umbraco-schema-and-operation-ids.md @@ -0,0 +1,98 @@ +--- +description: How to apply the Umbraco schema and operation IDs for custom Management APIs +--- + +# Umbraco schema and operation IDs + +All core Management APIs have a custom scheme for their generated OpenAPI schema and operation IDs. + +This scheme is strictly opt-in to avoid affecting custom APIs by default. In this article, we'll see how to opt-in to the scheme. + +{% hint style="info" %} +If you are happy with your APIs' default schema and operation IDs, nothing is likely gained by using the Umbraco ones. +{% endhint %} + +## Schema IDs + +Schema IDs are handled by `ISchemaIdHandler` implementations. To opt-in to the Umbraco schema IDs, we base our implementation on the core handler: + +{% code title="SampleSchemaIdHandler.cs" %} +```csharp +using Umbraco.Cms.Api.Common.OpenApi; + +namespace UmbracoDocs.Samples; + +// this schema ID handler extends the Umbraco schema IDs +// to all types in the UmbracoDocs.Samples namespace +public class SampleSchemaIdHandler : SchemaIdHandler +{ + public override bool CanHandle(Type type) + => type.Namespace == "UmbracoDocs.Samples"; +} +``` +{% endcode %} + +Then, we implement a composer to register the new schema ID handler: + +{% code title="SampleSchemaIdComposer.cs" %} +```csharp +using Umbraco.Cms.Api.Common.OpenApi; +using Umbraco.Cms.Core.Composing; + +namespace UmbracoDocs.Samples; + +public class SampleSchemaIdComposer : IComposer +{ + public void Compose(IUmbracoBuilder builder) + => builder.Services.AddSingleton(); +} +``` +{% endcode %} + +## Operation IDs + +Operation IDs follow the same pattern as schema IDs. The only difference is that the `IOperationIdHandler` operates at the API level, not at the type level. + +Again, to opt-in to the Umbraco operation IDs, we base our implementation on the core handler: + +{% code title="SampleOperationIdHandler.cs" %} +```csharp +using Asp.Versioning; +using Microsoft.AspNetCore.Mvc.ApiExplorer; +using Microsoft.AspNetCore.Mvc.Controllers; +using Microsoft.Extensions.Options; +using Umbraco.Cms.Api.Common.OpenApi; + +namespace UmbracoDocs.Samples; + +// this operation ID handler extends the Umbraco operation IDs +// to all API controllers in the UmbracoDocs.Samples namespace +public class SampleOperationIdHandler : OperationIdHandler +{ + public SampleOperationIdHandler(IOptions apiVersioningOptions) + : base(apiVersioningOptions) + { + } + + protected override bool CanHandle(ApiDescription apiDescription, ControllerActionDescriptor controllerActionDescriptor) + => controllerActionDescriptor.ControllerTypeInfo.Namespace == "UmbracoDocs.Samples"; +} +``` +{% endcode %} + +Then, we implement a composer to register the new operation ID handler: + +{% code title="SampleOperationIdComposer.cs" %} +```csharp +using Umbraco.Cms.Api.Common.OpenApi; +using Umbraco.Cms.Core.Composing; + +namespace UmbracoDocs.Samples; + +public class SampleOperationIdComposer : IComposer +{ + public void Compose(IUmbracoBuilder builder) + => builder.Services.AddSingleton(); +} +``` +{% endcode %} diff --git a/16/umbraco-cms/tutorials/creating-a-backoffice-api/versioning-your-api.md b/16/umbraco-cms/tutorials/creating-a-backoffice-api/versioning-your-api.md new file mode 100644 index 00000000000..58a56cd472e --- /dev/null +++ b/16/umbraco-cms/tutorials/creating-a-backoffice-api/versioning-your-api.md @@ -0,0 +1,199 @@ +--- +description: Adding new versions of custom Management APIs +--- + +# Versioning your API + +All APIs register as version 1.0 by default, which means their endpoints are routed under `/umbraco/management/api/v1/`. + +As projects evolve, adding new versions of your APIs sometimes becomes necessary. Multiple versions of the same API can co-exist to retain backward compatibility. + +APIs are versioned using attribute annotation: + +- `[ApiVersion]` attributes on the API controllers. +- `[MapToApiVersion]` attributes on the API controller actions. + +{% hint style="info" %} +It is recommended to annotate _all_ API controller actions, as well as the version 1.0 actions. +{% endhint %} + +Using the API controller from the [Creating your own API article](./README.md) as an example, we can add version 2.0 implementations of select actions: + +{% code title="MyItemApiController.cs" %} + +```csharp +[ApiVersion("1.0")] +[ApiVersion("2.0")] +public class MyItemApiController : ManagementApiControllerBase +{ + [HttpGet] + [MapToApiVersion("1.0")] + public IActionResult GetAllItems(int skip = 0, int take = 10) + { + // ... + } + + [HttpGet("{id:guid}")] + [MapToApiVersion("1.0")] + public IActionResult GetItem(Guid id) + { + // ... + } + + [HttpPost] + [MapToApiVersion("1.0")] + public IActionResult CreateItem(string value) + { + // ... + } + + [HttpPut("{id:guid}")] + [MapToApiVersion("1.0")] + public IActionResult UpdateItem(Guid id, string value) + { + // ... + } + + [HttpDelete("{id:guid}")] + [MapToApiVersion("1.0")] + public IActionResult DeleteItem(Guid id) + { + // ... + } + + [HttpGet("{id:guid}")] + [MapToApiVersion("2.0")] + public IActionResult GetItemV2(Guid id) + { + MyItem? item = AllItems.FirstOrDefault(item => item.Id == id); + + return item is not null + ? Ok(item) + : OperationStatusResult( + MyItemOperationStatus.NotFound, + builder => NotFound( + builder + .WithTitle("The item was not found") + .WithDetail("The item with the given ID did not exist.") + .Build() + ) + ); + } + + [HttpPost] + [MapToApiVersion("2.0")] + public IActionResult CreateItemV2(string value) + { + var newItem = new MyItem(value); + AllItems.Add(newItem); + return CreatedAtId( + ctrl => nameof(ctrl.GetItemV2), + newItem.Id + ); + } +} +``` + +{% endcode %} + +Version 2.0 of the "get" and "create" endpoints - `GetItemV2` and `CreateItemV2` respectively, are added with the code above. The rest of the endpoints remain version 1.0 only. + +{% hint style="info" %} +The version 2.0 endpoints are routed under `/umbraco/management/api/v2/`. +{% endhint %} + +In the example above, the version 2.0 actions are added to the same API controller as their version 1.0 counterparts. If you prefer, they can be added to a new API controller instead. This will leave you with separate API controllers, one for each version of the API. See the examples below: + +{% code title="MyItemApiController.cs" %} + +```csharp +[ApiVersion("1.0")] +public class MyItemApiController : ManagementApiControllerBase +{ + [HttpGet] + [MapToApiVersion("1.0")] + public IActionResult GetAllItems(int skip = 0, int take = 10) + { + // ... + } + + [HttpGet("{id:guid}")] + [MapToApiVersion("1.0")] + public IActionResult GetItem(Guid id) + { + // ... + } + + [HttpPost] + [MapToApiVersion("1.0")] + public IActionResult CreateItem(string value) + { + // ... + } + + [HttpPut("{id:guid}")] + [MapToApiVersion("1.0")] + public IActionResult UpdateItem(Guid id, string value) + { + // ... + } + + [HttpDelete("{id:guid}")] + [MapToApiVersion("1.0")] + public IActionResult DeleteItem(Guid id) + { + // ... + } +} +``` + +{% endcode %} + +With the version 1.0 actions added in a controller sampled above, the version 2.0 actions are added in a new controller, as shown below. + +{% code title="MyItemApiVersionTwoController.cs" %} +```csharp +[ApiVersion("2.0")] +public class MyItemApiVersionTwoController : ManagementApiControllerBase +{ + private static readonly List AllItems = Enumerable.Range(1, 100) + .Select(i => new MyItem($"My V2 Item #{i}")) + .ToList(); + + [HttpGet("{id:guid}")] + [MapToApiVersion("2.0")] + public IActionResult GetItem(Guid id) + { + MyItem? item = AllItems.FirstOrDefault(item => item.Id == id); + + return item is not null + ? Ok(item) + : OperationStatusResult( + MyItemOperationStatus.NotFound, + builder => NotFound( + builder + .WithTitle("The item was not found") + .WithDetail("The item with the given ID did not exist.") + .Build() + ) + ); + } + + [HttpPost] + [MapToApiVersion("2.0")] + public IActionResult CreateItem(string value) + { + var newItem = new MyItem(value); + AllItems.Add(newItem); + return CreatedAtId( + ctrl => nameof(ctrl.GetItem), + newItem.Id + ); + } +} +``` +{% endcode %} + +{% hint style="warning" %} +While perhaps tempting, do _not_ name your API controller `V2` - e.g. `MyItemApiVersionV2`. Due to an upstream issue in the API versioning system, this will currently cause routing issues in certain scenarios. +{% endhint %} diff --git a/16/umbraco-cms/tutorials/creating-a-basic-website/README.md b/16/umbraco-cms/tutorials/creating-a-basic-website/README.md new file mode 100644 index 00000000000..a917035ce2e --- /dev/null +++ b/16/umbraco-cms/tutorials/creating-a-basic-website/README.md @@ -0,0 +1,58 @@ +--- +description: "A guide to creating a basic website using Umbraco" +--- +# Creating a Basic Website using Umbraco + +This tutorial is based and tested on the latest version of **Umbraco 14**. + +## Introduction + +This tutorial will guide you through building an Umbraco website step by step. It will show you how to take any website template, such as flat HTML, CSS, and JavaScript, and install it into a new Umbraco site. Then, you can connect the sections that require content management by Umbraco. + +We will not be using any starter kits in this tutorial. While these can be helpful, they do not offer a full understanding of the basics of Umbraco. Instead, we will focus on creating Document Types and Templates and how they work together to build pages. + +It is recommended that you work through the sub-sections in the following order. + +## [Getting Started](getting-started.md) + +To follow this tutorial, ensure to complete this section first. + +## [Creating Your First Document Type](document-types.md) + +Learn how to create Document Types and understand their purpose. + +## [Creating Your First Template and Content Node](creating-your-first-template-and-content-node.md) + +Discover how to create your first template and set up a content node. + +## [CSS & Images](css-and-images.md) + +Learn how to integrate CSS and JavaScript into your Umbraco site. + +## [Displaying the Document Type Properties](displaying-the-document-type-properties.md) + +Find out how to connect Document Type Properties (Umbraco Data Fields) to templates to display the editor's data correctly. + +## [Creating a Master Template](creating-master-template-part-1.md) + +Learn how to create a Master Template to streamline the creation of additional pages and reduce repetitive HTML code. + +## [Creating Pages and using the Master Template](creating-master-template-part-2.md) + +Use the Master Template to generate new page types. + +## [Setting The Navigation Menu](setting-the-navigation-menu.md) + +Manage the navigation menu in your template. + +## [Articles Parent and Article Items](article-parent-and-article-items.md) + +Learn how to create a parent page that automatically lists and links to child nodes. Ideal for something like blogs or news sections. + +## [Adding Language Variants](adding-language-variants.md) + +Explore how to expand your basic site by introducing Language Variants to support multiple languages. + +## [Conclusions](conclusion.md) + +By this stage, you will have a basic functioning site. Discover where to go next and explore the deeper capabilities of Umbraco with additional resources. diff --git a/16/umbraco-cms/tutorials/creating-a-basic-website/adding-language-variants.md b/16/umbraco-cms/tutorials/creating-a-basic-website/adding-language-variants.md new file mode 100644 index 00000000000..bffaa198e04 --- /dev/null +++ b/16/umbraco-cms/tutorials/creating-a-basic-website/adding-language-variants.md @@ -0,0 +1,94 @@ +# Adding Language Variants + +Now that we have a basic site setup, let us make it multilingual by creating content variations in another language. In this tutorial, we will create a Danish version of the `HomePage`. + +## Adding a new language + +To add a new language, follow these steps: + +1. Go to the **Settings** section. +2. Go to **Languages**. +3. Click **Create**. +4. Select a **Language** from the dropdown list. In this tutorial, we will pick Danish. +5. In **Settings**, to set the new language as the: + * Default language for your site, toggle **Default Language**. + * Mandatory language for your site, toggle **Mandatory Language**. +6. Select a **Fallback Language** from the drop-down list. +7. Click **Submit**. +8. Click **Save**. + + ![Adding a language](images/adding-a-language.png) + +## Enabling Language Variants on Document Types and Properties + +To enable language variants on Document Types, follow these steps: + +1. Go to the **Settings** section. +2. Select **HomePage** from the **Document Types** folder. +3. Go to the **Settings** tab and toggle **Allow vary by culture** + + ![Enable Vary by Culture](images/enable-vary-by-culture.png) +4. Click **Save**. +5. Go to the **Design** tab. +6. Click on the Data Type of the **Page Title** and enable **Vary by culture**. +7. Click **Submit**. +8. Click **Save**. + + ![Allow property editor Language Variants](images/enable-vary-by-culture-property.png) + +## Adding Culture and Hostnames to the root node of the website + +To add culture and hostnames, follow these steps: + +1. Go to the **Content** section. +2. Click on the **...** dots next to the **Home Page** content node. +3. Select **Culture and Hostnames**. +4. Add a domain for each hostname, like it's done here: +5. Click **Save**. + + ![Culture and Hostnames](images/culture-and-hostnames-v14.png) + +## Adding Language Variants to the Content + +You will find a language dropdown above your content tree. If it's not there, you might need to refresh the page: + +![Language of Content Tree](images/language-content-tree-v14.png) + +In the language dropdown, you will find all the languages that you have installed for your site. You can switch between them to update the content variations for each language. + +To add language variants to the content, follow these steps: + +1. Go to the **Home Page** node. You will find a language dropdown next to the title at the top. + + ![Language Variant dropdown](images/language-dropdown-v14.png) +2. Click on the dropdown. You will see a **Split view** option next to the new language. + + ![Open Language in Splitview](images/open-in-splitview-v14.png) +3. Click **Split view**. We can now see the content node with each language side by side. + + You may notice that the bodytext is greyed out - this is because we haven't checked the **Vary by culture** checkbox. + + ![Splitview editing](images/splitview-editing.png) +4. Enter the **Name** for your content node and the **Page Title** in the new language. +5. Click **Save and Publish**. +6. The **Ready to Publish** window opens providing the option to publish in one or more languages. + + ![Publishing Variant content](images/publishing-variant-content-v14.png) +7. You can select either one or multiple languages and click **Save and Publish**. + +## Viewing the Language Variant on the Browser + +To view the language variant on the browser, follow these steps: + +1. Go to the **Content** section. +2. Select your new language from the language dropdown above your content tree. +3. Select the **Home Page** node and go to the **Info** tab. +4. You will notice the links with the new language domain added to it. If it's not there, you might need to refresh the page. + + ![Viewing the Language Variant Link](images/viewing-langvariant-browser-v14.png) +5. Click on the link to view the new language node in the browser. +6. Alternatively, you can add the domain name to your localhost in the browser. For example: `http://localhost:xxxx/da/`. + +## More Information + +Further information on multi-language setups can be found in the [Creating a Multilingual Site](../multilanguage-setup.md) tutorial. diff --git a/16/umbraco-cms/tutorials/creating-a-basic-website/article-parent-and-article-items.md b/16/umbraco-cms/tutorials/creating-a-basic-website/article-parent-and-article-items.md new file mode 100644 index 00000000000..1bd557fd20e --- /dev/null +++ b/16/umbraco-cms/tutorials/creating-a-basic-website/article-parent-and-article-items.md @@ -0,0 +1,383 @@ +# Articles and Article Items + +Having a parent page with child pages provides a good example of Umbraco's features. For example, our fictional company, Widgets Ltd, publishes about ten articles per month and therefore wants the parent page to function as a blog. This setup works for articles, posts, and other collections that require multiple content items based on the same content type. + +## Creating Articles and Article Items + +Create two new Document Types with template: **Articles Main** and **Articles Item**. + +To create **Articles Main** Document Type, follow these steps: + +1. Go to **Settings**. +2. Click **...** next to the **Document Types** in the **Settings** tree. +3. Select **Create...**. +4. Select **Document Type with Template**. +5. Enter a **Name** for the **Document Type**. Let's call it _Articles Main_. +6. Let's add two fields with the following specifications: + + | Group | Field Name | Alias | Data Type | + | ----- | ------------------ | ---------------- | ---------------- | + | Intro | Articles Title | articlesTitle | Textstring | + | Intro | Articles Body Text | articlesBodyText | Rich Text Editor | + + ![Articles Main Document Type Data Properties](images/articles-main.png) +7. Click **Save** + +To create **Articles Item** Document Type, follow these steps: + +1. Go to **Settings**. +2. Click **...** next to the **Document Types** in the **Settings** tree. +3. Select **Create...**. +4. Select **Document Type with Template**. +5. Enter a **Name** for the **Document Type**. Let's call it _Articles Item_. +6. Let's add two fields with the following specifications: + + | Group | Field Name | Alias | Data Type | + | ------- | --------------- | -------------- | ---------------- | + | Content | Article Title | articleTitle | Textstring | + | Content | Article Content | articleContent | Rich Text Editor | + + ![Article Item Document Type Data Properties](images/articles-item.png) +7. Click **Save** + +### Updating the Document Type Permissions + +To add **Articles Main** as a child node: + +1. Navigate to the **Home Page** Document Type. +2. Go to the **Structure** tab. +3. Select **Choose** in the **Allowed child node types**. +4. Select **Articles Main**. +5. Click **Choose**. +6. Click **Save**. + +To update **Articles Main** Document Type permissions: + +1. Navigate to the **Articles Main** Document Type. +2. Go to the **Structure** tab. +3. Select **Choose** in the **Allowed child node types**. +4. Select **Articles Item**. +5. Click **Choose**. + + ![Adding child Node](images/adding-child-node.png) +6. Click **Configure as a collection**. +7. Select **List View - Content**. +8. Click **Save**. + + ![Enabling Collection](images/list-view-enabled.png) + +## Creating the Content Node + +To add a content node: + +1. Go to **Content**. +2. Select **...** next to the **HomePage** node. +3. Click **Create**. +4. Select **Articles Main**. +5. Enter the name for the article. We are going to call it _Articles_. +6. Enter the content in the **Article Title** and **Article Body Text** fields. +7. Click **Save and Publish**. When you click on Save and Publish, you will notice an empty Collection is created. + + We still need to add the child nodes which will be displayed in the Collection making it easier to view them. You can create new nodes from this section. + + {% hint style="info" %} + If you do not see the Collection, try refreshing the page. + {% endhint %} + +8. Click **Create Articles Item**. +9. Enter the name for the article. We are going to call it _Article 1_. +10. Enter the content in the **Article Title** and **Article Content** fields. +11. Repeat steps 8 to 10 to create _Article 2_. +12. Click **Save and Publish**. + + ![Content Tree with Articles](images/figure-40-articles-created-v8.png) + +## Updating the Template + +To update the **Articles Main** template, follow these steps: + +1. Go to **Settings**. +2. Expand the **Templates** folder in the **Templating** section. +3. Open the **Articles Main** template. +4. Select **Master** in the **Master template:No Master** field. +5. Click **Choose**. +6. Click **Save**. +7. Open the **Custom Umbraco Template** folder. +8. Copy the contents of **Blog.html**. +9. Paste the content into **Articles Main** below the closing curly brace "}". +10. Remove everything from the `` (around line 8) to the end of the `` tag (around line 43) which is the `header` and `navigation` of the site since it is already mentioned in the Master template. +11. Remove everything from the `` tag (around line 83) to the end of the `` tag (around line 130) +12. Replace the static text within the `

` tags (around line 12) with the Model.Value reference to _**articlesTitle**_. +13. Replace the static text within the `
` tags (from line 23 to 29) with the Model.Value reference to _**articlesBodyText**_. + + ![Articles Main Template](images/articles-main-template.png) +14. Define a query for all articles below the `

` tag (around line 30) of the `` section. + + ![Query Builder](images/query-builder.png) +15. You can set conditions to get specific articles or decide the order of the articles. For the purpose of this guide, we are using the following parameters: + + ![Query parameters](images/query-parameters-v14.png) +16. If you've set the correct parameters, you will get a preview of the items being selected with the query. +17. Click **Submit**. +18. You will see a similar code snippet added to your template: + + ```html + @{ var selection = + Umbraco.Content(Guid.Parse("56aa9cc5-243b-4947-8fb1-37b209b97373")) + .ChildrenOfType("articlesItem") .Where(x => x.IsVisible()) + .OrderByDescending(x => x.CreateDate); } +
    + @foreach (var item in selection) { +
  • + @item.Name() +
  • + } +
+ ``` + +19. The above code will output a list of all the _**Article Items**_ as links using the name. +20. We will modify the template a little, to add more information about the articles. +21. Replace the `HTML` in the _foreach_ loop with this snippet: + + ```csharp + + ``` +22. Remove the `
    ` tags surrounding the _foreach_ loop. +22. Click **Save**. + +
    + +See the entire file: Articles Main + +{% code title="articlesMain.cshtml" lineNumbers="true" %} +```csharp +@using Umbraco.Cms.Web.Common.PublishedModels; +@inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage +@{ + Layout = "master.cshtml"; +} + + + + +
    +
    +

    @Model.Value("articlesTitle")

    +

    What we do when we do what we do - and why!

    +
    +
    + + +
    + + +
    +
    + @Model.Value("articlesBodyText") +
    +
    + + +
    +
    +

    Latest stories

    + @{ + var selection = Umbraco.Content(Guid.Parse("66da3501-23df-4129-aa79-d511064d6ab8")) + .ChildrenOfType("articlesItem") + .Where(x => x.IsVisible()) + .OrderByDescending(x => x.CreateDate); + } + + @foreach (var item in selection) + { + + } + +
    +
    + + + + +
    +``` +{% endcode %} + +
    + +To update the **Articles Item** template, follow these steps: + +1. Go to **Settings**. +2. Expand the **Templates** folder in the **Templating** section. +3. Open the **Articles Item** template. +4. Select **Master** in the **Master template:No master** field. +5. Click **Choose**. +6. Click **Save**. +7. Open the **Custom Umbraco Template** folder. +8. Copy the contents of **Blogpost.html**. +9. Paste the content into **Articles Item** below the closing curly brace "}". +10. Remove everything from the `` (around line 8) to the end of the `

` tag (around line 43) which is the `header` and `navigation` of the site since it is already mentioned in the Master template. +11. Remove everything from the `` tag (around line 113) to the end of the `` tag (around line 160) +12. Replace the static text within the `

` tags (around line 13) with the Model.Value reference to _**articleTitle**_. +13. Replace the static text within the `
` tags (from line 25 to 39) with the Model.Value reference to _**articleContent**_. + + ![Articles Item Template](images/articles-item-template-v9.png) +14. Click **Submit**. +15. Click **Save**. + +
+ +See the entire file: Articles Item + +{% code title="articlesItem.cshtml" lineNumbers="true" %} +```csharp +@using Umbraco.Cms.Web.Common.PublishedModels; +@inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage +@{ + Layout = "master.cshtml"; +} + + + + +
+
+

@Model.Value("articleTitle")

+

And this is only the beginning.

+
+
+ + + + +
+
+
+
+ @Model.Value("articleContent") +
+
+
+
+ + +
+ +
+
+

The end of 2019 is approaching and with this, also the last Product Update of the year. + And what a year it has been - especially when we’re talking “product”! Two major product launches with Umbraco 8 and Umbraco Heartcore. + 2019 also saw a record-breaking amount of PRs submitted, mainly due to a record-shattering Hacktoberfest 🙌 + It’s absolutely amazing to see how much Umbraco 8 has evolved in the past 10 months, not least thanks to the friendly and very productive Umbraco community. + I cannot wait to see what happens in 2020! + But, before we get to that, let’s have a look at what we’ve been working on and what you can expect in the near future.

+
+

New Release: Forms API & Content Filtering for Umbraco Heartcore

+

December 17th, we released two new features for our headless saas offering, Umbraco Heartcore; Form API and Content Filtering. +

+ Forms API
+ You’ll find that the REST API has been expanded to include forms created with Umbraco Forms. + This means you can create forms (contact forms, questionnaires etc.) directly in the backoffice, + complete with conditionals and validation, and get all you need to render these forms in your presentation layer. + All you need to do is include a form on a content node using the Form Picker and you’ll see the form data necessary + to render the form as part of the JSON response - you can also retrieve forms independent of content. + This also includes endpoints for posting form entries, so they will be stored in your Heartcore project and can utilise multi-step workflows. +

+ Content filtering
+ The REST API also includes a new endpoint for content delivery that lets you do more advanced filtering. + Previously, if you wanted to filter content based on a search term, you could utilise the search endpoint. + This is still possible, and useful, but if you want to be more precise and filter the content server side before it’s returned, + the new filter endpoint might be what you’re looking for. Let’s say you only want to retrieve content of a specific + Document Type that contains a specific phrase in a specific property.

+
+

What's new on Umbraco Cloud

+

If you have an organisation set up on Umbraco Cloud, you’ll find that the overview of projects and members of an organisation has been + given an overhaul to provide you with a better overview. This is done in line with the overall brand “make-over”of the + Umbraco Cloud portal which we released in November. You’ll now see easy access to the different views for your organisation in a menu on the left.

+
+
+

Upcoming Release: Umbraco Deploy 3.3

+

As mentioned in previous Product Updates we have been working on an improved Partial Restore feature for + Umbraco Cloud making it a breeze to get existing projects up and running with real content. + This feature is now complete and undergoing the last round of testing.
+ + We aim to release this on January 7th with a new minor version of Umbraco Deploy.
+ + From this date, all existing projects on Umbraco Cloud can get this by upgrading Umbraco Deploy on the project page and + all new projects will be running the latest version of Deploy when it is released. + This release will be for version 8 and we’ll release this functionality for version 7 projects as soon as it’s backported and tested.

+
+
+
+ + + +``` +{% endcode %} + +
+ +Check your browser, you should now see something similar to the screen below. + +![Finished Articles section](images/article-main-frontend-v14.png) diff --git a/16/umbraco-cms/tutorials/creating-a-basic-website/conclusion.md b/16/umbraco-cms/tutorials/creating-a-basic-website/conclusion.md new file mode 100644 index 00000000000..bd50bbdce71 --- /dev/null +++ b/16/umbraco-cms/tutorials/creating-a-basic-website/conclusion.md @@ -0,0 +1,12 @@ +# Conclusions + +Congratulations! You now have a fully functional website. Hopefully, this guide has provided you with all the basics needed to create your own site in Umbraco. + +This is only the beginning — there is much more to explore in Umbraco beyond what we have covered here. + +* **Video Tutorials:** Check out the [Umbraco Learning Base channel on Youtube](https://www.youtube.com/c/UmbracoLearningBase). +* **Training and Certification:** Attend an official Umbraco Master Class or [Book an Online Course now](https://umbraco.com/products/training). +* **Documentation:** Browse the [Umbraco Documentation website](https://docs.umbraco.com/welcome/) for detailed guidance. +* **Support:** For help with topics not covered in this guide, connect with the community on the [Umbraco Forum](https://our.umbraco.com/forum). + +## Happy Umbraco-ing diff --git a/16/umbraco-cms/tutorials/creating-a-basic-website/creating-master-template-part-1.md b/16/umbraco-cms/tutorials/creating-a-basic-website/creating-master-template-part-1.md new file mode 100644 index 00000000000..72541828959 --- /dev/null +++ b/16/umbraco-cms/tutorials/creating-a-basic-website/creating-master-template-part-1.md @@ -0,0 +1,251 @@ +# Creating a Master Template + +We've seen how to create a **Document Type** and its corresponding **Template**. If you want to create a website containing Home, News, and Contact Us pages, you will need to create a _**Document Type**_ with a corresponding **Template**. + +You may end up copying the same HTML code into each of these templates, which can be time-consuming and repetitive. In such scenario, you might want to consider creating a new master template. + +To create a new master template: + +1. Go to **Settings**. +2. Select the **...** next to the **Templates** folder. Alternatively, click **+** to open a blank template. +3. Click **Create**. + A template opens up in the content editor. +4. Enter a **Name** for the master template. Let's call it _Master_. + + ![Master Template](images/master-template-v14.png) +5. Click **Save**. + +## Using the Master Template + +To use the master template: + +1. Go to **Settings**. +2. Expand the **Templates** folder from the **Templating** section. +3. Open the **Homepage** template. +4. Select `Master Template: No Master`. + The Master template dialog opens on the right-side of the browser. +5. Select the template called **Master**. +6. Click **Choose**. + The Razor code section is updated from `Layout = null;` to `Layout = "Master.cshtml";` + + ![Adding Master Template to HomePage](images/homepage-has-master-template.png) +7. Click **Save**. + +## Updating Templates With the New Master Template + +We now need to move the parts of our HTML template that are common across all templates into the _**Master**_. It might be slightly different for different websites. You'll need to consider if all pages contain a `
` section so that you can update it in the master. + +To update templates with the new master template, follow these steps: + +1. Go to **Settings**. +2. Expand the **Templates** folder from the **Templating** section. +3. Navigate to the **Homepage** template. +4. Cut everything from the `` (around line 7) to the end of the `
` tag (around line 43) which is the `header` and `navigation` of the site to the master template. + + ![Header and navigation tags selected in the HomePage template](images/homepage-after-cutting-the-header.png) +5. Click **Save**. +6. Go to the **Master** template and paste this HTML markup after the closing curly brace (around line 7). + + ![Header and navigation tags added in the Master template](images/master-after-adding-the-header.png) +7. Add `@RenderBody()` at the end of the markup. This will tell Umbraco to insert the child template's content. + + ![Adding renderbody in the Master template](images/adding-renderbody.png) +8. Click **Save**. +9. Repeat the same process for the footer content: + * Go to the **Homepage** template and cut everything from the `` tag (around line 108) to the end of the `` tag (around line 122) and click **Save**. + * Go to the **Master** template and paste this HTML markup after the `@RenderBody()` field we've added. + + ![End of the Master template](images/master-template-complete.png) + +10. Click **Save**. + +Now we've done a lot of work. When we refresh our localhost page, nothing has changed. If you have a compilation error you have perhaps mistyped **@RenderBody()**. + +If you are missing any content such as header or footer, ensure that the templates matches the following: + +
+ +See the entire file: Master Template + +{% code title="master.cshtml" lineNumbers="true" %} +```csharp +@using Umbraco.Cms.Web.Common.PublishedModels; +@inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage +@{ + Layout = null; +} + + + + Welcome - UmbracoTV + + + + + + + + + + +
+ +
+ + @RenderBody() + + + + + + + + + + +``` +{% endcode %} + +
+ +
+ +See the entire file: HomePage Template + +{% code title="homePage.cshtml" lineNumbers="true" %} +```csharp +@using Umbraco.Cms.Web.Common.PublishedModels; +@inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage +@{ + Layout = "master.cshtml"; +} + + + + +
+
+

@Model.Value("pageTitle")

+

And this is only the beginning.

+
+
+ + + +
+ +
+
+ + @Model.Value("bodyText") + +
+
+ +
+ + +
+
+
+
+

A flexible CMS - do things your way

+

The Umbraco source code is free and available for download either as a ZIP file or via NuGet under the MIT license, so you can start crafting websites in Umbraco today. + Need to build a site with multiple 3rd party extensions and customized add-ons? No problem. Need to build a simple site with a straightforward contact form? No problem.

+

Whatever type or scale of your project, Umbraco will be able to handle it - and the cherry on top; with Umbraco’s well-known editor friendly UI, + no matter how complex a site you’re building, you know that your editors will still find it a breeze to edit. Oh, and the cherry on top of the cherry - + there's no need for you to spend time learning a new coding or templating language as Umbraco is based on C#, javaScript and Razor.

+
+
+ Community illustration +
+
+
+
+ + +
+ +
+
+

Latest from the blog

+
+ + +
+
+
+
Product Update - Deploying Deploy 3.3
+

January 20th 2020 - by Sofie Toft

+

The end of 2019 is approaching and with this, also the last Product Update of the year. + Let’s have a look at what we’ve been working on and what you can expect in the near future.

+
+ Read more >> +
+
+
+
+
Umbraco <3 YouTube
+

January 20th 2020 - by Sofie Toft

+

The end of 2019 is approaching and with this, also the last Product Update of the year. + Let’s have a look at what we’ve been working on and what you can expect in the near future.

+
+ Read more >> +
+
+
+
+
A day in a Documentation Curators life
+

January 20th 2020 - by Sofie Toft

+

The end of 2019 is approaching and with this, also the last Product Update of the year. + Let’s have a look at what we’ve been working on and what you can expect in the near future.

+
+ Read more >> +
+
+ +
+ + +
+
+ +
+
+

How to continue your Umbraco journey

+

What’s next? We have gathered the 3 best ways for you to go on from here ensuring you get the best start to your Umbraco journey:

+ 3 ways to start your Umbraco Journey >> +
+
+ +
+
+``` +{% endcode %} +
diff --git a/16/umbraco-cms/tutorials/creating-a-basic-website/creating-master-template-part-2.md b/16/umbraco-cms/tutorials/creating-a-basic-website/creating-master-template-part-2.md new file mode 100644 index 00000000000..ef3a8f7422e --- /dev/null +++ b/16/umbraco-cms/tutorials/creating-a-basic-website/creating-master-template-part-2.md @@ -0,0 +1,147 @@ +# Creating Pages and Using the Master Template + +## Creating a Contact Us Page + +We will now create a page to display our contact details. For added functionality, you might consider replacing this page with a fully-fledged "Contact Us" form. + +Here are some potential solutions: + +* **Use Umbraco Forms (for non-developers):** Umbraco offers an add-on called Umbraco Forms. This tool is ideal for users who aren’t programmers, as it allows editors to create custom forms. You can find more information and purchase the product on [Umbraco.com](https://umbraco.com/products/umbraco-forms/). +* **Build Your Own Contact Form (for developers):** If you prefer a custom solution, you can build your own contact form using [Surface Controllers](../../fundamentals/code/creating-forms.md) in Umbraco. + +### Creating the Document Type and Template + +To create a content-only contact page in Umbraco, follow these steps: + +1. Go to **Settings**. +2. Click on **...** next to **Document Types**. +3. Select **Create**. The Create Document Type dialog opens. +4. Select **Document Type with Template**. +5. Select an **Icon** from the list of icons. +6. Click **Submit**. +7. Enter a **Name**. Let's call it _Simple Content Page_. +8. Let's add two fields with the following specifications: + + | Group | Field Name | Alias | Data Type | + | ------- | ---------- | --------- | ---------------- | + | Content | Page Title | pageTitle | Textstring | + | Content | Body Text | bodyText | Rich Text Editor | + + ![Simple Content Page Template with Data Fields](images/contact-us-template-with-data-fields.png) +9. Click **Save**. +10. Go to **Templates** to view your _Simple Content Page_ template that was created automatically with the Document Type. + + {% hint style="info" %} + If you do not see the template, try refreshing the page. + {% endhint %} +11. Open the _Simple Content Page_ template. +12. Select `Master Template: No Master` and choose the **Master** template. +13. Click **Choose**. +14. Add the following HTML after the closing `}`. + + ```html + +
+
+

Umbraco Support

+
+
+ + +
+
+ +

Are you a developer?

+

Are you a marketer?

+

Are you working at an agency?

+

Let Umbraco unleash your talent

+
+
+ ``` +15. Click **Save**. + +### Updating the Document Type Permissions + +We now need to update the Document Type permissions to specifically add child nodes under the root content node. + +To update the Document Type permissions: + +1. Go to **Settings**. +2. Open the **Homepage** Document Type. +3. Go to the **Structure** tab. +4. Click **Choose** in the **Allowed child node types**. +5. Select **Simple Content Page**. + + ![Allow child nodes in HomePage](images/homepage-allowed-child.png) +6. Click **Choose**. +7. Click **Save**. + +### Creating the content node + +To create a content node: + +1. Go to **Content**. +2. Select **...** next to the **Homepage** node. +3. Click **Create**. +4. Select **Simple Content Page**. + + ![Adding Content Page as Child node](images/adding-child-node-Content.png) +5. Enter a name for the Document Type. Let's call it _Contact Us_. +6. Fill in details for the **Page Title** and **Body Text**. +7. Click **Save and Publish**. + +### Adding the Document Type Properties + +To add the Document Type properties: + +1. Go to **Settings**. +2. Expand the **Templates** folder from the **Templating** section. +3. Go to **Master** and open the **Simple Content Page** template. +4. Scroll to the `` (around line 7) section and highlight the text `“Umbraco Support”` (around line 10). +5. Click **Insert** and select **Value**. + * Select **Document Type** from the **Choose field** drop-down list. + * Select **Simple Content Page**. + * Click **Choose**. + * Select **pageTitle** from the **Simple Content Page** drop-down list. + * Click **Submit**. +6. Repeat the same process for the `
` tag: + * Highlight the content from the `

` tag (around line 18) to the end of the `

` tag (around line 21). + * Click **Insert** and select **Value**. + * Select **Document Type** from the **Choose field** drop-down list. + * Select **Simple Content Page**. + * Click **Choose**. + * Select **bodyText** from the **Simple Content Page** drop-down list. + * Click **Submit**. +7. Click **Save**. + +### Viewing the Contact Us Page + +To view the **Contact Us** Page: + +1. Go to **Content**. +2. Navigate to the **Contact Us** page. +3. Go to the **Info** tab. +4. Click the link to view the page. + + ![Viewing Contact Us Page](images/viewing-contact-us.png) + +## Using Document Type Properties from the Homepage + +You may notice that the footer is now empty - we don't have the content from our Homepage node. + +To use the Document Type properties from the _Homepage_ Document Type, do the following: + +1. Go to **Settings**. +2. Expand the **Templates** folder from the **Templating** section. +3. Open the **Master** template. +4. Highlight `@Model.Value("footerText")` in the footer (around line 51). +5. Click **Insert** and select **Value**. +6. Select **Document Type** from the **Choose field** drop-down list. +7. Select **Home Page**. +8. Click **Choose**. +9. Select **footerText** field from the **HomePage** drop-down list. +10. Select **Yes, make it recursive** checkbox. This notifies Umbraco to look up the content tree if the field doesn't exist at the node level for the page we're requesting. +11. Click **Submit**. +12. Click **Save**. + +Reload the _Contact Us_ page to view the content with the footer. diff --git a/16/umbraco-cms/tutorials/creating-a-basic-website/creating-your-first-template-and-content-node.md b/16/umbraco-cms/tutorials/creating-a-basic-website/creating-your-first-template-and-content-node.md new file mode 100644 index 00000000000..b3cdfd35e35 --- /dev/null +++ b/16/umbraco-cms/tutorials/creating-a-basic-website/creating-your-first-template-and-content-node.md @@ -0,0 +1,72 @@ +# Creating Your First Template + +Umbraco creates a corresponding template when you select the **Document Type with Template** option when creating a Document Type. + +To edit the template: + +1. Go to **Settings**. +2. Expand the **Templates** folder in the **Templating** section of the tree. You should see a template titled **HomePage**. + + {% hint style="info" %} + If you do not see the template, try refreshing the page. + {% endhint %} +3. Open the template. It contains a little bit of Razor code. + + **Code Breakdown:** + + * `@using Umbraco.Cms.Web.Common.PublishedModels;` - Imports the namespace for Umbraco's strongly-typed content models. + * `@inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage` - Sets the view to inherit from Umbraco's base view class for accessing Umbraco-specific features and helpers. + * `Layout = "null";` - The view will not use a layout page unless explicitly specified. For more information on [Razor Pages Layout](https://learn.microsoft.com/en-us/aspnet/core/mvc/views/layout), see the Microsoft Documentation. + + ![Home Page Template](images/empty-homepage-template.png) + +We will use the files from the [Custom Umbraco Template](https://umbra.co/Umbracotemplate) folder. Clicking the link will automatically download the files to your device. + +1. Open the **Custom Umbraco Template** folder and copy the contents of **index.html**. +2. Paste the content into the _HomePage_ template below the closing curly brace "}". + * Umbraco _**Templates**_ uses _**Razor**_ that allows you to add code in your _**Template**_ files. _**Razor**_ reacts to `@` signs. +3. Click **Save**. + +We now have a _Template_. That's two out of the three stages complete for our first page. + +## Creating Your First content node + +Our third and final stage to creating our first page in Umbraco, is to create a _**content node**_. The content node uses our _**Document Type**_ and _**Template**_ to serve up an HTML page to web visitors. + +To add a content node: + +1. Go to **Content**. +2. Select **...** next to **Content** in the tree. +3. Click **Create**. +4. Select **HomePage**. The Home Page opens in the content editor. + + * If you cannot see the content node, check that **Settings** > **Document Types** > **HomePage** > **Structure** > **Allow at root** is enabled. + + ![Home Page Content Node](images/create-a-homepage-content-node.png) +5. Enter the **Name** for the content node. We are going to call this _Homepage_. + * The name will show up in the node list and will be used to create a URL for the page. Try to keep it short but descriptive. +6. Enter the following details: + + | Name | Description | + | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | + | Page Title | Welcome to Widgets Ltd | + | Body Text |

Lorem ipsum

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam et aliquet ante, ut eleifend libero.

  • Proin eleifend consequat nunc id vulputate.
  • Ut eget lobortis metus, non congue lorem.
  • Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.
  • Maecenas tempus non lectus rhoncus efficitur.

Sed est ligula, maximus in dolor sed, lacinia egestas ligula. Donec eu nisi lectus.

Morbi pharetra pulvinar arcu non gravida.

| + | Footer Text | Copyright Widgets Ltd 2024 | +7. Click **Save and Publish**. The content tree will reload with the homepage node. + + ![Home Page in Content Tree](images/homepage-in-content-tree.png) + +## Accessing the Frontend + +To view your content on the frontend: + +1. Go to the **Info** tab in the **Homepage** content node. +2. Click on the link under the **Links** section. + +The default Umbraco page is gone and we can see a basic unstyled page. We are getting there. + +{% hint style="info" %} +If you see a blank page, check if the template is entered and remember to save it. +{% endhint %} + +![An Unstyled Homepage](../../../../10/umbraco-cms/tutorials/creating-a-basic-website/images/figure-16-unstyled-homepage-v8.png) diff --git a/16/umbraco-cms/tutorials/creating-a-basic-website/css-and-images.md b/16/umbraco-cms/tutorials/creating-a-basic-website/css-and-images.md new file mode 100644 index 00000000000..4b5dd1ce618 --- /dev/null +++ b/16/umbraco-cms/tutorials/creating-a-basic-website/css-and-images.md @@ -0,0 +1,12 @@ +# CSS and Images + +Our homepage is currently missing the CSS and image files. To include these files: + +1. Navigate to the **MyCustomUmbracoProject** folder and the **Custom Umbraco Template** folder in File Explorer. +2. Copy the **css** and **images** folders from the _Custom Umbraco template_ folder and place it in the **wwwroot** folder inside the _MyCustomUmbracoProject_ folder. +3. Navigate to the **HomePage** template in the **Settings** section and ensure the stylesheet reference link is `/css/main.css`. + + ![Stylesheet reference](images/stylesheet-reference.png) +4. Using Chrome/Firefox/Edge Developer Tools, start or refresh your `http://localhost:xxxx.` + + The network tab should not report any missing CSS or images files. If the network tab reports any error, check for typos and if the folders are in the right places. diff --git a/16/umbraco-cms/tutorials/creating-a-basic-website/displaying-the-document-type-properties.md b/16/umbraco-cms/tutorials/creating-a-basic-website/displaying-the-document-type-properties.md new file mode 100644 index 00000000000..57163cb5765 --- /dev/null +++ b/16/umbraco-cms/tutorials/creating-a-basic-website/displaying-the-document-type-properties.md @@ -0,0 +1,44 @@ +# Displaying the Document Type Properties + +You might have noticed that the content we've added to the homepage is not being displayed. We need to wire up the Data Type properties to the template. + +Let’s look at our template and identify where the content should be displayed. + +![Where our Data Properties Content Should be Output](images/figure-17-where-our-data-fields-go-v8.png) + +The top arrow in this image is the _Page Title_ and the bottom arrow is the _Body Text_. The Footer is all the way at the bottom of the page. + +## Setting the Document Type Properties + +To set the Document Type properties: + +1. Go to **Settings**. +2. Open the **Homepage** template. +3. Scroll down to the `` section (around line 45) and highlight the text `“Welcome - UmbracoTV”` (around line 48). + + ![Replace page Title value](images/replace-hardcoded-text-with-umbraco-page-field.png) +4. Click **Insert** and select **Value**. +5. Select **Document Type** from the **Choose field** dropdown list. +6. Select **HomePage**. +7. Click **Choose**. +8. Select **pageTitle** field from the **HomePage** dropdown list. + + ![Page Title field](images/umbraco-page-field.png) +9. Click **Submit**. +10. Go to the content between the `
` tags (around line 60 to 77): +11. Highlight the content as shown in the figure. + + ![Replace Body Text value](images/replace-bodytext-with-page-field.png) +12. Repeat steps 4 to 9 to insert the **bodyText** field. +13. Go to the content between the `