src/docs/releases/3.0.0.md
Release date: Not yet released
Before upgrading from version 2 to v3, it is important to first compile your project using the latest available v2 version, resolve any warnings, and then proceed with the upgrade to v3.
Upgrade to .NET 10.0 : Orchard Core 3.0.0 requires .NET 10.0. Please ensure that your development environment and hosting infrastructure are compatible with this version of .NET before proceeding with the upgrade.
In the previous versions you need to use OrchardCore namespace in Razor pages or views whenever you want to access the content helpers that provided by IOrchardHelper which is unlike the other services that you can access directly without the need to use the namespace. In this version, we have removed the need to use the OrchardCore namespace in Razor pages or views to access the content helpers. You can now access the content helpers directly without the need to use the OrchardCore namespace.
The ContentRazorHelperExtensions has been replaced by ContentOrchardHelperExtensions.
For reliability and performance reasons, the liquid tag has been disabled by default in Liquid templates.
To re-enable the liquid tag, you can configure it in your appsettings.json:
{
"OrchardCore_Liquid": {
"AllowLiquidTags": true
}
}
Previously, the ILocalizationService interface had a static default method called GetAllCulturesAsync(), which returned a list of all cultures available in the system with their aliases.
This method has been replaced in the current version and has become an instance method, allowing developers to customize implementations instead of relying on the default ones.
Previously, we configured YesSql with the ReadUncommitted isolation level. We have now set ReadCommitted as the default and made it configurable through settings.
Previously, emails sent from Orchard Core could have either a plain text body, or an HTML body, but not both. Now, they can have both. This also brings some code-level API changes, see below.
When interacting with email-related services from code, MailMessage, the class representing an e-mail, exposed a string Body property. This could contain either plain text or HTML, which was indicated by IsHtmlBody.
These two properties have now been replaced by the TextBody and HtmlBody properties, which contains a plain text and/or HTML body.
Previously, the EmailResult class was used to represent the result of sending an email. This class has been replaced by Result, which provides a more consistent and flexible way to handle operation results.
The GraphQL libraries have been upgraded from version 7 to version 8. Below are important changes and considerations for your implementation:
Removal of Default Implementation:
The IContentTypeBuilder interface previously included a default implementation for the Clear() method. This implementation has been removed. If you have a custom implementation of IContentTypeBuilder, you must now provide your own Clear() method. The method can remain empty if no specific actions are needed.
Sealed Classes:
Several classes have been marked as sealed to prevent further inheritance. This change is intended to enhance stability and maintainability. The following classes are now sealed:
InputObjectGraphTypeObjectGraphType<>WhereInputObjectGraphTypeDynamicContentTypeBuilderIContentTypeBuilderGraphQLFilterISchemaBuilderIContentItemIndexHandler was renamed to IDocumentIndexHandler.IndexingConstants class was renamed to ContentIndexingConstants.BuildIndexContext class was renamed to BuildDocumentIndexContext.BuildDocumentIndexContext class, the ContentItem property was removed. Instead, you should use the new Record property. You may check if Record in ContentItem like this:if(context.Record is ContentItem contentItem) {
// Do something with contentItem
})
IIndexingTaskManager interface was changed to be use as a universal task manager not only for content items. If you want to queue the content items in the index manager, you'll need to pass Content in the category argument. You may also store non-content-items in the index manager by passing a different category name.!!! warning
The IndexingTask table in the database has been migrated to RecordIndexingTask to better reflect its purpose as a universal task manager. The IndexingTask table is kept intact to allow you to rollback your deployment if needed. If you no longer need that table, you can either manually delete it or create a migration in your project that deletes it.
We previously relied on the NEST library to interface with the Elasticsearch service. However, due to the deprecation of NEST, we have migrated to its successor, Elastic.Clients.Elasticsearch. As part of this transition, the following interfaces and classes have been removed:
IElasticAnalyzerIElasticSearchQueryService (use ElasticsearchQueryService as an alternative)ElasticAnalyzerTo ensure consistency across our codebase, all classes and interfaces are now prefixed with Elasticsearch instead of Elastic or ElasticSearch. The only exception to this naming convention is for settings, which have been retained to minimize further breaking changes.
Additionally, the OrchardCore.Search.Elasticsearch.Abstractions project has been removed, and the following classes have been relocated to the OrchardCore.Search.Elasticsearch.Core project:
ElasticsearchOptionsElasticsearchQueryResultsElasticsearchTopDocsThe method ExecuteQueryAsync(string indexName, Query query, List<SortOptions> sort, int from, int size) has been removed from the ElasticsearchQueryService class. In its place, we have introduced the new method GetContentItemIdsAsync(ElasticsearchSearchContext request), which streamlines the query execution process by encapsulating the necessary parameters within a single context object.
The latest update introduces support for Search Highlights in Elasticsearch queries. When highlights are enabled, Elasticsearch will return relevant text segments that match the search term, allowing for a more contextual and user-friendly search result display.
Here's an example of how to configure the Elasticsearch query to include highlights:
{
"query": {
"multi_match": {
"fields": [
"Content.ContentItem.FullText"
],
"query": "{{ term }}",
"fuzziness": "AUTO"
}
},
"highlight": {
"pre_tags": [
"<span style='background-color: #FFF3CD;'>"
],
"post_tags": [
"</span>"
],
"fields": {
"Content.ContentItem.FullText": {
"fragment_size": 150,
"number_of_fragments": 3
}
}
}
}
With this feature, Elasticsearch will return highlighted fragments wrapped in custom HTML tags, which can then be displayed in the Search module or other components. This enables the presentation of more relevant content that directly matches the search term.
For more information, check out the Elasticsearch documentation.
The latest update adds support for Total Hits Count in Elasticsearch queries. When a query includes track_total_hits: true, the response will report the total number of documents matching the query, rather than just the number of documents returned in the current page.
The signature in the ISearchService interface was changed from Task<SearchResult> SearchAsync(string indexName, string term, int start, int size) to Task<SearchResult> SearchAsync(IndexEntity index, string term, int start, int size).
The route /search/{indexName?} was change to accept index id instead of name. The route is now /search/{indexId?}. This change was made to ensure that the search functionality works correctly with the new IndexEntity class, which is now used to represent indexes in the system.
The default search settings was changed to require default index-id instead of a provider name.
With the improvement done to the Indexing module, managing Azure AI Search indexes is now part of the indexing module. This change reduced the code complexity and simplify the user experience since we now have one UI for all indexes. Another advantage is that we are now able to add data sources other than contents.
With the improvement done to the Indexing module, managing Lucene indexes is now part of the indexing module. This change reduced the code complexity and simplify the user experience since we now have one UI for all indexes. Another advantage is that we are now able to add data sources other than contents.
There are lots of binary breaking changes in the Azure Search AI module most won't impact you. The following change will impact you:
AzureAISearchPermissions.ManageAzureAISearchIndexes was removed and the AzureAISearchPermissions.ManageAzureAISearchISettings was added.Previously, the SmsResult class was used to represent the result of sending an SMS. This class has been replaced by Result, which provides a more consistent and flexible way to handle operation results.
In previous releases, taxonomy listings on the frontend used a simplified pager with only Next and Previous buttons. As of this release, the pager has been upgraded to a full-featured pagination control, allowing users to jump directly to specific pages.
If your project overrides the TermPart view and modifies the Model.Shape, ensure your customization is compatible with the new shape type introduced by this change.
Additionally, a new interface, IContentsTaxonomyListFilter, has been introduced. This interface allows developers to customize or modify the query used when retrieving taxonomies, providing greater flexibility and control over taxonomy listings.
The user registration and login functionalities have been refactored for better extensibility:
Registration Improvements:
IRegistrationFormEvents interface now includes Task RegisteringAsync(UserRegisteringContext context) for streamlined customization.RegistrationFormEventsBase, allows developers to override only necessary methods.Login Improvements:
ILoginFormEvent interface has a new method: Task<IActionResult> ValidatingLoginAsync(IUser user).LoginFormEventBase class enables overriding relevant methods. Note that the base implementation of LoggingInAsync() has been removed; you must now implement this method if using LoginFormEventBase.User Service Update:
IUserService interface: Task<IUser> RegisterAsync(RegisterUserForm model, Action<string, string> reportError) facilitates registration with error reporting.These enhancements make the user management system more modular and customizable.
The following obsolete settings were removed from RegistrationSettings class
NoPasswordForExternalUsersNoUsernameForExternalUsersNoEmailForExternalUsersUseScriptToGenerateUsernameGenerateUsernameScriptUsersCanRegisterThe following obsolete settings were removed from LoginSettings class
UseExternalProviderIfOnlyOneDefinedUseScriptToSyncRolesSyncRolesScriptThe ExternalLogin action has been removed from the Account controller. If you are using a custom Login.cshtml view or Login template, please update the external login form action. As of this update, the ExternalLogin action has been relocated to the ExternalAuthentications controller.
The AssignRoleToUsers permission is no longer implicitly granted by EditUsers. To maintain the same behavior, make sure to explicitly assign the AssignRoleToUsers permission to any role that already has the EditUsers permission.
The Administrator role no longer registers permission-based claims by default during login. This means that directly checking for specific claims in Liquid, such as:
{% assign isAuthorized = User | has_claim: "Permission", "AccessAdminPanel" %}
will return false for administrators, even though they still have full access. Non-admin users, however, may return true if they have the claim. It's important to use the has_permission filter for permission checks going forward:
{% assign isAuthorized = User | has_permission: "AccessAdminPanel" %}
To simplify the LoginForm.Edit.cshtml view, the following code has been moved to Views/Account/Login.cshtml:
<h1 class="fs-4">@T["Log in"]</h1>
<hr />
To centralize user activation and deactivation, the "Is enabled?" checkbox has been removed from the Edit User form. Instead, you can now enable or disable a user directly from the Users List by clicking the Actions button next to the user and selecting "Enable" or "Disable".
To support this change, two new methods—EnableAsync() and DisableAsync()—have been added to IUserService, providing a more standardized approach to managing user status.
In the previous implementation, the ReCaptcha module supported two modes: AlwaysShow and PreventRobots. To simplify the module and enhance integration, the PreventRobots mode and its related components have been removed. Going forward, only the AlwaysShow mode will be supported.
As part of this update, the following components have been deprecated and removed:
IDetectRobotsIPAddressRobotDetectorReCaptchaModeFurthermore, the ReCaptchaService class has been sealed to prevent inheritance. The following methods have also been removed:
MaybeThisIsARobotIsThisARobotThisIsAHumanPreviously, the FormReCaptcha view was available to inject a ReCaptcha challenge from any display driver. This view has been removed. The recommended approach now is to return a shape directly, as shown below:
return Dynamic("ReCaptcha", (m) =>
{
m.language = CultureInfo.CurrentUICulture.Name;
}).Location("Content:after");
Instead of using this approach:
return View("FormReCaptcha", model).Location("Content:after");
If you still need to render ReCaptcha using the deprecated FormReCaptcha, you can manually add the FormReCaptcha.cshtml view to your project. Here's the code for this:
<div class="mb-3">
<captcha mode="AlwaysShow" language="@Orchard.CultureName()" />
</div>
This change is designed to simplify your integration process and make it easier to manage ReCaptcha usage in your project.
IContentDefinitionService Moved to AbstractionsThe IContentDefinitionService interface has been moved from OrchardCore.ContentTypes module to OrchardCore.ContentTypes.Abstractions. This allows other modules to use content definition operations without requiring a reference to the full OrchardCore.ContentTypes module.
The following methods now use DTOs instead of ViewModels:
AlterFieldAsync - Now takes AlterFieldContext instead of EditPartViewModel and EditFieldViewModelAlterTypePartAsync - Now takes AlterTypePartContext instead of EditTypePartViewModelThe following methods have been removed from IContentDefinitionService and are now private helpers within the AdminController:
LoadTypesAsync, GetTypesAsync, LoadTypeAsync, GetTypeAsyncLoadPartsAsync, GetPartsAsync, LoadPartAsync, GetPartAsyncGetFieldsAsyncPreviously, the IContentHandler.UpdatingAsync and IContentHandler.UpdatedAsync methods were triggered for both creation and update events. This behavior has been corrected in this release. Now, if you are modifying or altering content items within the UpdatingAsync or UpdatedAsync handlers, you must also implement the Creating or Created events to maintain the previous functionality.
Additionally, when manually creating or updating content items in your project, ensure that you use ContentManager.CreateAsync exclusively for creating new items and ContentManager.UpdateAsync for updating existing ones. This guarantees that the Creating, Created, Updating, and Updated events are triggered appropriately.
A new CollapseContainedItems setting has been added to BagPartSettings and FlowPartSettings, allowing users to control whether contained items are collapsed or expanded by default. To ensure consistency, both BagPart and FlowPart now default to expanding contained items when the page loads.
We have migrated to using the AspNet.Security.OAuth.GitHub package to simplify and reduce the amount of custom code required for GitHub integration. As part of this change, the following classes have been removed:
GithubDefault — replaced by the library's GitHubAuthenticationDefaults.GithubOptions — replaced by the library's GitHubAuthenticationOptions.GithubHandler — replaced by the library's GitHubAuthenticationHandler.In addition, the IGithubAuthenticationService interface has been removed.
If you need to access the GitHub authentication settings, you can now do so by:
IOptions<GitHubAuthenticationOptions>, orISiteService to retrieve the site's configuration.To resolve CS0433 naming conflicts, the OrchardRazorHelperExtensions classes across multiple modules have been renamed to follow a consistent naming pattern:
OrchardCore.ContentManagement.Display → ContentOrchardRazorHelperExtensionsOrchardCore.Media → MediaOrchardRazorHelperExtensionsOrchardCore.Shortcodes → ShortcodesOrchardRazorHelperExtensionsImpact: Extension methods continue to work identically in Razor views (@Orchard.AssetUrl(), @Orchard.ConsoleLog(), etc.). This change only affects projects that directly reference these class names in code.
The Display Management subsystem received performance-focused refactoring. The following API changes may require code updates:
FeatureShapeDescriptor is now sealed. Do not inherit from it.ShapeMetadata.BindingSources.ShapeDescriptor.BindingSources and the corresponding override on descriptor index types. If you relied on these, inspect ShapeDescriptor.Bindings to infer binding sources.ShapeBinding is now sealed and its BindingAsync property is no longer virtual. Do not inherit from ShapeBinding or override its members.ShapeDescriptor.BindingSource and ShapeDescriptor.Binding are no longer virtual. Use the ShapeAlterationBuilder events or display drivers to customize binding behavior.ShapeDescriptor.Binding now returns null if no binding exists instead of throwing. Add null checks when accessing a shape's binding.ShapeDescriptor.Placement now lazily falls back to the default placement action if not set. This should be transparent, but code that assumed Placement was preset may need to handle a possibly null delegate before first access.ShapeDescriptorIndex is now sealed. Do not inherit from it.The following APIs have been removed from Orchard Core:
OrchardCore.Modules.Manifest.FeatureAttribute.GetValues(): This protected method has been removed. If you have custom implementations that rely on this method, you will need to refactor your code to avoid using it.The following interfaces, classes, and default implementations have been removed to clean up deprecated code:
IDataMigrationManager.UpdateAsync(string feature) method with its default implementation has been removed. Use UpdateAsync(params string[] features) instead.ILocalClock.LocalNowAsync property has been removed along with its default implementation in GetLocalNowAsync(). Use GetLocalNowAsync() method instead.ISmtpService interface has been removed. Use IEmailService instead.SmtpResult class has been removed. Use EmailResult instead.SmtpService class has been removed. Use the email providers registered with IEmailService instead.IAdminMenuPermissionService interface has been removed. Use IPermissionService instead.AdminMenuPermissionService class has been removed.CommonPermissions class in OrchardCore.Roles namespace has been removed. The ManageRoles permission is now available in RolesPermissions.ManageRoles. For assigning roles to users, use OrchardCore.Users.UsersPermissions.AssignRoleToUsers or OrchardCore.Users.UsersPermissions.CreateAssignRoleToUsersPermission(roleName).StringExtensions.IsLetter(char) extension method has been removed from OrchardCore.ContentManagement.Utilities. Use char.IsLetter() instead.StringExtensions.IsSpace(char) extension method has been removed from OrchardCore.ContentManagement.Utilities. Use char.IsWhiteSpace() instead.MediaTypeNamesExtended has been added to extend the MediaTypeNames class with additional media type constants. This allows for easier reference to a wider range of media types within the application.The Localization module allows the user to control the fallback to the parent culture when a translation is not available in the current culture. The default behavior in ASP.NET Core is that the culture automatically falls back to its parent if not exist, but now you have the control to make this feature on or off.
The Reverse Proxy module has been updated to include a new setting called Known Networks & Proxies. This setting allows you to specify trusted networks and proxies that can be used to determine the user's IP address. This is particularly useful when your application is behind a reverse proxy or load balancer, ensuring that the correct client IP address is captured.
The Roles recipe now includes the ability to define specific permission behaviors, allowing you to control how permissions are managed within a role. The following behaviors are available:
Permissions collection. This is the default behavior.Permissions collection to the role, but only if they do not already exist. It does not affect the existing permissions.Permissions collection from the role's existing permissions.For more info about the new PermissionBehavior, check out the documentation.
In the past, administrators could confirm user's email by editing their details and checking the Confirm Email box. This process has now been updated.
To confirm a user's email, follow these steps:
This change streamlines the email confirmation process by moving it out of the user edit form, offering a more straightforward approach to user management.
!!! note The Confirm email option will only be visible if the user has not yet been confirmed.
The Search Module now supports Highlights. If the search service provider supports Highlights, they will be rendered by default. If Highlights are unavailable, the module will fall back to displaying the ContentItem in a Summary display type.
A new ReCaptcha shape has been introduced, enabling you to render the ReCaptcha challenge using a customizable shape. For more details, please refer to the documentation.
The Menu module enables you to build frontend menus for your users through a user-friendly interface. We've enhanced this feature by allowing you to require one or more permissions before a menu item becomes visible to the user.
If you're using a custom MenuItem in your project and want to incorporate this functionality, you can achieve it by attaching the MenuItemPermissionPart to your custom MenuItem content type.
When caching your menu, it's crucial to include the cache-context to ensure the menu is properly cached and invalidated based on the user's roles. This ensures the menu is displayed correctly for each logged-in user, based on their specific roles.
For example, here's how you can add the menu with cache-context using Razor:
<menu alias="alias:main-menu" cache-id="main-menu" cache-fixed-duration="00:05:00" cache-tag="alias:main-menu" cache-context="user.roles" />
Notice the cache-context="user.roles" attribute.
Alternatively, here's how you can implement the same functionality using Liquid:
{% shape "menu", alias: "alias:main-menu", cache_id: "main-menu", cache_fixed_duration: "00:05:00", cache_tag: "alias:main-menu", cache_context: "user.roles" %}
Again, notice the inclusion of cache_context: "user.roles".
By default, permissions are enabled for new tenants. However, if you'd like to add permissions to an existing tenant, you can use the "Add Permissions to Menus" recipe either through the UI or by executing the recipe programmatically as shown below:
{
"steps": [
{
"name": "recipes",
"Values": [
{
"executionid": "MenuAddPermissions",
"name": "MenuAddPermissions"
}
]
}
]
}
!!! note Be sure to update all instances where you create a menu shape by adding the cache-context attribute. This ensures the menu is properly cached and tailored based on the user's roles.
A new option, Check content permissions, has been added to the Content Menu Item. This feature allows you to control the visibility of a menu item based on the user's permissions. When this option is enabled, the system ensures that the current user has the View Content permission for the selected item before displaying it.
Orchard Core now features a new Asset Manager, set to gradually replace the Gulp pipeline. While the Gulp pipeline remains for backward compatibility during the transition and refactoring period, a comprehensive documentation is available to guide users on the new Asset Manager.
The Gulp pipeline is being phased out as it is no longer suitable for bundling assets with tools like Webpack. The new Asset Manager leverages Concurrently, enabling the execution of shell commands directly from Node.js.This provides flexibility to use APIs from bundlers, compilers, and transpilers to perform various actions seamlessly.
For more information, see the corresponding page.
The Admin Theme ResourceManagementOptionsConfiguration.cs file is now changed. We renamed "admin" assets to "the-admin" for ease of maintenance of dependencies in the solution.
The Indexing module has been refactored to improve extensibility and maintainability. Key enhancements include:
Introduced new Vector and Complex data types for indexing, enabling advanced search capabilities.
All indexes from multiple providers—such as Elasticsearch, Azure AI Search, and Lucene—are now centrally registered and managed through the new Indexes interface, accessible via Search > Indexes.
The following recipe steps were added to allow you to manage any index profile.
CreateOrUpdateIndexProfile allows you to create or update index profile.ResetIndex allows you to reset an index.RebuildIndex allows you to rebuild an index.Search index-specific permissions were centralized into the Indexing module. Thus every index gets a dynamically created "Query 'index name' Index" permission. Use these to allow specific user roles to query the index, and thus also use the frontend search feature.
!!! warning You need to update user roles and recipes to use the new permissions. If you e.g. allow Anonymous users to use the frontend search feature, not doing so will prevent such users from searching.
To create a custom source, you'll need to implement the IIndexManager, IDocumentIndexManager and IIndexNameProvider interfaces and register them as following
To register a new source, you can add the following code to your Startup.cs file:
// Currently we support 'AzureAISearch', 'Elasticsearch', and 'Lucene' providers.
services.AddIndexingSource<CustomSourceIndexManager, CustomSourceDocumentIndexManager, CustomSourceIndexNameProvider>("ProviderName", "CustomSource", o =>
{
o.DisplayName = S["Custom Source in Provider"];
o.Description = S["Create a Provider index based on custom source."];
});
The following recipe steps were deprecated:
elastic-index and ElasticIndexSettings steps, which was used to create a Elasticsearch index. Instead, please use the CreateOrUpdateIndexProfile step to create or update an index.elastic-index-reset step, which was used to create a Elasticsearch query. Instead, please use the ResetIndex step to create a query.elastic-index-rebuild step, which was used to create a Elasticsearch query. Instead, please use the RebuildIndex step to create a query.!!! note
The elastic-index, ElasticIndexSettings, elastic-index-reset, and elastic-index-rebuild steps are still available for backward compatibility, but they are deprecated and will be removed in a future release. We recommend you update your recipes by setting up a site with your current ones, then creating a Deployment Plan with the new steps to export the configuration in the new format.
Now, the Elasticsearch module supports multiple authentication types for connecting to the Elasticsearch server. You can now choose between the following authentication methods:
Basic: For basic authentication, you need to provide a Username and Password.ApiKey: For API key authentication, you need to provide an ApiKey.Base64ApiKey: For base64-encoded API key authentication, you need to provide a Base64ApiKey API key.KeyIdAndKey: For key ID and key authentication, you need to provide a KeyId and Key.The following recipe steps were deprecated:
lucene-index and LuceneIndexSettings steps, which was used to create a Lucene index. Instead, please use the CreateOrUpdateIndexProfile step to create or update an index.lucene-index-reset step, which was used to create a Lucene query. Instead, please use the ResetIndex step to create a query.lucene-index-rebuild step, which was used to create a Lucene query. Instead, please use the RebuildIndex step to create a query.!!! note
The lucene-index, LuceneIndexSettings, lucene-index-reset, and lucene-index-rebuild steps are still available for backward compatibility, but they are deprecated and will be removed in a future release. We recommend you update your recipes by setting up a site with your current ones, then creating a Deployment Plan with the new steps to export the configuration in the new format.
Font Awesome was updated to version 7.0.0. Some changes may be required in your templates. Please follow the instructions from the changelog.
A new PlacementLocationBuilder fluent API has been added as a type-safe alternative to manually constructing placement location strings in display drivers. The builder uses a single class where all methods return the same instance for easy chaining, following the placement nesting hierarchy (Zone → Tab → Card → Column).
// Before (string-based — error-prone and hard to read):
.Location("Parameters:5#Settings;1%Details;2|Left_9;3")
// After (fluent API — type-safe and self-documenting):
.Location(l => l
.Zone("Parameters", "5")
.Tab("Settings", "1")
.Card("Details", "2")
.Column("Left", "3", "9"))
Each method in the chain returns the same builder instance, so all methods are available at any point after .Zone(). Levels can be skipped (e.g., .Zone().Card() without a .Tab() is valid).
For more details, see the Placement documentation.
The Admin → Media → Library screen now includes an "Available Storage" indicator. If the media items are stored on the local file system, it automatically shows the free disk space on the drive used for storage. The available storage is checked when uploading or copying files in the media store, to give a more informative warning when the space is exhausted.
If you use a different underlying file store (e.g., Azure Blob storage, Amazon AWS), or if you want to enforce additional storage constraints (i.e., usage quotas), that's also possible. Create a new event handler that implements the IMediaEventHandler.MediaPermittedStorageAsync(MediaPermittedStorageContext) method. Note that if you want to use multiple event handlers, you should update the PermittedStorage value using the MediaPermittedStorageContext.Constrain(long) method rather than manually editing the PermittedStorage value.
A new Razor helper method has been introduced to simplify morphing shapes directly within Razor views. You can now use @DisplayAsAsync(Model, "NewShapeType") to render the current model as a different shape type.
Many type commonly used by classes can be sealed, which improves runtime performance. While it's not mandatory, we recommend that you consider applying this improvement to your own extensions as well. We've implemented this enhancement in pull request #16897.
IConfigureOptions<ResourceManagementOptions> ImplementationsWhen adding an IConfigureOptions<ResourceManagementOptions> implementation, used to add static resources and commonly named ResourceManagementOptionsConfiguration, you previously had to do the following in the Startup classes:
services.AddTransient<IConfigureOptions<ResourceManagementOptions>, ResourceManagementOptionsConfiguration>();
To simplify this, we introduced a new extension method to do the same in a shorter form:
services.AddResourceConfiguration<ResourceManagementOptionsConfiguration>();
You can utilize this in your codebase by searching the AddTransient<IConfigureOptions<ResourceManagementOptions>, (.+)>\(\) regex pattern and replacing it with AddResourceConfiguration<$1>(). Projects using this have to reference the OrchardCore.ResourceManagement package.
A new shape, UserDisplayName has been introduced to render a user's display name. For more info, see the documentation.
Previously, the IContentManager.ValidateAsync() method would silently call ISession.CancelAsync() when validation failed, cancelling the entire database session without the caller's knowledge. This could discard unrelated pending changes from other components sharing the same session.
Now, ValidateAsync() only performs validation and returns the result. It no longer cancels the session on failure. Callers who need to cancel the session on validation failure must do so explicitly:
var result = await _contentManager.ValidateAsync(contentItem);
if (!result.Succeeded)
{
await _session.CancelAsync();
}
The IContentManager.RemoveAsync() method now supports cancelable deletes and returns a bool indicating whether the delete operation actually occurred or was cancelled by a handler.
Introduces a lightweight Result pattern to represent operation outcomes without throwing exceptions for control flow. The new Result and Result<T> types surface success/failure and carry zero or more ResultError entries (supports LocalizedString for localized messages).