Browse Source

Merge branch 'dev' into issue-6542

pull/22922/head
SALİH ÖZKARA 1 year ago
parent
commit
9c3e205174
  1. 20
      Directory.Packages.props
  2. BIN
      docs/en/Community-Articles/2025-06-12-fix-mongodb-guid-abp-v9.2.0-upgrade/mongo-db.jpg
  3. 93
      docs/en/Community-Articles/2025-06-12-fix-mongodb-guid-abp-v9.2.0-upgrade/post.md
  4. 4
      docs/en/release-info/release-notes.md
  5. 1
      framework/src/Volo.Abp.AspNetCore/Microsoft/AspNetCore/RequestLocalization/DefaultAbpRequestLocalizationOptionsProvider.cs
  6. 2
      framework/src/Volo.Abp.AspNetCore/Volo.Abp.AspNetCore.csproj
  7. 3
      framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/AbpAspNetCoreModule.cs
  8. 36
      framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/WebClientInfo/HttpContextWebClientInfoProvider.cs
  9. 2
      framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/AbpBackgroundJobOptions.cs
  10. 2
      framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/FakeAuthenticationScheme.cs
  11. 8
      framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/WebClientInfoProvider/WebClientInfoProviderDto.cs
  12. 26
      framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/WebClientInfoProvider/WebClientInfoProviderTestController.cs
  13. 36
      framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/WebClientInfoProvider/WebClientInfoProviderTestController_Tests.cs
  14. 18
      npm/ng-packs/packages/components/extensible/src/lib/components/multi-select/extensible-form-multiselect.component.ts
  15. 34
      npm/ng-packs/packages/feature-management/src/lib/components/feature-management/feature-management.component.html
  16. 4
      npm/ng-packs/packages/feature-management/src/lib/components/feature-management/feature-management.component.ts

20
Directory.Packages.props

@ -29,7 +29,7 @@
<PackageVersion Include="Dapper" Version="2.1.66" /> <PackageVersion Include="Dapper" Version="2.1.66" />
<PackageVersion Include="Dapr.AspNetCore" Version="1.15.4" /> <PackageVersion Include="Dapr.AspNetCore" Version="1.15.4" />
<PackageVersion Include="Dapr.Client" Version="1.15.4" /> <PackageVersion Include="Dapr.Client" Version="1.15.4" />
<PackageVersion Include="DeviceDetector.NET" Version="6.4.2" /> <PackageVersion Include="MyCSharp.HttpUserAgentParser" Version="3.0.25" />
<PackageVersion Include="Devart.Data.Oracle.EFCore" Version="10.4.235.9" /> <PackageVersion Include="Devart.Data.Oracle.EFCore" Version="10.4.235.9" />
<PackageVersion Include="DistributedLock.Core" Version="1.0.8" /> <PackageVersion Include="DistributedLock.Core" Version="1.0.8" />
<PackageVersion Include="DistributedLock.Redis" Version="1.0.3" /> <PackageVersion Include="DistributedLock.Redis" Version="1.0.3" />
@ -113,10 +113,10 @@
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.13.0" /> <PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.13.0" />
<PackageVersion Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="9.0.0" /> <PackageVersion Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="9.0.0" />
<PackageVersion Include="Microsoft.SourceLink.GitHub" Version="8.0.0" /> <PackageVersion Include="Microsoft.SourceLink.GitHub" Version="8.0.0" />
<PackageVersion Include="System.IdentityModel.Tokens.Jwt" Version="8.9.0" /> <PackageVersion Include="System.IdentityModel.Tokens.Jwt" Version="8.12.0" />
<PackageVersion Include="Microsoft.IdentityModel.Protocols.OpenIdConnect" Version="8.9.0" /> <PackageVersion Include="Microsoft.IdentityModel.Protocols.OpenIdConnect" Version="8.12.0" />
<PackageVersion Include="Microsoft.IdentityModel.Tokens" Version="8.9.0" /> <PackageVersion Include="Microsoft.IdentityModel.Tokens" Version="8.12.0" />
<PackageVersion Include="Microsoft.IdentityModel.JsonWebTokens" Version="8.9.0" /> <PackageVersion Include="Microsoft.IdentityModel.JsonWebTokens" Version="8.12.0" />
<PackageVersion Include="Minio" Version="6.0.4" /> <PackageVersion Include="Minio" Version="6.0.4" />
<PackageVersion Include="MongoDB.Driver" Version="3.3.0" /> <PackageVersion Include="MongoDB.Driver" Version="3.3.0" />
<PackageVersion Include="NEST" Version="7.17.5" /> <PackageVersion Include="NEST" Version="7.17.5" />
@ -128,11 +128,11 @@
<PackageVersion Include="NUglify" Version="1.21.15" /> <PackageVersion Include="NUglify" Version="1.21.15" />
<PackageVersion Include="Nullable" Version="1.3.1" /> <PackageVersion Include="Nullable" Version="1.3.1" />
<PackageVersion Include="Octokit" Version="14.0.0" /> <PackageVersion Include="Octokit" Version="14.0.0" />
<PackageVersion Include="OpenIddict.Abstractions" Version="6.2.1" /> <PackageVersion Include="OpenIddict.Abstractions" Version="6.4.0" />
<PackageVersion Include="OpenIddict.Core" Version="6.2.1" /> <PackageVersion Include="OpenIddict.Core" Version="6.4.0" />
<PackageVersion Include="OpenIddict.Server.AspNetCore" Version="6.2.1" /> <PackageVersion Include="OpenIddict.Server.AspNetCore" Version="6.4.0" />
<PackageVersion Include="OpenIddict.Validation.AspNetCore" Version="6.2.1" /> <PackageVersion Include="OpenIddict.Validation.AspNetCore" Version="6.4.0" />
<PackageVersion Include="OpenIddict.Validation.ServerIntegration" Version="6.2.1" /> <PackageVersion Include="OpenIddict.Validation.ServerIntegration" Version="6.4.0" />
<PackageVersion Include="Oracle.EntityFrameworkCore" Version="9.23.80" /> <PackageVersion Include="Oracle.EntityFrameworkCore" Version="9.23.80" />
<PackageVersion Include="Polly" Version="8.5.2" /> <PackageVersion Include="Polly" Version="8.5.2" />
<PackageVersion Include="Polly.Extensions.Http" Version="3.0.0" /> <PackageVersion Include="Polly.Extensions.Http" Version="3.0.0" />

BIN
docs/en/Community-Articles/2025-06-12-fix-mongodb-guid-abp-v9.2.0-upgrade/mongo-db.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

93
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<Guid>(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!

4
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) * [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 * Added `ApplicationName` Property to Isolate Background Jobs & Background Workers
* Docs Module: Added "Alternative Words" to Filter Items * Docs Module: Added "Alternative Words" to Filter Items

1
framework/src/Volo.Abp.AspNetCore/Microsoft/AspNetCore/RequestLocalization/DefaultAbpRequestLocalizationOptionsProvider.cs

@ -4,7 +4,6 @@ using System.Globalization;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using DeviceDetectorNET;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Localization; using Microsoft.AspNetCore.Localization;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;

2
framework/src/Volo.Abp.AspNetCore/Volo.Abp.AspNetCore.csproj

@ -31,7 +31,7 @@
<ProjectReference Include="..\Volo.Abp.VirtualFileSystem\Volo.Abp.VirtualFileSystem.csproj" /> <ProjectReference Include="..\Volo.Abp.VirtualFileSystem\Volo.Abp.VirtualFileSystem.csproj" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" /> <PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" />
<PackageReference Include="IdentityModel" /> <PackageReference Include="IdentityModel" />
<PackageReference Include="DeviceDetector.NET" /> <PackageReference Include="MyCSharp.HttpUserAgentParser" />
</ItemGroup> </ItemGroup>
</Project> </Project>

3
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.AspNetCore.RequestLocalization;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.FileProviders;
using MyCSharp.HttpUserAgentParser.DependencyInjection;
using Volo.Abp.AspNetCore.Auditing; using Volo.Abp.AspNetCore.Auditing;
using Volo.Abp.AspNetCore.VirtualFileSystem; using Volo.Abp.AspNetCore.VirtualFileSystem;
using Volo.Abp.Auditing; using Volo.Abp.Auditing;
@ -59,6 +60,8 @@ public class AbpAspNetCoreModule : AbpModule
context.Services.AddAbpDynamicOptions<RequestLocalizationOptions, AbpRequestLocalizationOptionsManager>(); context.Services.AddAbpDynamicOptions<RequestLocalizationOptions, AbpRequestLocalizationOptionsManager>();
StaticWebAssetsLoader.UseStaticWebAssets(context.Services.GetHostingEnvironment(), context.Services.GetConfiguration()); StaticWebAssetsLoader.UseStaticWebAssets(context.Services.GetHostingEnvironment(), context.Services.GetConfiguration());
context.Services.AddHttpUserAgentCachedParser();
} }
private static void AddAspNetServices(IServiceCollection services) private static void AddAspNetServices(IServiceCollection services)

36
framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/WebClientInfo/HttpContextWebClientInfoProvider.cs

@ -1,7 +1,8 @@
using System; using System;
using DeviceDetectorNET;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using MyCSharp.HttpUserAgentParser;
using MyCSharp.HttpUserAgentParser.Providers;
using Volo.Abp.DependencyInjection; using Volo.Abp.DependencyInjection;
namespace Volo.Abp.AspNetCore.WebClientInfo; namespace Volo.Abp.AspNetCore.WebClientInfo;
@ -11,13 +12,16 @@ public class HttpContextWebClientInfoProvider : IWebClientInfoProvider, ITransie
{ {
protected ILogger<HttpContextWebClientInfoProvider> Logger { get; } protected ILogger<HttpContextWebClientInfoProvider> Logger { get; }
protected IHttpContextAccessor HttpContextAccessor { get; } protected IHttpContextAccessor HttpContextAccessor { get; }
protected IHttpUserAgentParserProvider HttpUserAgentParser { get; }
public HttpContextWebClientInfoProvider( public HttpContextWebClientInfoProvider(
ILogger<HttpContextWebClientInfoProvider> logger, ILogger<HttpContextWebClientInfoProvider> logger,
IHttpContextAccessor httpContextAccessor) IHttpContextAccessor httpContextAccessor,
IHttpUserAgentParserProvider httpUserAgentParser)
{ {
Logger = logger; Logger = logger;
HttpContextAccessor = httpContextAccessor; HttpContextAccessor = httpContextAccessor;
HttpUserAgentParser = httpUserAgentParser;
} }
public string? BrowserInfo => GetBrowserInfo(); public string? BrowserInfo => GetBrowserInfo();
@ -46,27 +50,21 @@ public class HttpContextWebClientInfoProvider : IWebClientInfoProvider, ITransie
protected virtual string? GetDeviceInfo() protected virtual string? GetDeviceInfo()
{ {
string? deviceInfo = null; var browserInfo = GetBrowserInfo();
var deviceDetector = new DeviceDetector(GetBrowserInfo()); if (browserInfo.IsNullOrWhiteSpace())
deviceDetector.Parse();
if (!deviceDetector.IsParsed())
{ {
return deviceInfo; return null;
}
var osInfo = deviceDetector.GetOs();
if (osInfo.Success)
{
deviceInfo = osInfo.Match.Name;
} }
var clientInfo = deviceDetector.GetClient(); var httpUserAgentInformation = HttpUserAgentParser.Parse(browserInfo);
if (clientInfo.Success) 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;
} }
} }

2
framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/AbpBackgroundJobOptions.cs

@ -68,7 +68,7 @@ public class AbpBackgroundJobOptions
public void AddJob(Type jobType) public void AddJob(Type jobType)
{ {
AddJob(new BackgroundJobConfiguration(jobType, GetBackgroundJobName(jobType))); AddJob(new BackgroundJobConfiguration(jobType, GetBackgroundJobName(BackgroundJobArgsHelper.GetJobArgsType(jobType))));
} }
public void AddJob(BackgroundJobConfiguration jobConfiguration) public void AddJob(BackgroundJobConfiguration jobConfiguration)

2
framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/FakeAuthenticationScheme.cs

@ -1,8 +1,8 @@
using System; using System;
using System.Linq;
using System.Security.Claims; using System.Security.Claims;
using System.Text.Encodings.Web; using System.Text.Encodings.Web;
using System.Threading.Tasks; using System.Threading.Tasks;
using DeviceDetectorNET;
using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;

8
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; }
}

26
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<WebClientInfoProviderDto> GetAsync()
{
return Task.FromResult(new WebClientInfoProviderDto
{
BrowserInfo = _webClientInfoProvider.BrowserInfo,
DeviceInfo = _webClientInfoProvider.DeviceInfo
});
}
}

36
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<WebClientInfoProviderDto> 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<WebClientInfoProviderDto>(await response.Content.ReadAsStringAsync(), JsonSerializerOptions.Web);
}
}
}

18
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 { Component, ChangeDetectionStrategy, forwardRef, input } from '@angular/core';
import { NG_VALUE_ACCESSOR, ControlValueAccessor, ReactiveFormsModule } from '@angular/forms'; import { NG_VALUE_ACCESSOR, ControlValueAccessor, ReactiveFormsModule } from '@angular/forms';
import { CommonModule } from '@angular/common'; 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 { FormProp } from '../../models/form-props';
import { NgxValidateCoreModule } from '@ngx-validate/core';
const EXTENSIBLE_FORM_MULTI_SELECT_CONTROL_VALUE_ACCESSOR = { const EXTENSIBLE_FORM_MULTI_SELECT_CONTROL_VALUE_ACCESSOR = {
provide: NG_VALUE_ACCESSOR, provide: NG_VALUE_ACCESSOR,
@ -19,21 +20,24 @@ const EXTENSIBLE_FORM_MULTI_SELECT_CONTROL_VALUE_ACCESSOR = {
<input <input
type="checkbox" type="checkbox"
class="form-check-input" class="form-check-input"
[id]="'checkbox_' + option.value"
[disabled]="disabled" [disabled]="disabled"
[checked]="isChecked(option.value)" [checked]="isChecked(option.value)"
(change)="onCheckboxChange(option.value, $event.target.checked)" (change)="onCheckboxChange(option.value, $event.target.checked)"
/> />
@if (prop().isExtra) { <label [for]="'checkbox_' + option.value">
{{ '::' + option.key | abpLocalization }} @if (prop().isExtra) {
} @else { {{ '::' + option.key | abpLocalization }}
{{ option.key }} } @else {
} {{ option.key }}
}
</label>
</div> </div>
} }
</div> </div>
`, `,
providers: [EXTENSIBLE_FORM_MULTI_SELECT_CONTROL_VALUE_ACCESSOR], providers: [EXTENSIBLE_FORM_MULTI_SELECT_CONTROL_VALUE_ACCESSOR],
imports: [LocalizationModule, CommonModule, ReactiveFormsModule], imports: [LocalizationPipe, CommonModule, ReactiveFormsModule, NgxValidateCoreModule],
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
}) })
export class ExtensibleFormMultiselectComponent implements ControlValueAccessor { export class ExtensibleFormMultiselectComponent implements ControlValueAccessor {

34
npm/ng-packs/packages/feature-management/src/lib/components/feature-management/feature-management.component.html

@ -28,6 +28,10 @@
<hr class="mt-2 mb-3" /> <hr class="mt-2 mb-3" />
@for (feature of features[group.name]; track feature.id || i; let i = $index) { @for (feature of features[group.name]; track feature.id || i; let i = $index) {
@let provider = feature.provider.name;
@let isFeatureDisabled =
provider !== providerName && provider !== defaultProviderName;
<div class="mt-2" [ngStyle]="feature.style" (keyup.enter)="save()"> <div class="mt-2" [ngStyle]="feature.style" (keyup.enter)="save()">
@switch (feature.valueType?.name) { @switch (feature.valueType?.name) {
@case (valueTypes.ToggleStringValueType) { @case (valueTypes.ToggleStringValueType) {
@ -38,11 +42,15 @@
[id]="feature.name" [id]="feature.name"
[(ngModel)]="feature.value" [(ngModel)]="feature.value"
(ngModelChange)="onCheckboxClick($event, feature)" (ngModelChange)="onCheckboxClick($event, feature)"
[disabled]="isFeatureDisabled"
/> />
<label class="form-check-label" [htmlFor]="feature.name">{{ <label class="form-check-label" [htmlFor]="feature.name">
feature.displayName {{ feature.displayName }}
}}</label> @if (isFeatureDisabled) {
<span>({{ provider }})</span>
}
</label>
<ng-container <ng-container
*ngTemplateOutlet=" *ngTemplateOutlet="
descTmp; descTmp;
@ -53,15 +61,19 @@
} }
@case (valueTypes.FreeTextStringValueType) { @case (valueTypes.FreeTextStringValueType) {
<div class="mb-3 form-group" [class.px-2]="!!feature.parentName"> <div class="mb-3 form-group" [class.px-2]="!!feature.parentName">
<label [htmlFor]="feature.name" class="form-label">{{ <label [htmlFor]="feature.name" class="form-label">
feature.displayName {{ feature.displayName }}
}}</label> @if (isFeatureDisabled) {
<span>({{ provider }})</span>
}
</label>
<input <input
class="form-control" class="form-control"
type="text" type="text"
[id]="feature.name" [id]="feature.name"
[(ngModel)]="feature.value" [(ngModel)]="feature.value"
[abpFeatureManagementFreeText]="feature" [abpFeatureManagementFreeText]="feature"
[disabled]="isFeatureDisabled"
/> />
<ng-container <ng-container
@ -75,13 +87,17 @@
@case (valueTypes.SelectionStringValueType) { @case (valueTypes.SelectionStringValueType) {
@if (feature.valueType.itemSource?.items?.length) { @if (feature.valueType.itemSource?.items?.length) {
<div class="mb-3 form-group" [class.px-2]="!!feature.parentName"> <div class="mb-3 form-group" [class.px-2]="!!feature.parentName">
<label [htmlFor]="feature.name" class="form-label">{{ <label [htmlFor]="feature.name" class="form-label">
feature.displayName {{ feature.displayName }}
}}</label> @if (isFeatureDisabled) {
<span>({{ provider }})</span>
}
</label>
<select <select
class="form-select" class="form-select"
[id]="feature.name" [id]="feature.name"
[(ngModel)]="feature.value" [(ngModel)]="feature.value"
[disabled]="isFeatureDisabled"
> >
@for ( @for (
item of feature.valueType.itemSource?.items; item of feature.valueType.itemSource?.items;

4
npm/ng-packs/packages/feature-management/src/lib/components/feature-management/feature-management.component.ts

@ -28,6 +28,8 @@ enum ValueTypes {
SelectionStringValueType = 'SelectionStringValueType', SelectionStringValueType = 'SelectionStringValueType',
} }
const DEFAULT_PROVIDER_NAME = 'D';
@Component({ @Component({
selector: 'abp-feature-management', selector: 'abp-feature-management',
templateUrl: './feature-management.component.html', templateUrl: './feature-management.component.html',
@ -74,6 +76,8 @@ export class FeatureManagementComponent
valueTypes = ValueTypes; valueTypes = ValueTypes;
defaultProviderName = DEFAULT_PROVIDER_NAME;
protected _visible; protected _visible;
@Input() @Input()

Loading…
Cancel
Save