Create the shared entities/tables without IMultiTenant (no TenantId column). These are naturally readable by everyone because they are not tenant-filtered.
For tables that need both tenant-specific rows and global rows, implement IMultiTenant and store shared rows with TenantId = null.
When querying from the tenant side, explicitly include both:
TenantId == CurrentTenantIdTenantId == null (global/shared)Example (repository or EF Core query):
var tenantId = CurrentTenant.Id;
var items = await _repo.GetQueryableAsync();
var result = await items
.Where(x => x.TenantId == tenantId || x.TenantId == null)
.ToListAsync();
Notes:
- ABP’s automatic multi-tenancy filter for `IMultiTenant` typically returns only the current tenant’s rows. So for this “tenant + global” scenario, you should **control the predicate yourself** (as above), or use a separate read model/service dedicated to this query pattern.
This requirement violates strict multi-tenancy isolation (ABP team explicitly calls out that if tenants must see other tenants’ data, it’s not a pure multi-tenant model).
Practical approach in ABP is to model “sharing” explicitly, e.g.:
TenantId or TenantId = null)EntityTenantShare(EntityId, TenantId) that defines which tenants can see itConceptual example:
var tenantId = CurrentTenant.Id;
var query = from e in await _entityRepo.GetQueryableAsync()
join s in await _shareRepo.GetQueryableAsync() on e.Id equals s.EntityId into shares
from share in shares.DefaultIfEmpty()
where e.TenantId == tenantId
|| (e.TenantId == null && share.TenantId == tenantId)
select e;
If you find many tables needing this, consider whether you actually want multi-tenancy in ABP terms, or a “single-tenant app with a Company/Group filter” using ABP Data Filtering (custom filters).
When you need “not all tables”
IMultiTenant are tenant-filtered. Others are shared.Links (verified):
This response was generated by AI. If it didn’t solve your problem, feel free to post a new answer — our support team will jump in to help you soon.
[Mapper]
public partial class CoreBookToBookDtoMapper : MapperBase<Book, BookDto>
{
public override partial BookDto Map(Book source);
public override partial void Map(Book source, BookDto destination);
}
dotnet build does. That’s why runtime/build is fine, but the IDE shows errors.Fix (practical, ABP-safe)
dotnet cleandotnet buildbin/ and obj/ folders for the project(s) containing the mappers, then rebuild.public override partial ... (don’t convert them to non-partial, and don’t remove override), because ABP’s MapperBase<TSource,TDestination> is designed for this pattern.If you follow the steps above, the IDE should stop reporting CS8795 while keeping the correct ABP + Mapperly integration.
Sources (verified)
This response was generated by AI. If it didn’t solve your problem, feel free to post a new answer — our support team will jump in to help you soon.
Sorting field sent in the GetListAsync request (a PagedAndSortedResultRequestDto / GetIdentityUsersInput-like DTO).GetListInput.Sorting.@* YourProject.Blazor/Pages/Identity/UserManagement.razor *@
@using Volo.Abp.Identity.Blazor.Pages.Identity
@using Volo.Abp.DependencyInjection
@attribute [ExposeServices(typeof(UserManagement))]
@attribute [Dependency(ReplaceServices = true)]
@inherits UserManagement
Sorting before loading:// YourProject.Blazor/Pages/Identity/UserManagement.razor.cs
using System.Threading.Tasks;
namespace YourProject.Blazor.Pages.Identity;
public partial class UserManagement
{
protected override async Task UpdateGetListInputAsync()
{
await base.UpdateGetListInputAsync();
if (string.IsNullOrWhiteSpace(GetListInput.Sorting))
{
// Pick any valid field exposed by the backend DTO:
// e.g. "UserName", "Name", "Email", etc.
GetListInput.Sorting = "UserName asc";
}
}
}
Now the page will always request users sorted by UserName asc unless the UI sets another sorting.
If you want clicking column headers to sort, ensure the page sets:
GetListInput.Sorting = CurrentSorting;
(where CurrentSorting is the value produced by the ABP CRUD/grid base page logic). This is the same pattern used in ABP Blazor CRUD pages to pass sorting to the backend.
This response was generated by AI. If it didn’t solve your problem, feel free to post a new answer — our support team will jump in to help you soon.
"/Account/Login" with missing ".AspNetCore.Antiforgery.*" cookie is almost always a cookie not being stored/sent back during the login POST inside that browser session./Account/Login fails exactly like your log.AuthServer:Authority you use in the mobile OIDC config is https://... (not http://...) and the browser is actually hitting HTTPS.Secure and then dropped/not returned by modern browsers.UseCookiePolicy() early.In your *.Web (or the host project that serves the /Account/* pages), add:
ConfigureServices
context.Services.AddSameSiteCookiePolicy();
OnApplicationInitialization (before auth middleware that writes cookies)
app.UseCookiePolicy();
This is the approach used to fix browser-specific login cookie issues.
This response was generated by AI. If it didn’t solve your problem, feel free to post a new answer — our support team will jump in to help you soon.
Your setting Configure<AbpAntiForgeryOptions>(o => o.AutoValidate = false) disables ABP’s global [AbpAutoValidateAntiforgeryToken] behavior for MVC/API endpoints, but your error is coming from Microsoft’s built-in Razor Pages antiforgery filter:
Microsoft.AspNetCore.Mvc.ViewFeatures.Filters.AutoValidateAntiforgeryTokenAuthorizationFilter
and the failing endpoint is the Razor Page /Account/Login.
In ABP (with the Account module), /Account/Login is a Razor Page and it’s expected to be protected by antiforgery because it is a cookie-based, browser login page. For mobile OIDC (Authorization Code + PKCE), you typically should not POST to /Account/Login from the mobile app at all. The mobile app should start the OIDC authorize flow in a system browser/custom tab, and the browser will handle cookies + antiforgery correctly.
If you still want to allow posting to /Account/Login without antiforgery (not recommended), do it specifically for that Razor Page by overriding the login page model and suppressing antiforgery validation.
/Account/Login from the mobile app/connect/authorize (system browser)/connect/token (back-channel)/Account/Login from the app. That endpoint is designed for interactive browser login./Account/Login, override the Razor Page and ignore antiforgery*.HttpApi.Host (or *.Web) project, create:Pages/Account/Login.cshtml.cs
using Microsoft.AspNetCore.Mvc;
using Volo.Abp.Account.Web.Pages.Account;
namespace MyCompany.MyApp.HttpApi.Host.Pages.Account;
[IgnoreAntiforgeryToken] // disables Razor Pages antiforgery validation for this page
public class LoginModel : Volo.Abp.Account.Web.Pages.Account.LoginModel
{
public LoginModel(
Microsoft.AspNetCore.Authentication.IAuthenticationSchemeProvider schemeProvider,
Microsoft.Extensions.Options.IOptions<Volo.Abp.Account.Web.AbpAccountOptions> accountOptions,
Microsoft.Extensions.Options.IOptions<Microsoft.AspNetCore.Identity.IdentityOptions> identityOptions,
Volo.Abp.Identity.IdentityDynamicClaimsPrincipalContributorCache contributorCache)
: base(schemeProvider, accountOptions, identityOptions, contributorCache)
{
}
}
Pages/Account/Login.cshtml
Pages/Account/Login.cshtml to override the UI (you can copy the original ABP login page markup from the Account module as described in ABP’s “customize login page” approach). The key part for antiforgery is the Login.cshtml.cs with [IgnoreAntiforgeryToken].AutoValidateAntiforgeryTokenAttribute from MvcOptions/Account/Login./Account/Login in the first place for OIDC.This response was generated by AI. If it didn’t solve your problem, feel free to post a new answer — our support team will jump in to help you soon.
ICurrentUser.FindImpersonatorUserId() != null). So the fastest way to debug is to verify whether impersonation is still active (claims/cookies) and whether the toolbar item is still being contributed.CurrentUser.FindImpersonatorUserId() is NOT null.null, the “back” button will not render. In that case, impersonation may still “look like it worked” (you’re logged in as the user) but the impersonator info wasn’t persisted (usually due to cookies/auth pipeline changes, custom account controller, reverse proxy cookie issues, etc.).For MVC, the button is not “magic”; it comes from the impersonation UI integration. If you customized your toolbar/layout/theme, you may have removed the contributor/view component that renders it.
Make sure you followed the MVC impersonation UI setup:
FindImpersonatorUserId() != null.POST ~/Account/BackToImpersonator exists (it’s the action the button posts to).If you’ve overridden AccountController, confirm it still inherits from the impersonation-enabled base controller as required by the docs.
To prove it’s only a UI/toolbar problem (not claims), temporarily add this to your main layout (or a toolbar area) and test while impersonating:
@using Volo.Abp.Account.Localization
@using Microsoft.AspNetCore.Mvc.Localization
@inject IHtmlLocalizer<AccountResource> L
<form method="post" data-ajaxForm="false" action="~/Account/BackToImpersonator">
@Html.AntiForgeryToken()
<button type="submit" class="btn btn-link text-danger">
@L["BackToImpersonator"]
</button>
</form>
There is a known case where the button is not visible depending on responsive/mobile toolbar placement (especially with LeptonX). In older versions it was fixed by upgrading (example reported/fixed in later versions for mobile view). Even on desktop, if your theme moved the user menu into a different place, the button may now be under the username menu instead of the top bar.
If you have custom IToolbarContributor code, ensure it’s contributing to the toolbar your layout uses (e.g., StandardToolbars.Main). A wrong toolbar name check can prevent the item from being added.
Links I verified:
This response was generated by AI. If it didn’t solve your problem, feel free to post a new answer — our support team will jump in to help you soon.
/Account/Register?isExternalLogin=True to auto-create/link the user.The provided antiforgery token was meant for a different claims-based user than the current user.System.Collections.Generic.KeyNotFoundException: The given key 'IsSeeding' was not present in the dictionary.Cns.Cloud.Apps.Users.UserCreatedEventHandler.HandleEventAsync(...).So you have two issues:
UserCreatedEventHandler throws, which breaks the request (this is the primary failure)./Error?httpStatusCode=400 with a changed user context.IsSeeding exists (use TryGetValue / ContainsKey, or provide a default). Example pattern:public Task HandleEventAsync(EntityCreatedEto<IdentityUserEto> eventData)
{
// Example: if you stored extra properties somewhere, never index directly
// var isSeeding = dict["IsSeeding"]; // <- unsafe
var isSeeding = false;
if (eventData.Properties != null &&
eventData.Properties.TryGetValue("IsSeeding", out var value) &&
value is bool b)
{
isSeeding = b;
}
if (isSeeding)
{
return Task.CompletedTask;
}
// normal logic...
return Task.CompletedTask;
}
(Adapt to your actual dictionary/object; the key point is don’t index with [...] unless you’re 100% sure it’s there.)
/Account/Register?isExternalLogin=True appears).This response was generated by AI. If it didn’t solve your problem, feel free to post a new answer — our support team will jump in to help you soon.
IEmailSender with your own implementation that reads the current tenant’s configuration and sends using OAuth (e.g., Microsoft Graph, Gmail API, or SMTP with OAuth2 via MailKit if that’s your provider).Define your own setting names and register them with a SettingDefinitionProvider. ABP settings are automatically resolved per-tenant.
public static class TenantEmailOAuthSettings
{
private const string Prefix = "MyApp.Email.OAuth";
public const string Provider = Prefix + ".Provider"; // e.g. "MicrosoftGraph", "Gmail"
public const string TenantId = Prefix + ".TenantId";
public const string ClientId = Prefix + ".ClientId";
public const string ClientSecret = Prefix + ".ClientSecret";
public const string FromAddress = Prefix + ".FromAddress";
public const string FromDisplayName = Prefix + ".FromDisplayName";
}
Then, create a custom settings UI page (Blazor Server) similar to how custom setting pages are added (same technique as “white labeling” examples: define settings + implement an app service using ISettingManager + add a UI page/group).
IEmailSender with a tenant-aware OAuth senderImplement IEmailSender and read settings using ISettingProvider (which resolves values in the context of the current tenant).
using System.Net.Mail;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Emailing;
using Volo.Abp.Settings;
public class TenantOAuthEmailSender : IEmailSender, ITransientDependency
{
private readonly ISettingProvider _settingProvider;
public TenantOAuthEmailSender(ISettingProvider settingProvider)
{
_settingProvider = settingProvider;
}
public async Task SendAsync(MailMessage mail, bool normalize = true)
{
// Read per-tenant OAuth configuration
var provider = await _settingProvider.GetOrNullAsync(TenantEmailOAuthSettings.Provider);
var clientId = await _settingProvider.GetOrNullAsync(TenantEmailOAuthSettings.ClientId);
var clientSecret = await _settingProvider.GetOrNullAsync(TenantEmailOAuthSettings.ClientSecret);
// Use these to send via your chosen OAuth provider (Graph/Gmail/etc.)
// (Implementation depends on provider; this is where you call Graph API, etc.)
throw new NotImplementedException();
}
public Task SendAsync(string to, string subject, string body, bool isBodyHtml = true)
=> SendAsync(new MailMessage { Subject = subject, Body = body, IsBodyHtml = isBodyHtml, To = { to } });
public Task SendAsync(string from, string to, string subject, string body, bool isBodyHtml = true)
=> SendAsync(new MailMessage(from, to, subject, body) { IsBodyHtml = isBodyHtml });
public Task QueueAsync(string to, string subject, string body, bool isBodyHtml = true)
=> SendAsync(to, subject, body, isBodyHtml); // or integrate with background jobs
}
Register it by replacing ABP’s default email sender:
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Volo.Abp.Modularity;
public class MyAppEmailModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
context.Services.Replace(ServiceDescriptor.Transient<IEmailSender, TenantOAuthEmailSender>());
}
}
If you want tenants to edit email-related settings themselves, ABP can gate this via tenant features (SaaS). In practice, you enable the relevant tenant feature in the SaaS tenant management screen (this is how built-in “Emailing” settings visibility is controlled).
ABP’s default UI won’t cover OAuth flows. You typically:
IEmailSender implementation.Sources (verified)
This response was generated by AI. If it didn’t solve your problem, feel free to post a new answer — our support team will jump in to help you soon.
changePasswordToken is not an OpenIddict/OpenID Connect token type and there is no built-in OAuth/OIDC endpoint (like /connect/token) that “uses” it in ABP.
In ABP, a “change password / reset password token” is an ASP.NET Core Identity user token (generated by UserManager.GeneratePasswordResetTokenAsync(...) / related APIs) and it must be consumed by an ABP Account endpoint (HTTP API) that performs the password change/reset (not the OpenIddict token endpoint).
So the correct flow is typically:
/connect/token with password / authorization_code / etc.).If your goal is: “I want to exchange changePasswordToken for an access token at /connect/token”, that’s not supported by default. You would need to implement a custom OpenIddict extension grant (ITokenExtensionGrant) and validate your token yourself, then issue an access token.
Minimal outline (ABP’s OpenIddict extension grant mechanism):
OpenIddictServerOptions.GrantTypesITokenExtensionGrant implementation in AbpOpenIddictExtensionGrantsOptionsHandleAsync(...), validate the incoming token and return SignInResult to generate a token response.Sources (validated):
This response was generated by AI. If it didn’t solve your problem, feel free to post a new answer — our support team will jump in to help you soon.
Please contact us at https://abp.io/contact.
This response was generated by AI. If it didn’t solve your problem, feel free to post a new answer — our support team will jump in to help you soon.