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.
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.
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:
TheIContentTypeBuilder
interface previously included a default implementation for theClear()
method. This implementation has been removed. If you have a custom implementation ofIContentTypeBuilder
, you must now provide your ownClear()
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:- All implementations of
InputObjectGraphType
- All implementations of
ObjectGraphType<>
- All implementations of
WhereInputObjectGraphType
- All implementations of
DynamicContentTypeBuilder
- All implementations of
IContentTypeBuilder
- All implementations of
GraphQLFilter
- All implementations of
ISchemaBuilder
- All implementations of
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:
IElasticAnalyzer
IElasticSearchQueryService
(useElasticsearchQueryService
as an alternative)ElasticAnalyzer
To 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:
ElasticsearchOptions
ElasticsearchQueryResults
ElasticsearchTopDocs
The 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 index management UI now supports multiple data sources. When the Contents feature is enabled, the Contents
source is automatically added, allowing you to create indexes based on content types. To maintain backward compatibility, we've also introduced a migration process.
To add a custom source from your project, configure it as follows:
services.Configure<AzureAISearchOptions>(options =>
{
options.AddIndexSource("CustomSource", o =>
{
o.DisplayName = S["Custom Source"];
o.Description = S["Create an index based on custom data."];
});
});
We've introduced the IAzureAISearchIndexSettingsHandler
interface, enabling deeper customization of index settings. To simplify integration, you can extend AzureAISearchIndexSettingsHandlerBase
. Additionally, the new IAzureAISearchEvents
interface provides hooks for handling search-related events.
- UI routes now accept index IDs instead of index names.
- The following methods in
AzureAISearchIndexSettingsService
now useid
instead of an index name:GetAsync(id)
DeleteAsync(id)
- New methods added to
AzureAISearchIndexSettingsService
:Task<AzureAISearchIndexSettings> NewAsync(string source, JsonNode data = null)
Task<ValidationResultDetails> ValidateAsync(AzureAISearchIndexSettings settings)
Task SynchronizeAsync(AzureAISearchIndexSettings settings)
These enhancements provide greater flexibility, improved maintainability, and an easier integration experience.
!!! note
If you use the azureai-index-create
recipe to create indexes, make sure to update your recipe by adding "Source": "Contents"
to each index. Otherwise, the step will fail due to a missing source
.
The user registration and login functionalities have been refactored for better extensibility:
-
Registration Improvements:
- The
IRegistrationFormEvents
interface now includesTask RegisteringAsync(UserRegisteringContext context)
for streamlined customization. - A new base class,
RegistrationFormEventsBase
, allows developers to override only necessary methods.
- The
-
Login Improvements:
- The
ILoginFormEvent
interface has a new method:Task<IActionResult> ValidatingLoginAsync(IUser user)
. - The
LoginFormEventBase
class enables overriding relevant methods. Note that the base implementation ofLoggingInAsync()
has been removed; you must now implement this method if usingLoginFormEventBase
.
- The
-
User Service Update:
- A new method in
IUserService
interface:Task<IUser> RegisterAsync(RegisterUserForm model, Action<string, string> reportError)
facilitates registration with error reporting.
- A new method in
These enhancements make the user management system more modular and customizable.
The following obsolete settings were removed from RegistrationSettings
class
NoPasswordForExternalUsers
NoUsernameForExternalUsers
NoEmailForExternalUsers
UseScriptToGenerateUsername
GenerateUsernameScript
UsersCanRegister
The following obsolete settings were removed from LoginSettings
class
UseExternalProviderIfOnlyOneDefined
UseScriptToSyncRoles
SyncRolesScript
The 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 />
If you are overriding the Views/Account/Login.cshtml
view, you may want to add the above code to your custom version for consistency.
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:
IDetectRobots
IPAddressRobotDetector
ReCaptchaMode
Furthermore, the ReCaptchaService
class has been sealed to prevent inheritance. The following methods have also been removed:
MaybeThisIsARobot
IsThisARobot
ThisIsAHuman
Previously, 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.
Previously, 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.
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:
- Replace: This behavior removes all existing permissions associated with the role and replaces them with the new permissions from the
Permissions
collection. This is the default behavior. - Add: This behavior adds the new permission(s) from the
Permissions
collection to the role, but only if they do not already exist. It does not affect the existing permissions. - Remove: This behavior removes the specified permission(s) in the
Permissions
collection from the role’s existing permissions.
For more info about the new PermissionBehavior
, check out the documentation.
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.
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.
When 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.