dotnet/wcf

`dotnet-svcutil` generates incomplete client classes when providing multiple WSDL services

jacobjmarks opened this issue · 2 comments

Describe the bug
When attempting to generate a single C# service client reference for more than one WSDL spec - for the purposes of type reuse - all but the last service client class contains missing elements. See details below.

To Reproduce
Please find below a sample project which demonstrates this issue.
https://github.com/jacobjmarks/dotnet-svcutil-issue-demo

Contained within the repository are 3 WSDL services which closely represent the format of services I am looking to generate.

In general, all services each contain a single operation with unique request/response models, and all services reference a common reference type called ServiceFault, in this case. I am trying to generate the service clients in a way such that these common reference types between services are in fact shared in the resultant C# code.

There are two scripts as described below:

  • generate-service-clients.ps1
    Uses the dotnet-svcutil command once for each service; i.e.

    dotnet-svcutil service-a.wsdl
    dotnet-svcutil service-b.wsdl
    dotnet-svcutil service-c.wsdl
    

    Generates separate service clients within the MyProject/ServiceReference folder; i.e. ServiceA.cs, ServiceB.cs, ServiceC.cs.
    The results here are as expected: All service client classes contain the following constructors, as well as the partial ConfigureEndpoint method, as below;

    static partial void ConfigureEndpoint(System.ServiceModel.Description.ServiceEndpoint serviceEndpoint, System.ServiceModel.Description.ClientCredentials clientCredentials);
    public ServiceAServiceClient() : base(ServiceAServiceClient.GetDefaultBinding(), ServiceAServiceClient.GetDefaultEndpointAddress()) { ... }
    public ServiceAServiceClient(EndpointConfiguration endpointConfiguration) : base(ServiceAServiceClient.GetBindingForEndpoint(endpointConfiguration), ServiceAServiceClient.GetEndpointAddress(endpointConfiguration)) { ... }
    public ServiceAServiceClient(EndpointConfiguration endpointConfiguration, string remoteAddress) : base(ServiceAServiceClient.GetBindingForEndpoint(endpointConfiguration), new System.ServiceModel.EndpointAddress(remoteAddress)) { ... }
    public ServiceAServiceClient(EndpointConfiguration endpointConfiguration, System.ServiceModel.EndpointAddress remoteAddress) : base(ServiceAServiceClient.GetBindingForEndpoint(endpointConfiguration), remoteAddress) { ... }
    public ServiceAServiceClient(System.ServiceModel.Channels.Binding binding, System.ServiceModel.EndpointAddress remoteAddress) : base(binding, remoteAddress) { ... }
  • generate-shared-service-clients.ps1
    Uses the dotnet-svcutil once for all services; i.e.

    dotnet-svcutil service-a.wsdl service-b.wsdl service-c.wsdl
    

    Generates a shared service client within the MyProject/SharedServiceReference folder; i.e. Services.cs.
    The results here are not as expected: All but the last service client class contains only the following constructor (the partial ConfigureEndpoint method is also missing):

    public ServiceAServiceClient(System.ServiceModel.Channels.Binding binding, System.ServiceModel.EndpointAddress remoteAddress) : base(binding, remoteAddress) { ... }

    In addition, the last (fully-featured) service client class seems to erroneously contain the GetBindingForEndpoint and GetEndpointAddress methods as well as the EndpointConfiguration enum which contain endpoint information for all three services.

Expected behavior
When passing multiple WSDL services to the dotnet-svcutil command, fully-featured service client classes are generated as if they were individually generated, and share reference types where applicable.

The GetBindingForEndpoint(), GetEndpointAddress(), and enum EndpointConfiguration elements are abstracted in a way such that etiher

  • All services reference a shared implementation which contains information for all services
  • OR All services reference a local implementation which contains information only for that single service

Additional context
Project is targeting net6.0
Generated with dotnet-svcutil/2.1.0

Notes
Please do let me know if this is not in fact an intended use case for this tool, or I am approaching this the wrong way.

@jacobjmarks, thanks for raising the issue.

By debugging the tool, I found each endpoint to be generated code for comes from a WsdlEndpointConversionContext instance, this context is created from a binding instance which is returned from wsdlDocument.GetBinding(XmlQulifiedName name) and the name parameter here is the same for all the three wsdl documents, this results in the code always returned the same binding instance, in this specific case, that instance's ServiceDescription property happens to be the last one of the imported service description collection named ServiceCService. I think this leads to messy code being generated in the end as described in issue.

It behaves similarly for Svcutil.exe which is the .NET Framework version of the tool.

You can update the wsdl files so that the three wsdl binding names are different from each other and then rerun the tool command. For example, update service-a.wsdl as follows, and do similar modifications to service-b.wsdl and service-c.wsdl. Hope this workaround unblocks you.
image

Closing as no response, feel free to reopen if this is still an issue for you.