Open Closed

How to generate a self-contained HttpApi.Client package for integration services only? #10876


User avatar
0
remitrevisanuto created

Exception message and full stack trace

No exception. Compile-time issue on the generated client project (details in the steps below).

Steps to reproduce the issue

Context
-------
Modular monolith (not microservices), ABP Commercial 10.6.0, .NET 10, ABP CLI 3.1.1.
Two applications, AppA and AppB, versioned and deployed independently.
AppA.HttpApi.Client is published to a private NuGet feed and consumed by AppB.
AppA.Application.Contracts is NOT published: our CI only packages HttpApi.Client.

What we want
------------
AppA exposes 2 integration services. We want AppA.HttpApi.Client to be self-contained,
so that AppB can consume those contracts without referencing AppA.Application.Contracts.

Steps
-----
1. In AppA, declare the integration services with [IntegrationService] and set
   options.ExposeIntegrationServices = true in ConfigureConventionalControllers().
   They live in business namespaces, e.g. MyApp.Param_Ops.Ops.Numbers.

2. Start the host and run:
   abp generate-proxy -t csharp -u https://localhost:44314/ -m app -st integration

3. The 2 integration service interfaces and their client proxies are generated - correct.
   But the generated DTOs are not the transitive closure of those interfaces: out of 723
   exposed types, 73 are generated, matching exactly the namespaces (and sub-namespaces)
   of the 2 integration service controllers, whether or not they are reachable from the
   integration service signatures.

4. As a result, application DTOs that merely share the namespace are generated too (e.g.
   NumberWithRioDto), and they reference types from OTHER namespaces (e.g. CountryDto in
   Params.Countries) which are not generated. Building the client project standalone
   fails with 34 errors:

   error CS0234: The type or namespace name 'CountryDto' does not exist in the namespace
   'MyApp.Param_Ops.Params.Countries'

   Keeping a ProjectReference to Application.Contracts makes it compile, but every DTO
   then exists twice (generated file + imported type), producing 101 CS0436 warnings, and
   dotnet pack declares a NuGet dependency on a package we do not publish (NU1101 for the
   consumer). --without-contracts requires that same contracts package, which is exactly
   the coupling we are trying to avoid.

Question
--------
What is the recommended way to obtain a self-contained HttpApi.Client package exposing
only the integration service contracts, for applications that are packaged and deployed
independently?

We got it to work by moving the integration services and their dedicated *IntegrationDto
types into a separate namespace, but shared enums (StatusNumber, NumberType, ...) live in
AppA.Domain.Shared under the application namespaces, are used across the whole
application, and therefore are not generated. We had to duplicate them into the
integration namespace, which we would rather avoid since it creates two declarations of a
single business concept with no build-time protection against divergence.

Is that the expected approach, or is there a supported way to have the generator emit the
types a contract actually depends on, even when they belong to another namespace?
Markdown supported.
Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post)

2 Answer(s)
  • User Avatar
    0
    AI-Bot created
    Support Team ABP Agent
    • In ABP 10.6, the C# proxy generator is not a “dependency graph / transitive closure” generator.
      When you run abp generate-proxy -t csharp ... -st integration, ABP determines the “models to generate” primarily by namespace grouping (service namespace and related model namespaces), not by recursively walking every referenced DTO/enum type across other namespaces. That’s why you see:

      • DTOs in the same namespace getting generated even if not referenced by the integration service signatures, and
      • referenced DTOs/enums from other namespaces not generated, causing compile errors in a standalone HttpApi.Client package.
    • Recommended/supported approach for independently deployed apps (AppA → NuGet → AppB):

      1. Create a dedicated Integration Contracts surface (a stable boundary) and generate/publish the client from that:
        • Put integration service interfaces + integration DTOs/enums into a dedicated project/namespace (e.g. AppA.Integration.Contracts).
        • Keep them separate from internal application DTOs.
        • Generate the C# client proxies from integration services and package them.
      2. If you want to reuse “shared enums” without duplicating, the supported way is to move those shared enums (and any shared DTOs) into a shared contracts assembly that is part of your integration boundary and is published alongside (or referenced by) the client package.
        Practically:
        • Move StatusNumber, NumberType, etc. into something like AppA.Domain.Shared but in an integration-safe namespace/package (or a new AppA.Integration.Shared project).
        • Reference that shared package from both AppA and AppB.
        • Keep the integration DTOs referencing only types from:
          • ABP framework packages, and
          • your published shared integration contracts packages.
    • What you’re doing now (moving Integration services + IntegrationDto into a separate namespace) is essentially the intended boundary.
      The missing piece is: anything used by that boundary must also live in (or be referenced by) that boundary, otherwise the generated client can’t be self-contained because the generator won’t chase types across your whole solution and emit a perfect closure.

    • What not to rely on

      • --without-contracts is for the opposite scenario: it assumes the consumer already references the target service’s *.Application.Contracts package so types are reused (and avoids duplication). It won’t help you make a single self-contained package if you don’t publish those contracts.

    Sources (verified):

    • https://abp.io/docs/10.6/framework/api-development/static-csharp-clients
    • https://abp.io/docs/10.6/release-info/migration-guides/abp-7-0
    • https://abp.io/docs/10.6/release-info/migration-guides/abp-7-4

    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.

    Markdown supported.
    Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post)
  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    Hi,

    The C# proxy generator picks the DTOs by the namespace prefix of the service class, not by the types your method signatures actually use. That's why DTOs that only share the namespace get generated, and types your contracts depend on but live elsewhere don't. There is no switch to change that today.

    So rather than generating the contracts, publish them - that's what our own modules do. Volo.Abp.Identity.HttpApi.Client references the published Volo.Abp.Identity.Application.Contracts and is generated with --without-contracts, so it carries client proxy classes only and nothing is duplicated.

    For you that is a small AppA.Integration.Contracts project with the two integration service interfaces and their *IntegrationDto types. It references AppA.Domain.Shared, so StatusNumber and NumberType stay exactly where they are - no move, no second declaration. AppA.Application.Contracts references the new project, and you publish AppA.Domain.Shared and AppA.Integration.Contracts next to AppA.HttpApi.Client.

    [DependsOn(
        typeof(AppADomainSharedModule),
        typeof(AbpDddApplicationContractsModule)
    )]
    public class AppAIntegrationContractsModule : AbpModule
    {
    }
    

    In AppA.HttpApi.Client, swap the AppA.Application.Contracts project reference for AppA.Integration.Contracts, replace that one entry in DependsOn and keep the rest, and register the proxies over the contracts assembly:

    [DependsOn(
        typeof(AppAIntegrationContractsModule), // replaces AppAApplicationContractsModule
        typeof(AbpHttpClientModule),
        typeof(AbpIdentityHttpApiClientModule), // the other module dependencies stay as they are
        // ...
    )]
    public class AppAHttpApiClientModule : AbpModule
    {
        public const string RemoteServiceName = "Default";
    
        public override void ConfigureServices(ServiceConfigurationContext context)
        {
            context.Services.AddStaticHttpClientProxies(
                typeof(AppAIntegrationContractsModule).Assembly,
                RemoteServiceName
            );
    
            Configure<AbpVirtualFileSystemOptions>(options =>
            {
                options.FileSets.AddEmbedded<AppAHttpApiClientModule>();
            });
        }
    }
    

    Remove the previously generated files before you regenerate - generate-proxy overwrites and adds, it never removes, so the 73 old DTOs would stay behind and keep producing CS0436. remove-proxy deletes the whole ClientProxies folder, so move any partial proxy class you have customized out of the way first:

    abp remove-proxy -t csharp
    abp generate-proxy -t csharp -u https://localhost:44314/ -m app -st integration --without-contracts
    

    You end up with the two client proxy classes and the proxy json, no DTOs. AppB references AppA.HttpApi.Client and gets the contracts and the enums through it, so there is a single declaration of each enum and no dependency on AppA.Application.Contracts.

    We'll also improve the generator to emit the types a contract actually depends on instead of relying on the namespace grouping.

    Thanks

    Markdown supported.
    Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post)
Boost Your Development
ABP Live Training
Packages
See Trainings
Mastering ABP Framework Book
The Official Guide
Mastering
ABP Framework
Learn More
Mastering ABP Framework Book
Made with ❤️ on ABP v10.8.0-preview. Updated on September 28, 2026, 11:44
1
ABP Assistant
🔐 You need to be logged in to use the chatbot. Please log in first.