Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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?

like image 530
bgh Avatar asked Dec 08 '14 12:12

bgh


2 Answers

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://docs.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.

like image 163
Keyto Avatar answered Dec 28 '22 08:12

Keyto


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.

like image 26
Ivan Stoev Avatar answered Dec 28 '22 09:12

Ivan Stoev