1

I have a WCF service with a Flattened WSDL, and the consumer at the other end is telling me the nillable="true" attributes are hosing them up. I tried setting EmitDefaultValue = false in my service contract, but I didn't notice any change in behavior.

Admittedly, I have never had to dig into WSDL generation at this level before so I'm a little lost. Perhaps there a tweak to be made in the bit of code posted below that may solve my problem? If I'm at least in the correct place, I'll keep investigating.

Is there a simple way to remove the nillable="true" attributes from my WSDL, and is that going to have unintended consequences? Thanks!

public class FlatWsdl : IWsdlExportExtension, IEndpointBehavior
{
    public void ExportEndpoint(WsdlExporter exporter, WsdlEndpointConversionContext context)
    {
        XmlSchemaSet schemaSet = exporter.GeneratedXmlSchemas;

        foreach (ServiceDescription wsdl in exporter.GeneratedWsdlDocuments)
        {
            List<XmlSchema> importsList = new List<XmlSchema>();

            foreach (XmlSchema schema in wsdl.Types.Schemas)
            {
                AddImportedSchemas(schema, schemaSet, importsList);
            }

            wsdl.Types.Schemas.Clear();

            foreach (XmlSchema schema in importsList)
            {
                RemoveXsdImports(schema);
                wsdl.Types.Schemas.Add(schema);
            }
        }
    }

    ...omitted the rest of FlatWsdl.cs for brevity...
}
Community
  • 1
  • 1
Dave Ziegler
  • 1,737
  • 3
  • 19
  • 36
  • You have to write your own WsdlExportExtension. See this answer for a sample: http://stackoverflow.com/questions/10602749/setting-nillable-false-with-wcf/23366064#23366064 – Vizu Apr 29 '14 at 13:33

2 Answers2

1

After searching the internet for ours, me and my team decided to go with a different approach to get rid of the nillable="true". What we did was the following:

  1. Registered a special HttpModule during application startup.
  2. This module registered a handler to the BeginRequest event.
  3. In the case the WSDL or XSD are requested (checked by looking at the query string) the hooked handler would replace the default Response.Filter with a decorator (a Stream).
  4. This decorator would buffer the written data until the end of the document was reached.
  5. When all data is written the decorator builds up an XDocument based on that data and goes through that document to set (among other things, such as adding documentation) nillable="false".

This works all quite lovely.

The idea of using Response.Filter was found here.

Here's the HttpModule:

public class WsdlInterceptionHttpModule : IHttpModule
{
    public void Init(HttpApplication application)
    {
        application.BeginRequest += (sender, e) =>
        {
            var context = application.Context;

            if (IsRequestForWsdl(context.Request))
            {
                context.Response.Filter = 
                    new WsdlAnnotationsFilterDecorator(context.Response.Filter);
            }
        };
    }

    private static bool IsRequestForWsdl(HttpRequest request) { ... }
}

Here's the WsdlAnnotationsFilterDecorator:

public class WsdlAnnotationsFilterDecorator : Stream
{
    private const string DefinitionsEndOfFileMarker = "</wsdl:definitions>";
    private const string SchemaEndOfFileMarker = "</xs:schema>";

    private readonly Stream inputStream;
    private readonly StringBuilder responseXml = new StringBuilder();
    private bool firstWrite = true;
    private string endOfFileMarker = DefinitionsEndOfFileMarker;

    public WsdlAnnotationsFilterDecorator(Stream inputStream)
    {
        this.inputStream = inputStream;
        this.responseXml = new StringBuilder();
    }

    public override bool CanRead { get { return true; } }
    public override bool CanSeek { get { return true; } }
    public override bool CanWrite { get { return true; } }
    public override long Length { get { return 0; } }
    public override long Position { get; set; }

    public override void Close()
    {
        inputStream.Close();
    }

    public override void Flush()
    {
        inputStream.Flush();
    }

    public override long Seek(long offset, SeekOrigin origin)
    {
        return inputStream.Seek(offset, origin);
    }

    public override void SetLength(long length)
    {
        inputStream.SetLength(length);
    }

    public override int Read(byte[] buffer, int offset, int count)
    {
        return inputStream.Read(buffer, offset, count);
    }

    public override void Write(byte[] buffer, int offset, int count)
    {
        string valueToWrite = UTF8Encoding.UTF8.GetString(buffer, offset, count);

        SetEndOfFileMarker(valueToWrite);

        if (!valueToWrite.EndsWith(this.endOfFileMarker))
        {
            responseXml.Append(valueToWrite);
        }
        else
        {
            responseXml.Append(valueToWrite);

            string finalXml = responseXml.ToString();

            finalXml = WsdlAnnotator.Annotate(finalXml);

            byte[] data = UTF8Encoding.UTF8.GetBytes(finalXml);

            inputStream.Write(data, 0, data.Length);
        }
    }

    private void SetEndOfFileMarker(string valueToWrite)
    {
        if (firstWrite)
        {
            int definitionTagIndex = valueToWrite.IndexOf("<wsdl:definitions");
            int schemaTagIndex = valueToWrite.IndexOf("<xs:schema");

            if (definitionTagIndex > -1 || schemaTagIndex > -1)
            {
                firstWrite = false;

                if (definitionTagIndex > -1 && schemaTagIndex > -1)
                {
                    endOfFileMarker =
                        definitionTagIndex < schemaTagIndex 
                            ? DefinitionsEndOfFileMarker : SchemaEndOfFileMarker;
                }
                else if (definitionTagIndex > -1)
                {
                    endOfFileMarker = DefinitionsEndOfFileMarker;
                }
                else if (schemaTagIndex > -1)
                {
                    endOfFileMarker = SchemaEndOfFileMarker;
                }
            }
        }
    }
}

The WsdlAnnotator is where all the magic happens:

internal static class WsdlAnnotator
{
    internal static string Annotate(string xml)
    {
        XDocument document = XDocument.Parse(xml);

        try
        {
            // Your magic here.
        }
        catch (Exception ex)
        {
            throw new InvalidOperationException(
                ex.Message + " Document: " + document.ToString(), ex);
        }

        return document.ToString(SaveOptions.None);
    }
Steven
  • 166,672
  • 24
  • 332
  • 435
1

There's no direct simple way to achieve this. You'll have to use WsdlExporter to implement it yourself. Whether it will have unintended consequences depends on your intentions :-)

EDIT:

Have a look at the MSDN example of IWSDLExportExtension. It will allow you to do exactly what you want. Admittedly, it's a bit of a hassle to get it right, but you're looking in the right direction.

diggingforfire
  • 3,359
  • 1
  • 23
  • 33
  • I chose to take no action in the end, but if I were to attempt to remove the nillable attributes I would use WsdlExporter in the ExportEndpoint method. – Dave Ziegler Jan 30 '12 at 22:15
  • Is there some change to this behavior in .Net 4.5? I'm about to face the same situation now. Don't have IWSDLExportExtension in project. – DoomerDGR8 Sep 06 '12 at 12:04