Hi,
I tried to reproduce this locally on a freshly generated 9.2.2 Angular template (Identity Roles and Users pages). The ExtensibleTable component code in 9.1.x and 9.2.x is the same, so it's a useful comparison but not proof your exact setup is unaffected. On my side both pages render correctly after data load, no resize needed — so this doesn't look like a generic 9.1.2 framework defect; it's more likely related to project-specific layout, theme customization, browser state, or timing in your environment.
Could you share a bit more so we can narrow it down:
@abp/ng.components, @abp/ng.theme.lepton-x, and @swimlane/ngx-datatable from your package.json.ngx-datatable host element's computed width / height (often this kind of issue comes from the container being 0×0 at first render due to a hidden/flex parent).In the meantime, inside your existing root AppComponent you can add this to force a recalculate after every navigation:
import { NgZone, OnInit, inject } from '@angular/core';
import { NavigationEnd, Router } from '@angular/router';
import { filter } from 'rxjs/operators';
export class AppComponent implements OnInit {
private router = inject(Router);
private zone = inject(NgZone);
ngOnInit() {
this.router.events
.pipe(filter(e => e instanceof NavigationEnd))
.subscribe(() => {
this.zone.runOutsideAngular(() => {
setTimeout(() => window.dispatchEvent(new Event('resize')), 0);
setTimeout(() => window.dispatchEvent(new Event('resize')), 100);
});
});
}
}
This is the same mechanism your manual window resize is triggering today, dispatched programmatically. It's a temporary workaround while we narrow down the root cause from the details above.
Thanks
Hi,
Both have already been addressed, so you don't need to wait for v10.5:
GHSA-rvv3-g6hj-g44x. Volo.Abp.AutoMapper itself still references 14.x and will keep showing the advisory. The supported fix is to switch to Volo.Abp.LuckyPenny.AutoMapper (available since v10.3.0), which targets the patched commercial AutoMapper (16.x). It's a drop-in replacement — only two changes in your own modules:// *.csproj
-<PackageReference Include="Volo.Abp.AutoMapper" />
+<PackageReference Include="Volo.Abp.LuckyPenny.AutoMapper" />
// YourModule.cs
-[DependsOn(typeof(AbpAutoMapperModule))]
+[DependsOn(typeof(AbpLuckyPennyAutoMapperModule))]
All types stay in the same namespaces (AbpAutoMapperOptions, IMapperAccessor, etc.), so your existing Profiles and CreateMap calls keep working as-is. LuckyPenny has a free Community License for organizations with annual gross revenue under $5,000,000 USD and that never received more than $10,000,000 USD in outside capital (sign-up page); otherwise paid plans. Full setup (including the license key configuration): https://abp.io/docs/latest/framework/infrastructure/luckypenny-automapper
If you'd rather not take on a commercial dependency, the free alternative is Mapperly (what ABP modules use internally in v10). Migration guide: https://abp.io/docs/latest/release-info/migration-guides/AutoMapper-To-Mapperly
About the VS 2026 PMC behavior: that's upstream NuGet Audit kicking in when a vulnerable package is restored — it's not something $ErrorActionPreference can override. Once you upgrade to v10.4.x and switch Volo.Abp.AutoMapper to either Volo.Abp.LuckyPenny.AutoMapper or Volo.Abp.Mapperly, the audit stops flagging those packages and Add-Migration / Update-Database work normally again.
Thanks
Hi,
You're right, this is a real bug. The Guid.TryParse(parts[0], ...) branch in QdrantVectorStore.BuildPointId collapses every chunk of the same data source onto the data source's GUID as the PointId, so each new chunk overwrites the previous one and only the last chunk survives in Qdrant. Verified end-to-end against a local Qdrant container: with the original code 3 stored chunks produce 1 point in the collection; with the underscore branch removed they produce 3 distinct points.
Removing that branch (so every non-GUID id falls through to the SHA256 hash) is the correct fix. Your workaround is essentially what we'll ship.
The fix is scheduled for the next release. Existing Qdrant data already affected needs to be reindexed (DeleteByWorkspaceIdAsync on the workspace, then re-run indexing) since only one chunk per document was actually stored.
Thanks
Hi,
You were right — reproduced and root-caused. The bug is in framework-shared code (AbpExtensibleDataGrid.razor), not in Volo.Saas. Affects every JSON-only extension column (Saas Tenants, Identity Users, etc.) and is present in both 10.3.0 and 10.4.0.
Fix: https://github.com/abpframework/abp/pull/25480 — will ship in the next 10.3.x patch.
Until then MapEfCoreProperty remains the simplest workaround.
Thanks
: )
Hi,
Quick clarifications on both your points:
You're right about the constructor — TenantAppService in 10.3.0 takes the 9 parameters you listed. More importantly, an override shouldn't be needed at all. The framework copies ExtraProperties from Tenant to SaasTenantDto by default in 10.3.0, the same way it does on IdentityUser. There's no intentional change in Volo.Saas that would make the Saas grid behave differently from Identity — both use Mapperly with the same attributes. So your read on this being a regression in your environment (rather than a designed-in difference) is the right framing.
We followed your steps end-to-end on a fresh 10.3.0 Blazor Web App locally (Pro NuGet packages pinned to 10.3.0, SQL Server, JSON-only TaxNumber registered via ConfigureSaas.ConfigureTenant.AddOrUpdateProperty<>, seeded with tenant.SetProperty("TaxNumber", "12345678") + InsertAsync). Everything works as expected: the value lands in the SaasTenants.ExtraProperties JSON column, the entity loads it back, the mapping carries it through, the grid cells render, and GET /api/saas/tenants returns:
{
"items": [{
"name": "repro-10690",
"extraProperties": { "TaxNumber": "12345678" }
}]
}
So the JSON-only flow is intact on a clean 10.3.0 setup. Something in your project is breaking it before the mapping step.
The MapEfCoreProperty clue is the most useful signal you gave. That switch doesn't change the CLR/mapping API — both paths still read tenant.ExtraProperties[...]. It only changes physical storage (JSON column vs. a real DB column). So the failing step is most likely the EF load itself: the JSON value in the SaasTenants.ExtraProperties column isn't being deserialized into the in-memory ExtraProperties dictionary on the entity, and the mapping then correctly copies an empty dictionary downstream.
To narrow this down, could you do one or more of these:
var entities = await TenantRepository.GetListAsync(...);
var first = entities.FirstOrDefault();
Logger.LogInformation(
"Repro: ExtraProperties.Count = {Count}, TaxNumber from entity = {Value}",
first?.ExtraProperties.Count,
first?.GetProperty<string>("TaxNumber"));
If TaxNumber is null at the entity level while the DB has {"TaxNumber":"12345678"}, the bug is in EF deserialization — which is exactly the layer MapEfCoreProperty happens to bypass.
Open Chrome DevTools → Network on the Tenants page and share the raw JSON response of GET /api/saas/tenants. If extraProperties.TaxNumber is missing in the server response, the issue is server-side (consistent with the theory above). If it's present, the issue is on the Blazor grid binding side instead.
When you upgraded from 10.1.1 → 10.3.0, did you add and apply a new EF migration? If yes, could you share the generated migration diff for the SaasTenants table — especially anything touching the ExtraProperties column?
If easier, a minimal reproduction project (private GitHub repo with https://github.com/maliming invited, or a zip to liming.ma@volosoft.com) lets us reproduce in your exact configuration.
We'd rather find the real root cause than recommend MapEfCoreProperty as the permanent answer — JSON-only should work the same way it does on IdentityUser.
Thanks
Hi,
You're right, the current Volo.AIManagement.VectorStores.Qdrant module only parses host/port out of the connection string and calls new QdrantClient(host, port), so there is no way to pass an API key or enable HTTPS through the built-in settings.
The workaround is to replace the two built-in services — QdrantVectorStoreFactory and QdrantVectorStore — with your own. The factory reads Qdrant:ApiKey and Qdrant:Https from appsettings.json (keeping the secret out of the DB), builds a QdrantClient with them, and wraps it in your custom IVectorStore. The workspace's VectorStoreSettings field stays in the existing host:port format.
1. MyQdrantVectorStoreFactory.cs
using System;
using System.Threading.Tasks;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Qdrant.Client;
using Volo.Abp.DependencyInjection;
using Volo.AIManagement.VectorStores;
namespace MyCompanyName.MyProjectName;
[ExposeServices(typeof(IVectorStoreFactory))]
public class MyQdrantVectorStoreFactory : IVectorStoreFactory, ITransientDependency
{
public string Provider => "Qdrant";
private readonly ILoggerFactory _loggerFactory;
private readonly IConfiguration _configuration;
public MyQdrantVectorStoreFactory(ILoggerFactory loggerFactory, IConfiguration configuration)
{
_loggerFactory = loggerFactory;
_configuration = configuration;
}
public Task<IVectorStore> CreateAsync(Guid workspaceId, string? connectionSettings = null)
{
var (host, port) = ParseHostPort(connectionSettings!);
var apiKey = _configuration["Qdrant:ApiKey"];
var https = _configuration.GetValue<bool>("Qdrant:Https");
var client = new QdrantClient(host, port, https, apiKey, loggerFactory: _loggerFactory);
return Task.FromResult<IVectorStore>(
new MyQdrantVectorStore(client, workspaceId, _loggerFactory.CreateLogger<MyQdrantVectorStore>()));
}
private static (string Host, int Port) ParseHostPort(string raw)
{
if (raw.StartsWith("http://", StringComparison.OrdinalIgnoreCase) ||
raw.StartsWith("https://", StringComparison.OrdinalIgnoreCase))
{
var uri = new Uri(raw);
return (uri.Host, uri.Port > 0 ? uri.Port : 6334);
}
var idx = raw.IndexOf(':');
return idx > 0 && int.TryParse(raw[(idx + 1)..], out var p) ? (raw[..idx], p) : (raw, 6334);
}
}
2. MyQdrantVectorStore.cs — same shape as the built-in QdrantVectorStore, but the constructor takes a pre-built QdrantClient:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Qdrant.Client;
using Qdrant.Client.Grpc;
using static Qdrant.Client.Grpc.Conditions;
using Volo.AIManagement.VectorStores;
namespace MyCompanyName.MyProjectName;
public class MyQdrantVectorStore : IVectorStore, IDisposable
{
private readonly QdrantClient _client;
private readonly ILogger<MyQdrantVectorStore> _logger;
private readonly Guid _workspaceId;
private int? _workspaceDimension;
private readonly SemaphoreSlim _initLock = new(1, 1);
public MyQdrantVectorStore(QdrantClient client, Guid workspaceId, ILogger<MyQdrantVectorStore> logger)
{
_client = client;
_workspaceId = workspaceId;
_logger = logger;
}
private string CollectionName => $"ai_vector_embeddings_{_workspaceId:N}";
public async Task InitializeAsync(CancellationToken cancellationToken = default)
{
await _client.ListCollectionsAsync(cancellationToken);
}
public async Task<string> StoreAsync(Guid workspaceId, string id, float[] embedding,
Dictionary<string, object>? metadata = null, CancellationToken cancellationToken = default)
{
await EnsureCollectionAsync(embedding.Length, cancellationToken);
var payload = new Dictionary<string, Value>();
if (metadata != null)
{
foreach (var kvp in metadata) payload[kvp.Key] = ToValue(kvp.Value);
}
payload["workspace_id"] = ToValue(_workspaceId.ToString());
payload["creation_time"] = ToValue(DateTime.UtcNow.ToString("O"));
payload["original_id"] = ToValue(id);
var point = new PointStruct
{
Id = ToPointId(id),
Vectors = new Vectors { Vector = new Vector { Data = { embedding } } }
};
foreach (var kvp in payload) point.Payload[kvp.Key] = kvp.Value;
await _client.UpsertAsync(CollectionName, new[] { point }, cancellationToken: cancellationToken);
return id;
}
public async Task<List<VectorSearchResult>> SearchAsync(Guid workspaceId, float[] queryEmbedding, int topK,
CancellationToken cancellationToken = default)
{
if (!await CollectionExistsAsync(cancellationToken)) return new();
var results = await _client.SearchAsync(CollectionName, queryEmbedding, limit: (ulong)topK,
searchParams: new SearchParams { HnswEf = 512 }, cancellationToken: cancellationToken);
return results.Select(p =>
{
var meta = FromPayload(p.Payload);
var id = meta != null && meta.TryGetValue("original_id", out var oid) ? oid?.ToString() ?? p.Id.ToString() : p.Id.ToString();
return new VectorSearchResult { Id = id, Score = p.Score, Metadata = meta };
}).ToList();
}
public async Task<bool> DeleteAsync(Guid workspaceId, string id, CancellationToken cancellationToken = default)
{
if (!await CollectionExistsAsync(cancellationToken)) return false;
await _client.DeleteAsync(CollectionName, new[] { ToPointId(id) }, wait: true, cancellationToken: cancellationToken);
return true;
}
public async Task<int> DeleteByWorkspaceIdAsync(Guid workspaceId, CancellationToken cancellationToken = default)
{
if (!await CollectionExistsAsync(cancellationToken)) return 0;
await _client.DeleteCollectionAsync(CollectionName, cancellationToken: cancellationToken);
_workspaceDimension = null;
return 1;
}
public async Task<int> DeleteAllByDataSourceAsync(Guid workspaceId, Guid dataSourceId, CancellationToken cancellationToken = default)
{
if (!await CollectionExistsAsync(cancellationToken)) return 0;
await _client.DeleteAsync(CollectionName, filter: MatchKeyword("data_source_id", dataSourceId.ToString()),
wait: true, cancellationToken: cancellationToken);
return 1;
}
public void Dispose()
{
_client.Dispose();
_initLock.Dispose();
}
private async Task EnsureCollectionAsync(int dimension, CancellationToken cancellationToken)
{
await _initLock.WaitAsync(cancellationToken);
try
{
if (_workspaceDimension == dimension) return;
if (!await CollectionExistsAsync(cancellationToken))
{
await _client.CreateCollectionAsync(CollectionName,
vectorsConfig: new VectorParams
{
Size = (ulong)dimension,
Distance = Distance.Cosine,
HnswConfig = new HnswConfigDiff { M = 16, EfConstruct = 200 }
},
cancellationToken: cancellationToken);
}
_workspaceDimension = dimension;
}
finally { _initLock.Release(); }
}
private async Task<bool> CollectionExistsAsync(CancellationToken cancellationToken) =>
(await _client.ListCollectionsAsync(cancellationToken)).Any(c => c == CollectionName);
private static PointId ToPointId(string id)
{
if (Guid.TryParse(id, out var g)) return g;
if (id.Contains('_') && Guid.TryParse(id.Split('_', 2)[0], out var gp)) return gp;
return BitConverter.ToUInt64(System.Security.Cryptography.SHA256.HashData(System.Text.Encoding.UTF8.GetBytes(id)), 0);
}
private static Value ToValue(object? v) => v switch
{
string s => new Value { StringValue = s },
int i => new Value { IntegerValue = i },
long l => new Value { IntegerValue = l },
float f => new Value { DoubleValue = f },
double d => new Value { DoubleValue = d },
bool b => new Value { BoolValue = b },
null => new Value { NullValue = NullValue.NullValue },
_ => new Value { StringValue = v.ToString() ?? "" }
};
private static Dictionary<string, object>? FromPayload(Google.Protobuf.Collections.MapField<string, Value> payload)
{
if (payload == null || payload.Count == 0) return null;
var meta = new Dictionary<string, object>();
foreach (var kvp in payload)
{
if (kvp.Key is "workspace_id" or "creation_time") continue;
meta[kvp.Key] = kvp.Value.KindCase switch
{
Value.KindOneofCase.StringValue => kvp.Value.StringValue,
Value.KindOneofCase.IntegerValue => kvp.Value.IntegerValue,
Value.KindOneofCase.DoubleValue => kvp.Value.DoubleValue,
Value.KindOneofCase.BoolValue => kvp.Value.BoolValue,
_ => kvp.Value.ToString()!
};
}
return meta;
}
}
3. Remove the built-in factory registration in any of your existing modules that already depend on AIManagementQdrantVectorStoreModule. It has to be done in PostConfigureServices (not PreConfigureServices or ConfigureServices), because AbpApplicationBase runs Services.AddAssembly(...) between PreConfigureServices and ConfigureServices — if you call RemoveAll too early, the built-in factory hasn't been auto-registered yet and the call silently does nothing.
public override void PostConfigureServices(ServiceConfigurationContext context)
{
context.Services.RemoveAll(d => d.ImplementationType == typeof(QdrantVectorStoreFactory));
}
RemoveAll is needed (instead of [Dependency(ReplaceServices = true)]) because IVectorStoreFactory is consumed as IEnumerable<IVectorStoreFactory> and resolved by _factories.FirstOrDefault(f => f.Provider == "Qdrant"). If both factories stay registered, the framework one is registered first (its module loads before yours) and wins — your factory is never called.
4. appsettings.json (the workspace's VectorStoreSettings field stays as qdrant.example.com:6334):
{
"Qdrant": {
"ApiKey": "YOUR_PRODUCTION_API_KEY",
"Https": true
}
}
We'll add first-class ApiKey / UseHttps support to the Qdrant module in the next release so this workaround is no longer needed.
Thanks
Thanks DominaTang
Hi,
The recommended way is to upgrade ABP incrementally, one minor version at a time (10.0 → 10.1 → 10.2 → 10.3 → 10.4) and apply each migration guide. Jumping directly from 9.3.2 to 10.4.0 mixes 5 versions of breaking changes and makes root-causing very hard. The migration guides are here:
Below are answers to each issue you listed.
"No object mapping was found for the specified source and destination types"This happens because in v10 your solution loads both AbpAutoMapperModule (your existing code) and AbpMapperlyModule (loaded indirectly by ABP modules that already migrated to Mapperly). Both register IAutoObjectMappingProvider and the last-loaded module wins, so ObjectMapper.Map<TSource, TDestination>(...) ends up looking for a mapper in the wrong provider.
Fix: pick one provider and register it explicitly in your root module's ConfigureServices:
// If you keep AutoMapper:
public override void ConfigureServices(ServiceConfigurationContext context)
{
context.Services.AddAutoMapperObjectMapper();
}
// If you switch to Mapperly:
public override void ConfigureServices(ServiceConfigurationContext context)
{
context.Services.AddMapperlyObjectMapper();
}
Doc: https://abp.io/docs/10.4/release-info/migration-guides/AutoMapper-To-Mapperly#set-default-mapping-provider
Volo.Abp.AutoMapper is still shipped and works in v10. The reason this topic comes up so much is that:
Volo.Abp.AutoMapper is built on) has a known DoS vulnerability (GHSA-rvv3-g6hj-g44x) and the 14.x line will not get a patch.ABP gives you three valid options. Pick one and don't mix them across the solution:
Option A — Keep Volo.Abp.AutoMapper (no commercial license)
ABP Framework has applied a code-level mitigation (MaxDepth = 64) to the integration to address the GHSA-rvv3-g6hj-g44x DoS vector. This is the lowest-effort option if your project does not need the patched AutoMapper.
Option B — Move to Volo.Abp.LuckyPenny.AutoMapper (commercial AutoMapper 15.x)
This is a true drop-in replacement and only requires two changes, no namespace updates:
In every *.csproj:
<PackageReference Include="Volo.Abp.LuckyPenny.AutoMapper" />
(replacing Volo.Abp.AutoMapper)
In every module that previously depended on AbpAutoMapperModule:
[DependsOn(typeof(AbpLuckyPennyAutoMapperModule))]
public class MyModule : AbpModule { /* unchanged */ }
All types remain in the Volo.Abp.AutoMapper namespace, so using directives stay the same. Volo.Abp.LuckyPenny.AutoMapper and Volo.Abp.AutoMapper must not be referenced together — choose one or the other.
LuckyPenny has a free Community License for organizations with annual gross revenue under $5,000,000 USD and that never received more than $10,000,000 USD in outside capital (sign-up page). Otherwise see paid plans.
License key configuration (do this only on server-side projects, not Blazor WebAssembly / MAUI):
[DependsOn(typeof(AbpLuckyPennyAutoMapperModule))]
public class MyModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
var licenseKey = context.Configuration["AutoMapper:LicenseKey"];
Configure<AbpAutoMapperOptions>(options =>
{
options.Configurators.Add(ctx =>
{
ctx.MapperConfiguration.LicenseKey = licenseKey;
});
});
}
}
Doc: https://abp.io/docs/10.4/framework/infrastructure/luckypenny-automapper
Option C — Migrate to Volo.Abp.Mapperly
Mapperly is free and is what ABP modules use internally in v10. Five global search-and-replace steps, then convert each Profile class into a MapperBase<TSource, TDestination> partial class:
[Mapper(RequiredMappingStrategy = RequiredMappingStrategy.Target)]
public partial class MyEntityToMyEntityDtoMapper : MapperBase<MyEntity, MyEntityDto>
{
public override partial MyEntityDto Map(MyEntity source);
public override partial void Map(MyEntity source, MyEntityDto destination);
}
Use TwoWayMapperBase<TSource, TDestination> for ReverseMap(), [MapperIgnoreTarget] for Ignore(...), [MapProperty] for ForMember(...), and override AfterMap for custom logic. Doc: https://abp.io/docs/10.4/release-info/migration-guides/AutoMapper-To-Mapperly
Whichever option you pick, do this after the version upgrade is stable, not during.
WithNavigationProperties DTOs / LookupDto / "invalid navigation display properties"Two things to clarify first:
*AutoMapperProfile.cs exists it stays on AutoMapper; if *Mappers.cs exists it switches to Mapperly; if both exist, AutoMapper wins. The AutoMapper templates themselves were not significantly changed between v9 and v10, so if you stay on AutoMapper, regenerated code looks essentially the same as in v9.{Entity}WithNavigationPropertiesTo{Entity}WithNavigationPropertiesDtoMapper, a {NavEntity}ToLookupDto{Type}Mapper per navigation, child-collection helpers, a CombinedAfterMap). These are the files that may collide with hand-edited mappings after a Mapperly switch.How to fix the "invalid navigation display properties" compile errors:
destination.DisplayName = source.{DisplayProperty}; inside AfterMap, so a stale or wrong property name compiles into invalid code. This is the single most common cause of these specific errors after regeneration.*AutoMapperProfile.cs and *Mappers.cs in the same project during the transition. Pick one provider per project and remove the other family of files, otherwise Suite picks AutoMapper but you may also have orphan Mapperly classes pulled in by namespace.*AutoMapperProfile.cs, *ApplicationMapperlyMappers.cs, *ApplicationMappers.cs, *BlazorMappers.cs, *WebMappers.cs, *Mappers.cs). For example, MyEntityManualMappings.cs is safe — MyEntityMappers.cs is not.Suite v10 works against v10 solutions, but it is not a "v9 → v10 auto-migration" tool. For an upgraded solution:
Two cumulative changes since 10.0 that you need to apply, in addition to other small EF migrations triggered by various ABP modules.
10.1: IdentityUserPasswordHistory and IdentityUserPasskey are added under the Identity Pro module. Run Add-Migration and Update-Database after upgrading to 10.1.
10.2: IIdentityProDbContext adds the UserInvitations DbSet. If your DbContext implements IIdentityProDbContext (the standard startup-template setup), add this property to your DbContext class so it satisfies the new interface member and is included in the model:
public DbSet<UserInvitation> UserInvitations { get; set; }
Then Add-Migration + Update-Database again.
Docs:
Width, CurrentPage, bind-* are all part of the Blazorise 2.0 upgrade that ABP 10.2 brings. The complete list (apply all of them) is here: https://abp.io/docs/10.4/release-info/migration-guides/blazorise-2-0-migration
Highlights:
TextEdit → TextInput, DateEdit → DateInput, NumericEdit → NumericInput, etc.Value / ValueChanged:
@bind-Text, @bind-Checked, @bind-Date, @bind-SelectedValue → @bind-ValueDataGrid:
CurrentPage → Page on the native Blazorise DataGrid. Important: ABP's AbpExtensibleDataGrid still exposes CurrentPage — do not rename it there.
Column Width changed from string to fluent sizing:
Width="Width.Px(30)" // was Width="30px"
Width="Width.Is50" // was Width="50%"
Width="Width.Is100" // was Width="100%"
// Dynamic string values (e.g. when Width comes from configuration or an entity):
Width="@BlazoriseFluentSizingParse.Parse(column.Width)"
For non-standard percentages (e.g. 33%) use BlazoriseFluentSizingParse.Parse("33%") — Blazorise's Width static helper does not have a generic Percent(...) method.
Inside DisplayTemplate, context.Property → context.Item.Property. The same rule applies to Clicked / Visible / ConfirmationMessage in DataGridEntityActionsColumn and DataGridCommandColumn.
Modal: Size and Centered move from <ModalContent> to <Modal>.Dropdown: RightAligned="true" → EndAligned="true".Autocomplete: MinLength → MinSearchLength.TValue: <SelectItem></SelectItem> → <SelectItem TValue="string"></SelectItem>.<TargetFramework>, global.json, install SDK 10).IdentityUserPasswordHistory / IdentityUserPasskey. Also update custom Swagger filters if any (Swashbuckle 10 + Microsoft.OpenApi 2 are not source-compatible).DbSet<UserInvitation> to your DbContext, create a new EF migration.PermissionIntegrationController clients if any (now HttpPost).If you use MySQL, please check the 10.0 guide carefully — the EF Core MySQL providers have limited .NET 10 support at the moment.
Thanks
Hi,
Thanks for the SQL results — they actually rule out the most common cause I had in mind. Both test2 and all three grant rows have TenantId = NULL, so this is not a host/tenant isolation issue. The RequiredPermissionName is also NULL, so the check goes straight to the resource permission path. And the direct user grant U / 3b23e6ff-... matches test2's Id exactly, so on paper UserResourcePermissionValueProvider.CheckAsync should return Granted immediately.
The fact that it still fails means the mismatch is happening at runtime, not in the data. To pin it down I need two more things from you.
1) A HAR file of the failing request.
Please sign test2 out, sign in fresh, reproduce the error once, then export a HAR from the Patient Portal browser and share it. Instructions are in the second answer of https://abp.io/support/questions/8622/How-to-enable-Debug-logs-for-troubleshoot-problems — DevTools → Network → right-click → "Save all as HAR with content". The HAR contains the full request (URL, headers including Authorization: Bearer ..., response body, status code), which lets me check three things at once: (a) which API host the call actually went to, (b) what the sub claim in the token is — it needs to equal 3b23e6ff-aa66-8029-6ca7-3a214c3d575f, otherwise the U grant won't match, and (c) the traceId in the response body, so we can match it against the API log. Signing out and back in before reproducing rules out a stale token / cached session.
2) API-side debug logs of one failed request.
Please enable Debug-level logging on the API that's serving the failing request, reproduce the error once, and share the log around the failed call. The standard way to enable it is described here: https://abp.io/support/questions/8622/How-to-enable-Debug-logs-for-troubleshoot-problems — just bump your appsettings.json Serilog Default level to Debug, keep Microsoft.EntityFrameworkCore at Warning. The permission checker logs at Debug level which providers it tried and what each returned, so the log will show us exactly which value provider produced Undefined and why.
You can email the HAR and log files to me directly at liming.ma@volosoft.com — they tend to be large and may contain sensitive request data, so email is easier than attaching here.
One side question while you're at it: in your multi-portal setup, is the Patient Portal calling the same API host (same DB) as the admin portal where you opened the workspace permission modal? If they're separate services with separate databases (a common microservice setup), the grants you queried might live in a different DB than the one performing the check.
Thanks