Skip to content

Nullable enum properties missing minOccurs="0" in generated WSDL #1179

@suntereo

Description

@suntereo

Nullable enum properties missing minOccurs="0" in generated WSDL

Description

Nullable enum properties in DataContract classes are not generating minOccurs="0" in the WSDL, causing them to be incorrectly marked as required. This affects client proxy generation, where these properties are generated with IsRequired=true even when explicitly configured otherwise.

String properties work correctly (include minOccurs="0"), but enum properties do not, regardless of:

  • Using nullable syntax (Nullable<T> or T?)
  • Setting [DataMember(IsRequired = false)]
  • Setting [DataMember(EmitDefaultValue = false)]
  • Combining both attributes

Environment

  • SoapCore Version: 1.2.1.11
  • .NET Version: 8.0
  • OS: Windows (reproduced on multiple systems)

Expected Behavior

Nullable enum properties should render in the WSDL with minOccurs="0", similar to how string properties behave:

<xs:element minOccurs="0" name="NullableEnumProperty" nillable="true" type="tns:WorkflowState"/>

Actual Behavior

Nullable enum properties only get nillable="true" but are missing minOccurs="0":

<xs:element nillable="true" type="tns:WorkflowState" name="NullableEnumProperty"/>

This causes the element to be required but nullable, rather than optional.

Real-World Impact

When clients generate proxies using svcutil or Visual Studio's "Add Service Reference":

  • All nullable enum properties are generated with [DataMember(IsRequired=true)]
  • Clients must include these fields in every request, even when they should be optional
  • This breaks scenarios where enum values should truly be optional

Reproduction Code

TestService.cs:

using System.Runtime.Serialization;
using System.ServiceModel;

namespace SoapCoreEnumIssue
{
    [DataContract]
    public enum WorkflowState
    {
        [EnumMember] Pending = 0,
        [EnumMember] InProgress = 1,
        [EnumMember] Completed = 2,
        [EnumMember] Failed = 3,
        [EnumMember] Cancelled = 4
    }

    [DataContract]
    public class OperationResult
    {
        // String properties - these work correctly with minOccurs="0"
        [DataMember]
        public string Description { get; set; }

        [DataMember]
        public string Notes { get; set; }

        // Enum property attempts - NONE of these produce minOccurs="0"
        
        [DataMember]
        public WorkflowState BasicEnumProperty { get; set; }

        [DataMember]
        public WorkflowState? NullableEnumProperty { get; set; }

        [DataMember]
        public Nullable<WorkflowState> ExplicitNullableEnum { get; set; }

        [DataMember(IsRequired = false)]
        public WorkflowState? IsRequiredFalseEnum { get; set; }

        [DataMember(IsRequired = false, EmitDefaultValue = false)]
        public WorkflowState? BothAttributesEnum { get; set; }

        [DataMember]
        public string AdditionalInfo { get; set; }
    }

    [ServiceContract]
    public interface ITestService
    {
        [OperationContract]
        OperationResult GetOperationResult();
    }

    public class TestService : ITestService
    {
        public OperationResult GetOperationResult()
        {
            return new OperationResult
            {
                Description = "Test operation",
                BasicEnumProperty = WorkflowState.Pending
            };
        }
    }
}

Program.cs:

using SoapCore;
using SoapCoreEnumIssue;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddSingleton<ITestService, TestService>();
builder.Services.AddSoapCore();

var app = builder.Build();

app.UseSoapEndpoint<ITestService>("/Service.svc", new SoapEncoderOptions());

app.Run();

SoapCoreEnumIssue.csproj:

<Project Sdk="Microsoft.NET.Sdk.Web">
  <PropertyGroup>
    <TargetFramework>net8.0</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="SoapCore" Version="1.2.1.11" />
  </ItemGroup>
</Project>

Generated WSDL (Problem Section)

Access the WSDL at http://localhost:5000/Service.svc?wsdl and observe the OperationResult complex type:

<xs:complexType name="OperationResult">
  <xs:sequence>
    <xs:element minOccurs="0" name="AdditionalInfo" nillable="true" type="xs:string"/>
    <xs:element type="tns:WorkflowState" name="BasicEnumProperty"/>
    <!-- ❌ Missing minOccurs="0" even though nillable="true" is present -->
    <xs:element nillable="true" type="tns:WorkflowState" name="BothAttributesEnum"/>
    <xs:element minOccurs="0" name="Description" nillable="true" type="xs:string"/>
    <!-- ❌ Missing minOccurs="0" even though nillable="true" is present -->
    <xs:element nillable="true" type="tns:WorkflowState" name="ExplicitNullableEnum"/>
    <!-- ❌ Missing minOccurs="0" despite IsRequired=false -->
    <xs:element nillable="true" type="tns:WorkflowState" name="IsRequiredFalseEnum"/>
    <xs:element minOccurs="0" name="Notes" nillable="true" type="xs:string"/>
    <!-- ❌ Missing minOccurs="0" even though nillable="true" is present -->
    <xs:element nillable="true" type="tns:WorkflowState" name="NullableEnumProperty"/>
  </xs:sequence>
</xs:complexType>

Notice that:

  • String properties correctly have minOccurs="0"
  • All nullable enum properties have nillable="true" but are missing minOccurs="0"
  • The IsRequiredFalseEnum property explicitly sets IsRequired=false in the service but still lacks minOccurs="0" in the WSDL

Additional Notes

When generating client proxies with this WSDL using svcutil, all the nullable enum properties incorrectly generate as:

[System.Runtime.Serialization.DataMemberAttribute(IsRequired=true)]
public System.Nullable<TestService.WorkflowState> NullableEnumProperty { ... }

This forces clients to always include these fields in requests, defeating the purpose of making them optional.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions