Calling a SOAP service in .net Core

So I had to do this and used the WCF Web Service Reference Provider Tool.

The apparent need, according to responses like those here, for all the roundabout business with Bindings and Factories and Proxies seemed strange, considering that this all appeared to be part of the imported class.

Not being able to find a straightforward official "HowTo", I will post my findings as to the simplest setup I was able to cobble together to fit my requirements with Digest authentication:

    ServiceName_PortClient client = new ServiceName_PortClient();
    //GetBindingForEndpoint returns a BasicHttpBinding
    var httpBinding = client.Endpoint.Binding as BasicHttpBinding;
    httpBinding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Digest;
    client.ClientCredentials.HttpDigest.ClientCredential = new NetworkCredential("Username", "Password", "Digest");
    var result = await client.GetResultAsync();

Now, if you don't need to do any authentication simply doing:

    ServiceName_PortClient client = new ServiceName_PortClient();
    var result = await client.GetResultAsync();

Should be sufficient.

The ServiceName_PortClient class was generated as such by the import tool, where ServiceName was the name of the service I was importing.

Of course it seems to be more in the spirit of the imported code to place the configuration in a partial ServiceName_PortClient class along the lines of:

    public partial class ServiceName_PortClient
    {
        static partial void ConfigureEndpoint(System.ServiceModel.Description.ServiceEndpoint serviceEndpoint, System.ServiceModel.Description.ClientCredentials clientCredentials)
        {
            var httpBinding = serviceEndpoint.Binding as BasicHttpBinding;
            httpBinding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Digest;
            clientCredentials.HttpDigest.ClientCredential = new NetworkCredential("Username", "Password", "Realm");
        }
    }

To consume a SOAP service from .NET core, adding connected service from the project UI does not work.

Option 1: Use dotnet-svcutil CLI. Prerequisite: VS 2017, Version 15.5 or above

  1. Launch Developer Command Prompt VS 2017.
  2. Go to app.csproj file and add below references:

    <ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.9" />
    <PackageReference Include="System.ServiceModel.Http" Version="4.5.3" />
    </ItemGroup>
    <ItemGroup>
    <DotNetCliToolReference Include="dotnet-svcutil" Version="1.0.*" />
    </ItemGroup>
    
  3. Rebuild solution.

  4. Change directory to your project location from VS command prompt.
  5. run command: svcutil SOAP_URL?wsdl ; example: example.com/test/testing?wsdl This will generate reference files and output.config file in your project.
  6. .Net Core does not have any app.config or web.config files, but the output.config file will serve the SOAP binding.

Option 2 In case you need to refer more than one SOAP sevice,

  1. Create a new class library project, use .Net framework 4.5.1 .Net framework matters as i saw the reference files generated from contract is not correct if .Net Framework is latest.
  2. Add service reference by right click on References.
  3. Refer the class library project from your .Net core project.

Ok this answer is for those who are trying to connect to a WCF service from a .net Core project.

Here is the solution to my problem, using the new .net Core WCF syntax/library.

BasicHttpBinding basicHttpBinding = null;
EndpointAddress endpointAddress = null;
ChannelFactory<IAService> factory = null;
IAService serviceProxy = null;

try
{
    basicHttpBinding = new BasicHttpBinding(BasicHttpSecurityMode.Transport);
    basicHttpBinding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Basic;
    endpointAddress = new EndpointAddress(new Uri("https://someurl.com/ws/TheEndpoint.pub.ws:AService"));
    factory = new ChannelFactory<IAService>(basicHttpBinding, endpointAddress);

    factory.Credentials.UserName.UserName = "usrn";
    factory.Credentials.UserName.Password = "passw";
    serviceProxy = factory.CreateChannel();

    using (var scope = new OperationContextScope((IContextChannel)serviceProxy))
    {
        var result = await serviceProxy.getSomethingAsync("id").ConfigureAwait(false);
    }

    factory.Close();
    ((ICommunicationObject)serviceProxy).Close();
}
catch (MessageSecurityException ex)
{
     throw;
}
catch (Exception ex)
{
    throw;
}
finally
{
    // *** ENSURE CLEANUP (this code is at the WCF GitHub page *** \\
    CloseCommunicationObjects((ICommunicationObject)serviceProxy, factory);
}

UPDATE

I got the following exception using the code above

This OperationContextScope is being disposed out of order.

Which seems to be something that is broken (or needs addressing) by the WCF team.

So I had to do the following to make it work (based on this GitHub issue)

basicHttpBinding = new BasicHttpBinding(BasicHttpSecurityMode.Transport);
basicHttpBinding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Basic;

factory = new ChannelFactory<IAService_PortType>(basicHttpBinding, new EndpointAddress(new Uri("https://someurl.com/ws/TheEndpoint.pub.ws:AService")));
factory.Credentials.UserName.UserName = "usern";
factory.Credentials.UserName.Password = "passw";
serviceProxy = factory.CreateChannel();
((ICommunicationObject)serviceProxy).Open();
var opContext = new OperationContext((IClientChannel)serviceProxy);
var prevOpContext = OperationContext.Current; // Optional if there's no way this might already be set
OperationContext.Current = opContext;

try
{
    var result = await serviceProxy.getSomethingAsync("id").ConfigureAwait(false);

    // cleanup
    factory.Close();
    ((ICommunicationObject)serviceProxy).Close();
}
finally
{
  // *** ENSURE CLEANUP *** \\
  CloseCommunicationObjects((ICommunicationObject)serviceProxy, factory);
  OperationContext.Current = prevOpContext; // Or set to null if you didn't capture the previous context
}

But your requirements will probably be different. So here are the resources you might need to help you connecting to your WCF service are here:

  • WCF .net core at GitHub
  • BasicHttpBinding Tests
  • ClientCredentialType Tests

The tests helped me a lot but they where somewhat hard to find (I had help, thank you Zhenlan for answering my wcf github issue)


For those who are trying to do the same with NTLM and .Net Core and wondering what some of the variables are defined as, I clarified the code to look like:

IAService_PortType is the service reference you created if you followed the guide on https://joshuachini.com/2017/07/13/calling-a-soap-service-from-asp-net-core-or-net-core/

BasicHttpBinding basicHttpBinding = 
    new BasicHttpBinding(BasicHttpSecurityMode.TransportCredentialOnly);
// Setting it to Transport will give an exception if the url is not https
basicHttpBinding.Security.Transport.ClientCredentialType = 
    HttpClientCredentialType.Ntlm;

ChannelFactory<IAService_PortType> factory = 
    new ChannelFactory<IAService_PortType>(basicHttpBinding, 
    new EndpointAddress(
        new Uri("https://someurl.com/ws/TheEndpoint.pub.ws:AService")));
factory.Credentials.Windows.ClientCredential.Domain = domain;
factory.Credentials.Windows.ClientCredential.UserName = user;
factory.Credentials.Windows.ClientCredential.Password = pass;
IAService_PortType serviceProxy = factory.CreateChannel();
((ICommunicationObject)serviceProxy).Open();

try
{
    var result = serviceProxy.getSomethingAsync("id").Result;

}
finally
{
    // cleanup
    factory.Close();
    ((ICommunicationObject)serviceProxy).Close();
}