-
Notifications
You must be signed in to change notification settings - Fork 391
Description
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>orT?) - 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 missingminOccurs="0" - The
IsRequiredFalseEnumproperty explicitly setsIsRequired=falsein the service but still lacksminOccurs="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.