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; +
@switch (feature.valueType?.name) { @case (valueTypes.ToggleStringValueType) { @@ -38,11 +42,15 @@ [id]="feature.name" [(ngModel)]="feature.value" (ngModelChange)="onCheckboxClick($event, feature)" + [disabled]="isFeatureDisabled" /> - + - + - +