diff --git a/Directory.Packages.props b/Directory.Packages.props
index 83bae6b88b..62110b2326 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -29,7 +29,7 @@
-
+
@@ -113,10 +113,10 @@
-
-
-
-
+
+
+
+
@@ -128,11 +128,11 @@
-
-
-
-
-
+
+
+
+
+
diff --git a/docs/en/Community-Articles/2025-06-12-fix-mongodb-guid-abp-v9.2.0-upgrade/mongo-db.jpg b/docs/en/Community-Articles/2025-06-12-fix-mongodb-guid-abp-v9.2.0-upgrade/mongo-db.jpg
new file mode 100644
index 0000000000..a829bc28b4
Binary files /dev/null and b/docs/en/Community-Articles/2025-06-12-fix-mongodb-guid-abp-v9.2.0-upgrade/mongo-db.jpg differ
diff --git a/docs/en/Community-Articles/2025-06-12-fix-mongodb-guid-abp-v9.2.0-upgrade/post.md b/docs/en/Community-Articles/2025-06-12-fix-mongodb-guid-abp-v9.2.0-upgrade/post.md
new file mode 100644
index 0000000000..ddae9a3704
--- /dev/null
+++ b/docs/en/Community-Articles/2025-06-12-fix-mongodb-guid-abp-v9.2.0-upgrade/post.md
@@ -0,0 +1,93 @@
+# Solving MongoDB GUID Issues After an ABP Framework Upgrade
+
+So, you've just upgraded your ABP Framework application to a newer version (like v9.2.0+) and suddenly, your application can't read data from its MongoDB database. You're seeing strange deserialization errors, especially related to `Guid` types. What's going on?
+
+You've likely run into a classic compatibility issue with the MongoDB .NET driver.
+
+### The Problem: Legacy vs. Standard GUIDs
+
+Here's the short version:
+
+* **Old MongoDB Drivers** (used in older ABP versions) stored `Guid` values in a format called `CSharpLegacy`.
+* **New MongoDB Drivers** (v3.0+), now default to a universal `Standard` format.
+
+When your newly upgraded app tries to read old data, the new driver expects the `Standard` format but finds `CSharpLegacy`. The byte orders don't match, and... boom. Deserialization fails.
+
+The ABP Framework team has an excellent official guide covering this topic in detail. We highly recommend reading their **[MongoDB Driver 2 to 3 Migration Guide](https://abp.io/docs/latest/release-info/migration-guides/MongoDB-Driver-2-to-3)** for a full understanding.
+
+Our tip below serves as a fast, application-level fix if you need to get your system back online quickly without performing a full data migration.
+
+### The Quick Fix: Tell the Driver to Use the Old Format
+
+Instead of changing your data, you can simply tell the new driver to continue using the old `CSharpLegacy` format for all `Guid` and `Guid?` properties. This provides immediate backward compatibility without touching your database.
+
+It’s a simple, two-step process.
+
+#### Step 1: Create a Custom Convention
+
+First, create this class in your `.MongoDb` project. It tells the serializer how to handle `Guid` types.
+
+```csharp
+using MongoDB.Bson;
+using MongoDB.Bson.Serialization;
+using MongoDB.Bson.Serialization.Conventions;
+using MongoDB.Bson.Serialization.Serializers;
+using System;
+
+public class LegacyGuidConvention : ConventionBase, IMemberMapConvention
+{
+ public void Apply(BsonMemberMap memberMap)
+ {
+ if (memberMap.MemberType == typeof(Guid))
+ {
+ memberMap.SetSerializer(new GuidSerializer(GuidRepresentation.CSharpLegacy));
+ }
+ else if (memberMap.MemberType == typeof(Guid?))
+ {
+ var guidSerializer = new GuidSerializer(GuidRepresentation.CSharpLegacy);
+ var nullableGuidSerializer = new NullableSerializer(guidSerializer);
+ memberMap.SetSerializer(nullableGuidSerializer);
+ }
+ }
+}
+```
+
+#### Step 2: Register the Convention at Startup
+
+Now, register this convention in your `YourProjectMongoDbModule.cs` file. Add this code to the top of the `ConfigureServices` method. This ensures your rule is applied globally as soon as the application starts.
+
+```csharp
+using Volo.Abp.Modularity;
+using MongoDB.Bson.Serialization;
+using MongoDB.Bson.Serialization.Conventions;
+
+public class YourProjectMongoDbModule : AbpModule
+{
+ public override void ConfigureServices(ServiceConfigurationContext context)
+ {
+ // Fix Start
+ var conventionPack = new ConventionPack { new LegacyGuidConvention() };
+ ConventionRegistry.Register(
+ "LegacyGuidConvention",
+ conventionPack,
+ t => true); // Apply to all types
+ // Fix End
+
+ // ... Your existing ConfigureServices code
+ }
+}
+```
+
+### An Alternative to Full Data Migration
+
+It's important to note that the method described here is an **application-level fix**. It's a fantastic alternative to performing a full data migration, which involves writing scripts to convert every legacy GUID in your database.
+
+If you are interested in the more permanent, data-centric approach, the ABP.IO community has a detailed guide on [**Migrating MongoDB GUIDs from Legacy to Standard Format**](https://abp.io/community/articles/migrating-mongodb-guids-from-legacy-to-standard-format-mongodb-v2-to-v3-dqwybdtw).
+
+Our quick fix is ideal for getting a system back online fast or when a database migration is too complex. The full migration is better for long-term standards compliance. Choose the path that best fits your project's needs!
+
+### That's It!
+
+Restart your application, and the errors should be gone. Your app can now correctly read its old `Guid` data, and it will continue to write new data in the same legacy format, ensuring consistency.
+
+This approach is a lifesaver for existing projects, saving you from a risky and time-consuming data migration. For brand-new projects, you might consider starting with the `Standard` representation, but for everything else, this is a clean and effective fix. Happy coding!
diff --git a/docs/en/release-info/release-notes.md b/docs/en/release-info/release-notes.md
index 4c6bec206e..ce35f8b87c 100644
--- a/docs/en/release-info/release-notes.md
+++ b/docs/en/release-info/release-notes.md
@@ -8,9 +8,9 @@ Also see the following notes about ABP releases:
* [Change logs for ABP pro packages](https://abp.io/pro-releases)
-## 9.2 (2025-03-25)
+## 9.2 (2025-06-02)
-This is currently a RC (release-candidate) and you can see the detailed **[blog post / announcement](https://abp.io/community/articles/abp-platform-9.2-rc-has-been-released-jpq072nh)** for the v9.2 release.
+See the detailed **[blog post / announcement](https://abp.io/community/articles/announcing-abp-9-2-stable-release-061qmtzb)** for the v9.2 release.
* Added `ApplicationName` Property to Isolate Background Jobs & Background Workers
* Docs Module: Added "Alternative Words" to Filter Items
diff --git a/framework/src/Volo.Abp.AspNetCore/Microsoft/AspNetCore/RequestLocalization/DefaultAbpRequestLocalizationOptionsProvider.cs b/framework/src/Volo.Abp.AspNetCore/Microsoft/AspNetCore/RequestLocalization/DefaultAbpRequestLocalizationOptionsProvider.cs
index bd767f93fb..cf9f128852 100644
--- a/framework/src/Volo.Abp.AspNetCore/Microsoft/AspNetCore/RequestLocalization/DefaultAbpRequestLocalizationOptionsProvider.cs
+++ b/framework/src/Volo.Abp.AspNetCore/Microsoft/AspNetCore/RequestLocalization/DefaultAbpRequestLocalizationOptionsProvider.cs
@@ -4,7 +4,6 @@ using System.Globalization;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
-using DeviceDetectorNET;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Localization;
using Microsoft.Extensions.DependencyInjection;
diff --git a/framework/src/Volo.Abp.AspNetCore/Volo.Abp.AspNetCore.csproj b/framework/src/Volo.Abp.AspNetCore/Volo.Abp.AspNetCore.csproj
index d88febbfe9..886e1ed700 100644
--- a/framework/src/Volo.Abp.AspNetCore/Volo.Abp.AspNetCore.csproj
+++ b/framework/src/Volo.Abp.AspNetCore/Volo.Abp.AspNetCore.csproj
@@ -31,7 +31,7 @@
-
+
diff --git a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/AbpAspNetCoreModule.cs b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/AbpAspNetCoreModule.cs
index 64fab10ac6..405e49753c 100644
--- a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/AbpAspNetCoreModule.cs
+++ b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/AbpAspNetCoreModule.cs
@@ -4,6 +4,7 @@ using Microsoft.AspNetCore.Hosting.StaticWebAssets;
using Microsoft.AspNetCore.RequestLocalization;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.FileProviders;
+using MyCSharp.HttpUserAgentParser.DependencyInjection;
using Volo.Abp.AspNetCore.Auditing;
using Volo.Abp.AspNetCore.VirtualFileSystem;
using Volo.Abp.Auditing;
@@ -59,6 +60,8 @@ public class AbpAspNetCoreModule : AbpModule
context.Services.AddAbpDynamicOptions();
StaticWebAssetsLoader.UseStaticWebAssets(context.Services.GetHostingEnvironment(), context.Services.GetConfiguration());
+
+ context.Services.AddHttpUserAgentCachedParser();
}
private static void AddAspNetServices(IServiceCollection services)
diff --git a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/WebClientInfo/HttpContextWebClientInfoProvider.cs b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/WebClientInfo/HttpContextWebClientInfoProvider.cs
index 6e450efbcd..b34a9752f7 100644
--- a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/WebClientInfo/HttpContextWebClientInfoProvider.cs
+++ b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/WebClientInfo/HttpContextWebClientInfoProvider.cs
@@ -1,7 +1,8 @@
using System;
-using DeviceDetectorNET;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
+using MyCSharp.HttpUserAgentParser;
+using MyCSharp.HttpUserAgentParser.Providers;
using Volo.Abp.DependencyInjection;
namespace Volo.Abp.AspNetCore.WebClientInfo;
@@ -11,13 +12,16 @@ public class HttpContextWebClientInfoProvider : IWebClientInfoProvider, ITransie
{
protected ILogger Logger { get; }
protected IHttpContextAccessor HttpContextAccessor { get; }
+ protected IHttpUserAgentParserProvider HttpUserAgentParser { get; }
public HttpContextWebClientInfoProvider(
ILogger logger,
- IHttpContextAccessor httpContextAccessor)
+ IHttpContextAccessor httpContextAccessor,
+ IHttpUserAgentParserProvider httpUserAgentParser)
{
Logger = logger;
HttpContextAccessor = httpContextAccessor;
+ HttpUserAgentParser = httpUserAgentParser;
}
public string? BrowserInfo => GetBrowserInfo();
@@ -46,27 +50,21 @@ public class HttpContextWebClientInfoProvider : IWebClientInfoProvider, ITransie
protected virtual string? GetDeviceInfo()
{
- string? deviceInfo = null;
- var deviceDetector = new DeviceDetector(GetBrowserInfo());
- deviceDetector.Parse();
- if (!deviceDetector.IsParsed())
+ var browserInfo = GetBrowserInfo();
+ if (browserInfo.IsNullOrWhiteSpace())
{
- return deviceInfo;
- }
-
- var osInfo = deviceDetector.GetOs();
- if (osInfo.Success)
- {
- deviceInfo = osInfo.Match.Name;
+ return null;
}
- var clientInfo = deviceDetector.GetClient();
- if (clientInfo.Success)
+ var httpUserAgentInformation = HttpUserAgentParser.Parse(browserInfo);
+ switch (httpUserAgentInformation.Type)
{
- deviceInfo = deviceInfo.IsNullOrWhiteSpace() ? clientInfo.Match.Name : deviceInfo + " " + clientInfo.Match.Name;
+ case HttpUserAgentType.Browser:
+ case HttpUserAgentType.Robot:
+ return (httpUserAgentInformation.Platform.HasValue ? httpUserAgentInformation.Platform.Value.Name + " " : string.Empty) + httpUserAgentInformation.Name;
+ case HttpUserAgentType.Unknown:
+ default:
+ return httpUserAgentInformation.UserAgent;
}
-
- return deviceInfo;
}
-
}
diff --git a/framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/AbpBackgroundJobOptions.cs b/framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/AbpBackgroundJobOptions.cs
index f63bc2bb88..1a6cb6a9e9 100644
--- a/framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/AbpBackgroundJobOptions.cs
+++ b/framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/AbpBackgroundJobOptions.cs
@@ -68,7 +68,7 @@ public class AbpBackgroundJobOptions
public void AddJob(Type jobType)
{
- AddJob(new BackgroundJobConfiguration(jobType, GetBackgroundJobName(jobType)));
+ AddJob(new BackgroundJobConfiguration(jobType, GetBackgroundJobName(BackgroundJobArgsHelper.GetJobArgsType(jobType))));
}
public void AddJob(BackgroundJobConfiguration jobConfiguration)
diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/FakeAuthenticationScheme.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/FakeAuthenticationScheme.cs
index 641bbe03d7..0f9da48f68 100644
--- a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/FakeAuthenticationScheme.cs
+++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/FakeAuthenticationScheme.cs
@@ -1,8 +1,8 @@
using System;
+using System.Linq;
using System.Security.Claims;
using System.Text.Encodings.Web;
using System.Threading.Tasks;
-using DeviceDetectorNET;
using Microsoft.AspNetCore.Authentication;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/WebClientInfoProvider/WebClientInfoProviderDto.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/WebClientInfoProvider/WebClientInfoProviderDto.cs
new file mode 100644
index 0000000000..85f96a65e3
--- /dev/null
+++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/WebClientInfoProvider/WebClientInfoProviderDto.cs
@@ -0,0 +1,8 @@
+namespace Volo.Abp.AspNetCore.Mvc.WebClientInfoProvider;
+
+public class WebClientInfoProviderDto
+{
+ public string BrowserInfo { get; set; }
+
+ public string DeviceInfo { get; set; }
+}
diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/WebClientInfoProvider/WebClientInfoProviderTestController.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/WebClientInfoProvider/WebClientInfoProviderTestController.cs
new file mode 100644
index 0000000000..0d09c6e8ee
--- /dev/null
+++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/WebClientInfoProvider/WebClientInfoProviderTestController.cs
@@ -0,0 +1,26 @@
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Mvc;
+using Volo.Abp.AspNetCore.WebClientInfo;
+
+namespace Volo.Abp.AspNetCore.Mvc.WebClientInfoProvider;
+
+[Route("api/web-client-info")]
+public class WebClientInfoProviderTestController : AbpController
+{
+ private IWebClientInfoProvider _webClientInfoProvider { get; }
+
+ public WebClientInfoProviderTestController(IWebClientInfoProvider webClientInfoProvider)
+ {
+ _webClientInfoProvider = webClientInfoProvider;
+ }
+
+ [HttpGet]
+ public Task GetAsync()
+ {
+ return Task.FromResult(new WebClientInfoProviderDto
+ {
+ BrowserInfo = _webClientInfoProvider.BrowserInfo,
+ DeviceInfo = _webClientInfoProvider.DeviceInfo
+ });
+ }
+}
diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/WebClientInfoProvider/WebClientInfoProviderTestController_Tests.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/WebClientInfoProvider/WebClientInfoProviderTestController_Tests.cs
new file mode 100644
index 0000000000..0a5f13082b
--- /dev/null
+++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/WebClientInfoProvider/WebClientInfoProviderTestController_Tests.cs
@@ -0,0 +1,36 @@
+using System.Net;
+using System.Net.Http;
+using System.Text.Json;
+using System.Threading.Tasks;
+using Shouldly;
+using Xunit;
+
+namespace Volo.Abp.AspNetCore.Mvc.WebClientInfoProvider;
+
+public class WebClientInfoProviderTestController_Tests : AspNetCoreMvcTestBase
+{
+ [Theory]
+ [InlineData("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36", "Windows 10 Chrome")]
+ [InlineData("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36", "Mac OS X Chrome")]
+ [InlineData("PostmanRuntime/7.43.4", "PostmanRuntime/7.43.4")]
+ [InlineData("curl/7.64.1", "curl/7.64.1")]
+ [InlineData("Mozilla/5.0 (compatible; MojeekBot/0.11; +mojeek.com/bot.html)", "MojeekBot")]
+ public async Task TestAsync(string ua, string device)
+ {
+ var clientInfo = await GetWebClientInfoAsync(ua);
+ clientInfo.ShouldNotBeNull();
+ clientInfo.BrowserInfo.ShouldBe(ua);
+ clientInfo.DeviceInfo.ShouldBe(device);
+ }
+
+ private async Task GetWebClientInfoAsync(string userAgent )
+ {
+ using (var requestMessage = new HttpRequestMessage(HttpMethod.Get, "api/web-client-info"))
+ {
+ requestMessage.Headers.Add("User-Agent", userAgent);
+ var response = await Client.SendAsync(requestMessage);
+ response.StatusCode.ShouldBe(HttpStatusCode.OK);
+ return JsonSerializer.Deserialize(await response.Content.ReadAsStringAsync(), JsonSerializerOptions.Web);
+ }
+ }
+}
diff --git a/npm/ng-packs/packages/components/extensible/src/lib/components/multi-select/extensible-form-multiselect.component.ts b/npm/ng-packs/packages/components/extensible/src/lib/components/multi-select/extensible-form-multiselect.component.ts
index d55d55614d..96860b31b4 100644
--- a/npm/ng-packs/packages/components/extensible/src/lib/components/multi-select/extensible-form-multiselect.component.ts
+++ b/npm/ng-packs/packages/components/extensible/src/lib/components/multi-select/extensible-form-multiselect.component.ts
@@ -1,8 +1,9 @@
import { Component, ChangeDetectionStrategy, forwardRef, input } from '@angular/core';
import { NG_VALUE_ACCESSOR, ControlValueAccessor, ReactiveFormsModule } from '@angular/forms';
import { CommonModule } from '@angular/common';
-import { ABP, LocalizationModule } from '@abp/ng.core';
+import { ABP, LocalizationPipe } from '@abp/ng.core';
import { FormProp } from '../../models/form-props';
+import { NgxValidateCoreModule } from '@ngx-validate/core';
const EXTENSIBLE_FORM_MULTI_SELECT_CONTROL_VALUE_ACCESSOR = {
provide: NG_VALUE_ACCESSOR,
@@ -19,21 +20,24 @@ const EXTENSIBLE_FORM_MULTI_SELECT_CONTROL_VALUE_ACCESSOR = {
- @if (prop().isExtra) {
- {{ '::' + option.key | abpLocalization }}
- } @else {
- {{ option.key }}
- }
+
}
`,
providers: [EXTENSIBLE_FORM_MULTI_SELECT_CONTROL_VALUE_ACCESSOR],
- imports: [LocalizationModule, CommonModule, ReactiveFormsModule],
+ imports: [LocalizationPipe, CommonModule, ReactiveFormsModule, NgxValidateCoreModule],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ExtensibleFormMultiselectComponent implements ControlValueAccessor {
diff --git a/npm/ng-packs/packages/feature-management/src/lib/components/feature-management/feature-management.component.html b/npm/ng-packs/packages/feature-management/src/lib/components/feature-management/feature-management.component.html
index b5dc440c76..a81444e56c 100644
--- a/npm/ng-packs/packages/feature-management/src/lib/components/feature-management/feature-management.component.html
+++ b/npm/ng-packs/packages/feature-management/src/lib/components/feature-management/feature-management.component.html
@@ -28,6 +28,10 @@
@for (feature of features[group.name]; track feature.id || i; let i = $index) {
+ @let provider = feature.provider.name;
+ @let isFeatureDisabled =
+ provider !== providerName && provider !== defaultProviderName;
+