mongodb/mongodb-enterprise-server:latest in the ABP etc/docker compose for local development if you want MongoDB 8.x. That “enterprise-server” image/tag can change behavior across major versions and may fail to start with your existing local volumes/config, especially after you previously ran a different major version.mongo:8.0 image and (recommended for ABP features like transactions) run it as a single-node replica set. This is the setup ABP documents for a transaction-enabled MongoDB in Docker.Update your etc/docker/docker-compose*.yml Mongo service to something like:
version: "3.8"
services:
mongo-db:
container_name: mongodb
image: mongo:8.0
command: ["--replSet", "rs0", "--bind_ip_all", "--port", "27017"]
ports:
- "27017:27017"
volumes:
- mongodata:/data/db
- mongoconfigdata:/data/configdb
healthcheck:
test: echo "try { rs.status() } catch (err) { rs.initiate({_id:'rs0',members:[{_id:0,host:'127.0.0.1:27017'}]}) }" | mongosh --port 27017 --quiet
interval: 5s
timeout: 30s
start_period: 0s
start_interval: 1s
retries: 30
volumes:
mongodata:
mongoconfigdata:
Then reset volumes (important when switching major versions) and start again:
docker compose down -v
docker compose up -d
This ensures old 7.x data files aren’t reused by 8.x in a way that prevents a clean start.
Sources:
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.
Yes, you can place the handler in .Domain, if your .Domain project references the ABP Event Bus packages/modules you need (because IDistributedEventHandler<T> is an infrastructure concern). Practically, many teams put distributed event handlers in .Application (or a dedicated *.EventHandlers project) to keep the Domain layer pure, but ABP does not technically prevent you from implementing event handlers in .Domain as long as DI can discover/register them.
If you put it in .Domain, ensure it’s registered and runs inside a Unit of Work:
IDistributedEventHandler<...>ITransientDependency[UnitOfWork], make the handler method virtual so interception works (otherwise you may hit “DbContext can only be created inside a unit of work”).Person when IdentityUser is deleted (distributed entity event)Handle the distributed entity deleted event and delete the corresponding Person row by UserId.
using System;
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.EventBus.Distributed;
using Volo.Abp.EventBus.Entities;
using Volo.Abp.Identity;
using Volo.Abp.Uow;
public class IdentityUserDeleted_DeletePersonHandler :
IDistributedEventHandler<EntityDeletedEto<IdentityUserEto>>,
ITransientDependency
{
private readonly IRepository<Person, Guid> _personRepository;
public IdentityUserDeleted_DeletePersonHandler(IRepository<Person, Guid> personRepository)
{
_personRepository = personRepository;
}
[UnitOfWork]
public virtual async Task HandleEventAsync(EntityDeletedEto<IdentityUserEto> eventData)
{
var userId = eventData.Entity.Id;
var person = await _personRepository.FirstOrDefaultAsync(x => x.UserId == userId);
if (person == null)
{
return; // idempotent
}
await _personRepository.DeleteAsync(person, autoSave: true);
}
}
If your Person is soft-deletable (implements ISoftDelete), you may prefer DeleteAsync (it will soft-delete). If you want to hard delete regardless, you’d need to bypass soft-delete filters/behavior (implementation depends on your setup), but the above is the standard ABP approach.
Because this is cross-service, you should enable/consider Outbox/Inbox so user deletion + event publishing and event consumption are reliable and transactional on each side.
Links:
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.
IdentityUser is created in your AuthServer (separate/tiered), automatically create a 1-to-1 Person row in your main application using ABP Distributed Event Bus.IdentityUser (in AuthServer)ABP can automatically publish distributed events for entity create/update/delete once you configure it.
In your AuthServer module (where EF Core + Identity runs), configure distributed entity events for IdentityUser by registering an Event Transfer Object (ETO) for it (so it can be sent over the distributed bus):
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.EventBus.Distributed;
using Volo.Abp.Identity;
public override void ConfigureServices(ServiceConfigurationContext context)
{
Configure<AbpDistributedEntityEventOptions>(options =>
{
options.EtoMappings.Add<IdentityUser, IdentityUserEto>();
// IdentityUserEto exists in ABP and is used for transferring IdentityUser over distributed bus.
});
}
Once this is configured, when a user is created in AuthServer (e.g., via IdentityUserAppService / IdentityUserManager), ABP will publish a distributed entity-created event for that entity.
In the service that owns the Person table, create a distributed event handler that listens for the entity created event and inserts a Person record.
Conceptually:
IdentityUserEtoPerson with UserId = eventData.Entity.Id (and any other fields you want)Example shape:
using System;
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.EventBus.Distributed;
using Volo.Abp.EventBus.Entities;
using Volo.Abp.Identity;
public class IdentityUserCreated_CreatePersonHandler :
IDistributedEventHandler<EntityCreatedEto<IdentityUserEto>>,
ITransientDependency
{
private readonly IRepository<Person, Guid> _personRepository;
public IdentityUserCreated_CreatePersonHandler(IRepository<Person, Guid> personRepository)
{
_personRepository = personRepository;
}
public async Task HandleEventAsync(EntityCreatedEto<IdentityUserEto> eventData)
{
var user = eventData.Entity;
// Idempotency: ensure you don’t insert duplicates (important for distributed systems)
var existing = await _personRepository.FirstOrDefaultAsync(x => x.UserId == user.Id);
if (existing != null)
{
return;
}
await _personRepository.InsertAsync(
new Person(id: GuidGenerator.Create())
{
UserId = user.Id,
// populate other fields if needed (Name/Surname/Email etc. are available on the ETO if mapped)
},
autoSave: true
);
}
}
Make sure:
Person in the same DB transaction as user creation across services. Distributed events are asynchronous; design the handler to be idempotent (as shown).Person needs “current user” auditing fields (CreatorId, etc.), remember that distributed handlers typically run without the original HTTP user context. If you need to set audit fields based on a specific principal, ABP provides a way to change the current principal in a scope.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.
abp.dotnet nuget locals all --clear
abp --version
(or the command you were running previously)
C:\Users\<you>\.nuget\packages\volo.abp.studio.extensions.standardsolutiontemplates\2.2.7\dotnet nuget locals all --clear
abp --version
Sources:
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.
In ABP Blazor (including WASM) the expand/collapse behavior of parent menu items is purely client-side (handled by the theme’s Blazor/JS code). If the menu items are added/changed after the side menu has already been initialized (e.g., after an async call like EnsureLoadedAsync()), the DOM gets updated but the theme’s “bind click handlers / initialize side-menu” step won’t automatically re-run, so parent items stay collapsed and not expandable.
Fix: Re-initialize the LeptonX side menu after you dynamically build the menu.
wwwroot/index.html (or a JS file included by it) to re-run LeptonX’s side-menu initializer if it exists:<script>
window.reinitLeptonXSideMenu = function () {
const fn = window.leptonx
&& window.leptonx.init
&& window.leptonx.init.initializers
&& window.leptonx.init.initializers.get('initSideMenu');
if (fn) {
fn(); // rebind expand/collapse handlers
}
};
</script>
@inject IJSRuntime JS
protected override async Task OnAfterRenderAsync(bool firstRender)
{
// If your menu can change later (async), you can also call this whenever it changes.
await JS.InvokeVoidAsync("reinitLeptonXSideMenu");
}
If you have a specific place where you know the menu has just been updated (e.g., after your state/service finishes loading), call it there too.
Important: Do not try to build menus from an AppService at runtime expecting the UI to “just expand” without re-init. ABP’s IMenuContributor is designed for building the menu, but the theme initialization still must see the final DOM to wire expand/collapse.
Your ApplicationMenuItem code is fine. The symptom you describe (HTML contains sub-items but groups can’t expand) matches exactly “menu JS init ran before items existed”.
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.
If you decide to skip ObjectMapper.Map / Mapperly entirely for this “map only these fields” scenario, then yes: a reusable reflection-based patch is the simplest way to preserve your existing requiredProperties contract.
For “special handling” like double? -> CurrencyObject[]?, don’t hardcode it in every mapping. Instead, add a converter registry (dictionary) keyed by (sourceType, destType) and let the patcher use it when the property types don’t match.
Create one generic patcher which:
requiredPropertiesdouble? -> CurrencyObject[]?)using System.Collections.Concurrent;
using System.Reflection;
public static class ReflectionPatchMapper
{
private static readonly ConcurrentDictionary<(Type Src, Type Dest), (PropertyInfo[] SrcProps, Dictionary<string, PropertyInfo> DestProps)>
Cache = new();
// Register custom converters here (extend as needed)
private static readonly ConcurrentDictionary<(Type Src, Type Dest), Func<object?, object?>> Converters = new();
static ReflectionPatchMapper()
{
// Example: double? -> CurrencyObject[]?
Converters[(typeof(double?), typeof(CurrencyObject[]))] = src =>
{
var v = (double?)src;
return v.HasValue
? new[]
{
new CurrencyObject { CurrencyValue = v.Value, Currency = "USD" }
}
: null;
};
// If your destination property is CurrencyObject[]? (nullable annotation),
// reflection sees it as CurrencyObject[] (same runtime type), so this works.
}
public static void Patch<TSource, TDestination>(
TSource source,
TDestination destination,
IReadOnlyCollection<string> requiredProperties)
where TSource : class
where TDestination : class
{
if (requiredProperties == null || requiredProperties.Count == 0)
{
return;
}
var key = (typeof(TSource), typeof(TDestination));
var meta = Cache.GetOrAdd(key, static k =>
{
var srcProps = k.Src.GetProperties(BindingFlags.Instance | BindingFlags.Public)
.Where(p => p.CanRead)
.ToArray();
var destProps = k.Dest.GetProperties(BindingFlags.Instance | BindingFlags.Public)
.Where(p => p.CanWrite)
.ToDictionary(p => p.Name, p => p);
return (srcProps, destProps);
});
// Build a quick lookup for source properties by name
// (optional: cache too; kept simple here)
var srcByName = meta.SrcProps.ToDictionary(p => p.Name, p => p);
foreach (var name in requiredProperties)
{
if (!meta.DestProps.TryGetValue(name, out var destProp))
continue;
if (!srcByName.TryGetValue(name, out var srcProp))
continue;
var srcValue = srcProp.GetValue(source);
// 1) direct assignment if compatible
if (destProp.PropertyType.IsAssignableFrom(srcProp.PropertyType))
{
destProp.SetValue(destination, srcValue);
continue;
}
// 2) try registered converter
if (Converters.TryGetValue((srcProp.PropertyType, destProp.PropertyType), out var convert))
{
var converted = convert(srcValue);
destProp.SetValue(destination, converted);
continue;
}
// else: ignore or throw (your choice). I’d recommend ignore to match your old behavior.
}
}
// Optional: allow registering converters from modules
public static void RegisterConverter<TSrc, TDest>(Func<TSrc, TDest> converter)
=> Converters[(typeof(TSrc), typeof(TDest))] = o => converter((TSrc)o!);
}
Usage:
ReflectionPatchMapper.Patch(uploaded, surveyData, requiredProperties);
This gives you:
ObjectMapper.MapThis 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.
List<string>” cannot be applied during the generated deep object-graph mapping. That’s not an ABP limitation; it’s inherent to Mapperly being a compile-time source generator. So for your exact AutoMapper behavior (dynamic, runtime-selected member set applied across a big graph), there is no Mapperly-native equivalent.Given your constraints (large graph, shared extension used everywhere, long property list, and you don’t want reflection-copy of the entire graph), you have only these practical options:
ABP v10+ can use Mapperly as default while you still keep AutoMapper for specific cases. Use Mapperly for the majority of mappings, but keep AutoMapper profiles for the “patch by property list” scenarios where runtime configuration is essential.
This is the only option that preserves:
(ABP explicitly allows keeping AutoMapper in your app even after v10.)
If you can change the shape, this is the clean Mapperly solution:
List<string> requiredProperties with a DTO which contains only patchable fields (often nullable) and/or nested patch DTOs.This avoids reflection and avoids string lists completely, but it’s a refactor.
To drive a runtime list, something must interpret that list at runtime. If you don’t want to reflect-copy the whole graph, the best you can do is:
Address.Street, which gets complex quickly),But fundamentally: without changing the contract, complexity has to live somewhere, and Mapperly won’t generate “dynamic graph patch” code for you.
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.
You’re right: calling the Mapperly Map(source, destination) first and then “patching” is too late if the unwanted members have already been written into destination.
The fix is: never map directly into the real destination when you need runtime-selected members.
Use a two-step approach:
That way, the “ignored” members are never touched on the real entity.
// GOOD for "only update these fields"
PatchExtensions.PatchFromMapped(uploaded, surveyData, mapper.Map, requiredProperties);
// BAD for "only update these fields" (too late)
// mapper.Map(uploaded, surveyData);
// PatchExtensions.PatchFromExisting(surveyData, requiredProperties);
A complete reusable helper (same concept as the other user posted) looks like this:
using System.Collections.Concurrent;
using System.Reflection;
public static class PatchExtensions
{
private static readonly ConcurrentDictionary<Type, Dictionary<string, PropertyInfo>> WritablePropertyCache = new();
public static void PatchFromMapped<TSource, TDestination>(
TSource source,
TDestination destination,
Func<TSource, TDestination> mapFunc,
IReadOnlyCollection<string>? requiredProperties)
where TSource : class
where TDestination : class
{
if (requiredProperties == null || requiredProperties.Count == 0)
{
return;
}
// 1) Map into a NEW object (destination not touched yet)
var mapped = mapFunc(source);
// 2) Copy only selected properties into the REAL destination
var writableProps = WritablePropertyCache.GetOrAdd(
typeof(TDestination),
static t => t.GetProperties(BindingFlags.Instance | BindingFlags.Public)
.Where(p => p.CanWrite)
.ToDictionary(p => p.Name));
foreach (var name in requiredProperties)
{
if (writableProps.TryGetValue(name, out var prop))
{
prop.SetValue(destination, prop.GetValue(mapped));
}
}
}
}
Where to put / call it in ABP: inside the method where you update the entity (usually an Application Service method), after you load the entity and before UpdateAsync / SaveChanges.
If your existing workflow depends on “requiredProperties empty means map everything”, you can implement that behavior too:
if (requiredProperties == null || requiredProperties.Count == 0)
{
// full update
mapper.Map(source, destination);
return;
}
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.
Example: you receive UploadedSurveyData + requiredProperties, you load/update an existing SurveyData entity, then patch:
public class SurveyAppService : ApplicationService
{
private readonly SurveyDataMapper _mapper; // your Mapperly mapper
private readonly IRepository<SurveyData, Guid> _repo;
public SurveyAppService(SurveyDataMapper mapper, IRepository<SurveyData, Guid> repo)
{
_mapper = mapper;
_repo = repo;
}
public async Task UpdateFromUploadAsync(Guid id, UploadedSurveyData uploaded, List<string> requiredProperties)
{
var surveyData = await _repo.GetAsync(id);
// Only apply the selected fields:
PatchExtensions.PatchFromMapped(uploaded, surveyData, _mapper.Map, requiredProperties);
await _repo.UpdateAsync(surveyData, autoSave: true);
}
}
PatchExtensions classPatchExtensions in a shared place where all mappings can use it, e.g.:
*.Application project (common for DTO→Entity update helpers), or*.Domain.Shared / *.Shared project if multiple modules need it.This is consistent with ABP’s object-mapping usage pattern (mapping is typically executed inside application service methods when creating/updating entities).
Reference (validated): https://abp.io/docs/latest/framework/infrastructure/object-to-object-mapping
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.
The Project target does not exist. error means Angular CLI can’t find a project named dev-app in your workspace configuration (i.e., angular.json). In ABP module solutions, you typically don’t run ng serve dev-app directly; you run the workspace start script (or ng serve with the correct project name).
Do this instead:
angular.json is):cd angular
yarn
# or: npm install
yarn start
# or: npm start
If you specifically want to use ng serve <projectName> then open angular/angular.json and look under:
projects: { ... }<projectName>.dev-app (even if the folder is projects/dev-app), so ng serve dev-app fails.About “dev-app has only environment file”: In ABP module templates, the dev app is a lightweight host for module development; after installing dependencies and running via yarn start, it should serve correctly. Also ensure you’ve adjusted:
angular/projects/dev-app/src/environments/environment.ts
to point to your running Host application URL.
Sources:
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.