Prevent ServiceContractGenerator from generating message contracts (request/response wrappers)
Asked Answered
H

4

12

There is a specific WSDL for which the ServiceContractGenerator keeps on generating message contracts (request/response wrapper objects), which I do not want (I want straight parameters). Other WSDL's work fine.

When I use Visual Studio to create a WCF client ("Add Service Reference") and I click on "Advanced...", the checkbox which says "Always generate message contracts" does properly control whether the message contract objects are generated.

However, when I use the ServiceContractGenerator class to generate a WCF client programmatically, it keeps generating message contracts. I tried setting the ServiceContractGenerator's Options to ServiceContractGenerationOptions.None, but the result is the same.

Here is the code that I use:

MetadataSet metadataSet = new MetadataSet();
metadataSet.MetadataSections.Add(MetadataSection.CreateFromServiceDescription(System.Web.Services.Description.ServiceDescription.Read(wsdlStream)));
WsdlImporter importer = new WsdlImporter(metadataSet);
if (serviceDescription != null)
    importer.WsdlDocuments.Add(serviceDescription);
foreach (XmlSchema nextSchema in schemas)
    importer.XmlSchemas.Add(nextSchema);

ServiceContractGenerator generator = new ServiceContractGenerator();
generator.Options = ServiceContractGenerationOptions.None;
foreach (ContractDescription nextContract in importer.ImportAllContracts())
    generator.GenerateServiceContractType(nextContract);
if (generator.Errors.Count != 0)
    throw new Exception("Service assembly compile error: \r\n - " + string.Join("\r\n - ", generator.Errors.Select(e => e.Message)));

// Use generator.TargetCompileUnit to generate the code...

What should I do so that ServiceContractGenerator generates the web methods with straight parameters?

Hist answered 8/12, 2014 at 12:35 Comment(1)
It seems there is no way to control this using WsdlImporter. It is unclear what rules it uses to decide whether to generate message contracts or not, but it seems to be dependent on the nature of the parameters of the web methods (nullable / not-nullable etc.) We have resorted to detecting when message contracts were generated and unwrapping the parameters behind our API during runtime, which works fine.Hist
G
4

When I use Visual Studio to create a WCF client ("Add Service Reference") and I click on "Advanced...", the checkbox which says "Always generate message contracts" does properly control whether the message contract objects are generated.

That's not correct. Try with the problematic WSDL from the link and you'll get the same results as using ServiceContractGenerator. In fact, ServiceContractGenerationOptions.TypedMessages flag (by default off) directly corresponds to the forementioned dialog option and is used (when turned on) to force creation of message contracts.

With that being said, the problem is in the WSDL and is indicated in the generated .cs file with lines like this:

// CODEGEN: Generating message contract since element name login from namespace http://localhost/FinSwitch/ is not marked nillable

So that's the issue. Both svcutil.exe, "Add Service Reference" dialog and ServiceContractGenerator will not unwrap the methods when the method element or response element contains "object type" (string, base64Binary etc.) members not marked with nillable="true".

For instance, here is a part from the problematic WSDL:

<s:element name="DownloadFile">
    <s:complexType>
        <s:sequence>
            <s:element type="s:string" name="login" maxOccurs="1" minOccurs="0"/>
            <s:element type="s:string" name="password" maxOccurs="1" minOccurs="0"/>
            <s:element type="s:string" name="fileType" maxOccurs="1" minOccurs="0"/>
            <s:element type="s:dateTime" name="fileDate" maxOccurs="1" minOccurs="1"/>
            <s:element type="s:boolean" name="onlyDownloadIfFileChanged" maxOccurs="1" minOccurs="1"/>
            <s:element type="s:string" name="companyCode"  maxOccurs="1" minOccurs="0"/>
            <s:element type="s:string" name="category" maxOccurs="1" minOccurs="0"/>
        </s:sequence>
    </s:complexType>
</s:element>

<s:element name="DownloadFileResponse">
    <s:complexType>
        <s:sequence>
            <s:element type="s:base64Binary" name="DownloadFileResult" maxOccurs="1" minOccurs="0"/>
        </s:sequence>
    </s:complexType>
</s:element>

which generates

// CODEGEN: Generating message contract since element name login from namespace http://localhost/FinSwitch/ is not marked nillable
[System.ServiceModel.OperationContractAttribute(Action="http://localhost/FinSwitch/FinSwitchWebServiceSoap/DownloadFileRequest", ReplyAction="http://localhost/FinSwitch/FinSwitchWebServiceSoap/DownloadFileResponse")]
DownloadFileResponse DownloadFile(DownloadFileRequest request);

plus message contact classes.

However, if we modify it to:

<s:element name="DownloadFile">
    <s:complexType>
        <s:sequence>
            <s:element type="s:string" name="login" nillable="true" maxOccurs="1" minOccurs="0"/>
            <s:element type="s:string" name="password" nillable="true" maxOccurs="1" minOccurs="0"/>
            <s:element type="s:string" name="fileType" nillable="true" maxOccurs="1" minOccurs="0"/>
            <s:element type="s:dateTime" name="fileDate" maxOccurs="1" minOccurs="1"/>
            <s:element type="s:boolean" name="onlyDownloadIfFileChanged" maxOccurs="1" minOccurs="1"/>
            <s:element type="s:string" name="companyCode"  nillable="true" maxOccurs="1" minOccurs="0"/>
            <s:element type="s:string" name="category" nillable="true" maxOccurs="1" minOccurs="0"/>
        </s:sequence>
    </s:complexType>
</s:element>

<s:element name="DownloadFileResponse">
    <s:complexType>
        <s:sequence>
            <s:element type="s:base64Binary" name="DownloadFileResult" nillable="true" maxOccurs="1" minOccurs="0"/>
        </s:sequence>
    </s:complexType>
</s:element>

then the generated code is as expected

[System.ServiceModel.OperationContractAttribute(Action="http://localhost/FinSwitch/FinSwitchWebServiceSoap/DownloadFileRequest", ReplyAction="http://localhost/FinSwitch/FinSwitchWebServiceSoap/DownloadFileResponse")]
byte[] DownloadFile(string login, string password, string fileType, System.DateTime fileDate, bool onlyDownloadIfFileChanged, string companyCode, string category);

and no message contract classes.

What does all that mean? The rule is hardcoded deeply in the infrastructure (if someone is interested, here is the reference source) and cannot be changed. One can preprocess the WSDL content (afterall, it's a XML) and insert nillable="true" where needed, but I'm not sure that can be considered to be a correct action - AFAIK, it's the service provider responsibility to provide the correct WSDL and there is no guarantee that altering it will not cause side effects.

Germanophile answered 11/8, 2016 at 19:42 Comment(0)
R
1

I know it's an old question but it might help someone like me out who stumbled opon this question via google.

I had the exact same problem. I configured everything with DataContracts and used the correct settings when generating the client but it always generated message contracts.

The problem was that one of my methods returned a DataSet.

DataSet is not supported by the DataContractSerializer so Visual Studio / svcutil.exe uses the XmlSerializer. More about that can be found here: https://learn.microsoft.com/en-us/dotnet/framework/wcf/feature-details/data-contract-schema-reference?redirectedfrom=MSDN

The solution was to remove the method that returned the DataSet.

Reconstitute answered 2/10, 2019 at 9:57 Comment(0)
S
0

I too was struggling with something similar and like @Keyto realised I was returning a DataSet in one of my methods of my WCF service. Removing this method removed the auto-generated Request/Response objects from my Reference.cs that is produced when adding a Connected Service reference in my case from a .Net5 Web API to a WCF service.

Submerged answered 7/12, 2021 at 12:5 Comment(0)
C
0

The accepted answer above helped (https://mcmap.net/q/949012/-prevent-servicecontractgenerator-from-generating-message-contracts-request-response-wrappers).

The hint was nillable="true" - but I am working with a WSDL that did not have nillable attributes, but still avoids request/response wrappers. Instead, it has default values (default=""), which turns out to have the same effect.

Depending on your parameter / return value class semantics, then, you can use nullable property types, or use the [System.ComponentModel.DefaultValue()] attribute with an appropriate value.

In my case, with another service, the request parameter type is simply string, and that did not trigger a generated request wrapper. Only the response type needed default or nullable values, without which wrappers were generated for both request and response.

VS 2022, net8.0, net48

Consubstantial answered 25/7 at 17:42 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.