Open Closed

Extending a module entity with custom properties in a new entity and exposing these with CRUD operations in a modified service/controller #438


User avatar
0
DanielAndreasen created

I have been following the guide on how to customize the app modules with the intention of extending module entities with new properties and exposing these with a modified service/controller.

AppUser.cs I have extended IdentityUser from a module through AppUser.cs (which was already created in the startup template) with the following properties:

Added columns in AbpUsers (MySql table) I then mapped these new properties to the same database table as IdentityUser by updating DbContext.OnModelCreating and EfCoreEntityExtensionMappings and created a database-migration:

Exposing new properties with CRUD operations I now wish to expose these new properties with CRUD operations in the API but I don't think the guide on overriding services covers this subject.

I understand that I can override the virtual methods defined in application services, controllers and interfaces that originates from the module where the IdentityUser type exists, however I find that the return value of these methods will always be of this type (either IdentityUser or IdentityUserDTO). In other words, the returned object doesn't have the custom properties i defined.

For example: Overriding the GetAsync() method from the IdentityUserAppService will always map from a IdentityUser object because that is the type Usermanager.GetByIdAsync() returns. Custom properties defined in the new UserDto class will never get mapped to because they don't exist in IndentityUser. UserDto has the same custom properties as the AppUser class mentioned above.

Calling ".../api/identity/users/" will return the following: Summary To summarize, I wish to achieve the following:

  • Query Users from the database and receive an object like UserDto with the custom properties
  • Update values of custom properties in AppUser
  • Use the value of custom properties in AppUser with the properties originating from IdentityUser in business logic

How do I override methods from applications services and controllers in a way that enables me to meet the requirements I listed above?

  • ABP Framework version: v3.1.0
  • UI type: Angular
  • Tiered (MVC) or Identity Server Seperated (Angular): No

15 Answer(s)
  • User Avatar
    0
    liangshiwei created
    Support Team Fullstack Developer

    Hi,

    Please see https://docs.abp.io/en/abp/latest/Object-Extensions

  • User Avatar
    0
    DanielAndreasen created

    Using the ObjectExtensionManager in [ProjectName]ModuleExtensionConfigurator (in Domain.Shared) I have added the following:

    However when I attempt to call the GetAsync method with a http client, I get an exception stating that IdentityUserDto can't be mapped to UserDto:

    For reference, take a look at the App Service and UserDto class: IdentityUser App service override IdentityUser Dto override

  • User Avatar
    0
    liangshiwei created
    Support Team Fullstack Developer

    Hi,

    You cannot change the return type in this way, you should to extending data transfer objects, see https://docs.abp.io/en/abp/latest/Customizing-Application-Modules-Overriding-Services#extending-data-transfer-objects

  • User Avatar
    0
    DanielAndreasen created

    Thank you, that solves this part of my issue. I do find it a little odd though that the extended properties will be serialized into children of "ExtraProperties". That would suggest that these properties are stored as json in the database even though that is not the case.

    Additionally, with my extended entity of Organization unit, which is related to the IdentityUser entity class, I also have a few navigation properties like below:

    These navigation properties should be handled in a corresponding EF core repository class which of course already exist (EfCoreOrganizationUnitRepository) in the identity module. What is the procedure for overriding a repository class from the identity module and then using it in an overridden app service? I don't think the documentation covers this.

  • User Avatar
    0
    liangshiwei created
    Support Team Fullstack Developer

    Can you explain the case in details

  • User Avatar
    0
    DanielAndreasen created

    EvemtTypes- OrganizationUnitEventTypesLink

    The properties (Geofences, Users, MonitoredObjects and EventTypes) I mentioned in my previous reply is part of my extension of the enitity OrganizationUnit which originates from the abp Identity Module, and act as navigation properties.

    Repository extension - EfCoreOrganizationUnitRepository I wish to extend the EfCoreOrganizationUnitRepository that originates from the abp Identity Module and keep all of the methods implemented in the module but also extend the repository with some of my own like shown below (GetCustomersAsync and GetEventTypesAsync):

    I expected that I could simply inherit from the module repository, inject my project's dbcontext and then use this in my own methods to get all EventTypes related to the specific OrganizationUnit for example.

    AppService extension - OrganizationUnitAppService However when I attempt to make use of this repository I am not able to call any of the methods I defined because they are not defined in the IOrganizationUnitRepository from the Identity Module.

    I would like to know how to extend the existing OrganizationUnitRepository in a way that makes it possible to use it in a AppService with the methods it already defines and those I have defined myself.

  • User Avatar
    0
    liangshiwei created
    Support Team Fullstack Developer

    Hi,

    I think you can create an interface, like:

    public interface IMyOrganizationRepository : IOrganizationUnitRepository
    {
        public Task GetCustomerAsync();
    }
    
    [Dependency(ReplaceServices = true)]
    [ExposeServices(typeof(IOrganizationUnitRepository), typeof(IMyOrganizationRepository))]
    public class EfCoreAppOrganizationUnitRepository : EfCoreOrganizationUnitRepository, IMyOrganizationRepository
    {
        public EfCoreAppOrganizationUnitRepository(IDbContextProvider<IIdentityDbContext> dbContextProvider) : base(
            dbContextProvider)
        {
        }
    
        public Task GetCustomerAsync()
        {
            return Task.CompletedTask;
        }
    }
    
    public class OrganizationUnitsAppService : OrganizationUnitAppService
    {
        public OrganizationUnitsAppService(
            IOrganizationUnitRepository organizationUnitRepository)
            : base(organizationUnitRepository)
        {
        }
    
        public Task GetMember()
        {
            return ((IMyOrganizationRepository)OrganizationUnitRepository).GetCustomerAsync();
        }
    }
    
  • User Avatar
    0
    DanielAndreasen created

    I have implemented the following based on the solution you suggested:

      public interface IAppOrganizationUnitRepository : IOrganizationUnitRepository
        {
            public Task<List<MonitoredObject>> GetMonitoredObjectsAsync(Guid organizationUnitId, CancellationToken cancellationToken = default);
        }
    
        [Dependency(ReplaceServices = true)]
        [ExposeServices(typeof(EfCoreOrganizationUnitRepository), typeof(IAppOrganizationUnitRepository))]
        public class EfCoreAppOrganizationUnitRepository : EfCoreOrganizationUnitRepository, IAppOrganizationUnitRepository
        {
    
            private readonly IDbContextProvider<StellaDbContext> _stellaDBContext;
    
            public EfCoreAppOrganizationUnitRepository(IDbContextProvider<IIdentityDbContext> dbContextProvider, IDbContextProvider<StellaDbContext> stellaDbContext)
                : base(dbContextProvider)
            {
                _stellaDBContext = stellaDbContext;
            }
            public async Task<List<MonitoredObject>> GetMonitoredObjectsAsync(
                Guid organizationUnitId, CancellationToken cancellationToken = default)
            {
    
                var query = from organizationUnitMonitoredObjectLink in _stellaDBContext.GetDbContext().OrganizationUnitMonitoredObjectLinks
                            join monitoredObject in _stellaDBContext.GetDbContext().MonitoredObjects on organizationUnitMonitoredObjectLink.MonitoredObjectId equals monitoredObject.Id
                            where organizationUnitMonitoredObjectLink.MonitoredObjectId == organizationUnitId
                            select monitoredObject;
    
                return await query.ToListAsync(GetCancellationToken(cancellationToken));
            }
        }
    
        [Dependency(ReplaceServices = true)]
        [ExposeServices(typeof(OrganizationUnitManager),typeof(IAppOrganizationUnitAppService))]
        public class OrganizationUnitsAppService : OrganizationUnitAppService, IAppOrganizationUnitAppService
        {
            public OrganizationUnitsAppService(OrganizationUnitManager organizationUnitManager,
                IdentityUserManager userManager,
                IOrganizationUnitRepository organizationUnitRepository,
                IIdentityUserRepository identityUserRepository,
                IIdentityRoleRepository identityRoleRepository)
                : base(organizationUnitManager, userManager, organizationUnitRepository, identityUserRepository, identityRoleRepository)
            {
    
            }
    
            public async Task<List<MonitoredObjectDto>> GetMonitoredObjectsAsync(Guid id)
            {
                var monitoredObjects = await ((IAppOrganizationUnitRepository)OrganizationUnitRepository).GetMonitoredObjectsAsync(id);
                return ObjectMapper.Map<List<MonitoredObject>,List<MonitoredObjectDto>>(monitoredObjects);
            }
    
        }
    
        [Dependency(ReplaceServices = true)]
        [ExposeServices(typeof(OrganizationUnitController),typeof(IAppOrganizationUnitAppService))]
        public class AppOrganizationUnitController : OrganizationUnitController, IAppOrganizationUnitAppService
        {
            public AppOrganizationUnitController(IOrganizationUnitAppService organizationUnitAppService)
                : base(organizationUnitAppService)
            {
    
            }
    
            [HttpGet]
            [Route("{id}/monitoredObjects")]
            public Task<List<MonitoredObjectDto>> GetMonitoredObjectsAsync(Guid id)
            {
                return ((IAppOrganizationUnitAppService)OrganizationUnitAppService).GetMonitoredObjectsAsync(id);
            }
        }
    

    However when I attempt to start the HttpApi.Host project I now get the following exception:

  • User Avatar
    0
    liangshiwei created
    Support Team Fullstack Developer

    I think should not expose service as OrganizationUnitManager, should be IOrganizationUnitAppService.

  • User Avatar
    0
    DanielAndreasen created

    That seems to fix the problem, thank you.

    Now my final issue is related to the controller routing, but this seems to be a known issue.

    When overriding methods from a module controller like I have done with OrganizationUnitController it will create routing conflicts when any endpoints are called (even though I have not made changes to the virtual controller methods originating from the module) and throw an exception: "Microsoft.AspNetCore.Routing.Matching.AmbiguousMatchException: The request matched multiple endpoints".

    Have you found a solution to this issue or is it planned as part of the 3.3 milestone?

  • User Avatar
    0
    liangshiwei created
    Support Team Fullstack Developer

    Does https://stackoverflow.com/questions/37213937/asp-net-mvc-core-replace-controllers-using-custom-class-library work for you?

  • User Avatar
    0
    DanielAndreasen created

    My issue is not solved by the solution mentioned in that link.

    It seems like the endpoints from the organizationunitcontroller in the identity module has been duplicated after I made an override of the controller. The examples of calls to endpoints that originates from the identity modules is illustrated below along with the errors I receive:

    localhost/api/identity/organization-units

    Microsoft.AspNetCore.Routing.Matching.AmbiguousMatchException: The request matched multiple endpoints. Matches: 
    
    Stella.Controllers.OrganizationUnits.AppOrganizationUnitController.GetListAsync (Stella.HttpApi)
    Volo.Abp.Identity.OrganizationUnitController.GetListAsync (Volo.Abp.Identity.Pro.HttpApi)
    

    localhost/api/identity/organization-units/{id}

    Microsoft.AspNetCore.Routing.Matching.AmbiguousMatchException: The request matched multiple endpoints. Matches: 
    
    Stella.Controllers.OrganizationUnits.AppOrganizationUnitController.GetAsync (Stella.HttpApi)
    Volo.Abp.Identity.OrganizationUnitController.GetAsync (Volo.Abp.Identity.Pro.HttpApi)
    
  • User Avatar
    0
    liangshiwei created
    Support Team Fullstack Developer

    Hi,

    It works for me.

    [RemoteService(true, Name = "AbpIdentity")]
    [Route("api/identity/organization-units", Order = 0)]
    [ControllerName("OrganizationUnit")]
    [ExposeServices(typeof(OrganizationUnitController))]
    [Area("identity")]
    public class MyOrganizationunitcontroller : OrganizationUnitController, IOrganizationUnitAppService
    {
        public MyOrganizationunitcontroller(IOrganizationUnitAppService organizationUnitAppService) : base(
            organizationUnitAppService)
        {
        }
    
    
        [HttpGet(Order = 1)]
        public override Task<PagedResultDto<OrganizationUnitWithDetailsDto>> GetListAsync(
            GetOrganizationUnitInput input)
        {
            return base.GetListAsync(input);
        }
    }
    

    Swagger config:

     context.Services.AddSwaggerGen(
    options =>
    {
        options.SwaggerDoc("v1", new OpenApiInfo {Title = "qa API", Version = "v1"});
        options.DocInclusionPredicate((docName, description) => true);
        options.ResolveConflictingActions (apiDescriptions => apiDescriptions.First());
    });
    

  • User Avatar
    0
    hikalkan created
    Support Team Co-Founder

    Closing this. See https://github.com/abpframework/abp/issues/5269#issuecomment-705567350

  • User Avatar
    0
    khalid created

    Hi Support Team I have the same issue above ,I extending

    my issue how to use dynamic controller to show the endpoint of the new method which I added in app service without extending OrganizationUnitController?

    I tried to use [RemoteService(IsEnabled = false)] to prevent creating duplicate controller  but the end point of new method  not showing

Made with ❤️ on ABP v9.1.0-preview. Updated on December 10, 2024, 06:38