How to Eliminate Nested XML Issues in ASP.NET Core 6.0 SOAP Services

Introduction

While creating a SOAP API in ASP.NET Core 6.0 with SoapCore, I ran into a problem: my XML response had an extra layer that shouldn’t be there. The system using the API couldn’t work with this additional nesting.

Looking for solutions in various StackOverflow discussions and even a past GitHub issue, nothing seemed to work.

So, the decision was made to try something else - CoreWCF.

To illustrate the impact of our changes, let’s compare the SOAP messages before and after the transition.

Before (SoapCore)

With SoapCore, our configuration was:

// Program.cs

using SoapCore;

WebApplicationBuilder builder = WebApplication.CreateBuilder(args);

builder.Services.AddSoapCore();
builder.Services.AddScoped<MyService>();

WebApplication app = builder.Build();

app.UseRouting();

app.UseEndpoints(endpoints =>
{
    endpoints.UseSoapEndpoint<MyService>(
        "/Service.asmx", 
        new SoapEncoderOptions(),
        SoapSerializer.DataContractSerializer);
});

app.Run();
// MyService.cs

using System.ServiceModel;

[ServiceContract]
public interface IMyService
{
    [OperationContract]
    MyResponse MyRequest(MyRequest myRequest);
}

public class MyService : IMyService
{
    public MyResponse MyRequest(MyRequest myRequest)
    {
        return new()
        {
            OutputMessage = "Hello world"
        };
    }
}
// MyRequest.cs

using System.Runtime.Serialization;

[DataContract]
public class MyRequest
{
    [DataMember]
    public string InputMessage { get; set; }
}
// MyResponse.cs

using System.Runtime.Serialization;

[DataContract]
public class MyResponse
{
    [DataMember]
    public string OutputMessage { get; set; }
}

And our SOAP response had an extra element, MyRequestResult, which was causing issues.

// SOAP response

<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <s:Body>
    <MyRequestResponse xmlns="http://tempuri.org/">
      <MyRequestResult xmlns:d4p1="http://schemas.datacontract.org/2004/07/ExampleApi" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
        <d4p1:OutputMessage>Hello world</d4p1:Message>
      </MyRequestResult>
    </MyRequestResponse>
  </s:Body>
</s:Envelope>

After (CoreWCF)

We made a few changes:

// Program.cs

using CoreWCF;
using CoreWCF.Channels;
using CoreWCF.Configuration;
using CoreWCF.Description;

WebApplicationBuilder builder = WebApplication.CreateBuilder(args);

builder.Services.AddScoped<MyService>();
builder.Services.AddServiceModelServices();
builder.Services.AddServiceModelMetadata();

WebApplication app = builder.Build();

app.UseServiceModel(configure =>
{
    configure.AddService<MyService>()
        .AddServiceEndpoint<MyService, IMyService>(
            new BasicHttpBinding(BasicHttpSecurityMode.Transport), 
            "/Service.asmx");

    ServiceMetadataBehavior serviceMetadataBehavior = app.Services.GetRequiredService<ServiceMetadataBehavior>();
    serviceMetadataBehavior.HttpsGetEnabled = true;
});

app.Run();
// MyService.cs

using CoreWCF;

[ServiceContract]
public interface IMyService
{
    [OperationContract]
    MyResponse MyRequest(MyRequest myRequest);
}

public class MyService : IMyService
{
    public MyResponse MyRequest(MyRequest myRequest)
    {
        return new()
        {
            OutputMessage = "Hello world"
        };
    }
}

We changed our data contracts to message contracts:

// MyRequest.cs

using CoreWCF;

[MessageContract]
public class MyRequest
{
    [MessageBodyMember]
    public string InputMessage { get; set; }
}
// MyResponse.cs

using CoreWCF;

[MessageContract]
public class MyResponse
{
    [MessageBodyMember]
    public string OutputMessage { get; set; }
}

And now the XML output is as it should be, without the extra layer.

<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
   <s:Body>
      <MyResponse xmlns="http://tempuri.org/">
         <OutputMessage>Hello world</OutputMessage>
      </MyResponse>
   </s:Body>
</s:Envelope>

Conclusion

Switching to CoreWCF fixed our nested XML problem in our SOAP service in ASP.NET Core 6.0. If you’re facing similar issues, giving CoreWCF a try might do the trick.