Recreating WCF Service in DotNet Core from WSDL
When Microsoft started porting .NET Framework to .NET Core (now simply .NET), some features were to remain on .NET Framework. One such feature was the server-side codebase of WCF preventing WCF from being hosted in .NET Core apps.
Although Microsoft has deprecated WCF, I have found many legacy applications rely on WCF for integration. In an ideal world, the integration would be migrated to more modern REST or GraphQL endpoints and the consuming application updated. However, this is often not possible. Fortunately, the .NET community have revived WCF server-side through CoreWCF.
What is CoreWCF?
CoreWCF “is a port of the service side of Windows Communication Foundation (WCF) to .NET Core”. This adds the capability of hosting a WCF endpoint in an ASPNET Core application along with its many benefits.
Creating the WCF Endpoint
The CoreWCF project provides a comprehensive walkthrough of creating a new WCF service and endpoint from scratch. There are also posts on migrating C# WCF definitions to CoreWCF such as this post.
However, some integration solutions don’t have C# contract definitions that can be migrated, such as BizTalk WCF receive locations. This post will focus on the scenario of using an existing WSDL to generate an equivalent WCF endpoint in CoreWCF.
Generating the Service Contract from a WSDL
To generate WCF proxy clients, Microsoft provide dotnet-svcutil which can generate the relevant C# objects from a WSDL file or endpoint. To install this tool, run the command below:
dotnet tool install dotnet-svcutil --global --version "2.2.0-preview1.23462.5"
Notice that a preview version of the tool has been specified. At the time of writing, the current stable version does not include an argument we need which is --serviceContract
. This argument skips the generation of the client as it’s not needed for us to host the WCF service. The next command will then generate the ServiceContract:
dotnet svcutil .\path\to\wsdl.wsdl --namespace "*,Namespace.To.Use" --outputDir .\path\to\output\cs\ --serviceContract --noLogo
The command will then output a Reference.cs in the path specified similar to below:
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace Namespace.To.Use
{
[System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Tools.ServiceModel.Svcutil", "2.2.0-preview1.23462.5")]
[System.ServiceModel.ServiceContractAttribute(Namespace="http://sample.xmlns.com", ConfigurationName="WcfService")]
public interface WcfService
{
[System.ServiceModel.OperationContractAttribute(Action="MyOperation", ReplyAction="*")]
[System.ServiceModel.XmlSerializerFormatAttribute(SupportFaults=true)]
System.Threading.Tasks.Task<myOperationResponse> myOperationAsync(myOperationRequest request);
...
}
...
}
Cleaning up the code
As mentioned, dotnet-svcutil is primarily used to create WCF proxy clients in .NET. However, with a few changes, the generated Reference.cs can be used to define the CoreWCF service.
Renaming the interface
Depending on what the WSDL contains, the generated interface may not follow the typical convention of IServiceName. Therefore I typically rename the interface to follow this convention.
Fix namespaces
As the WCF client-side code is still in .NET, the System.ServiceModel namespace needs to be replaced with the namespace CoreWCF. Typically, the generated code uses fully qualified class names and attributes, therefore, CoreWCF can be added as a using statement and System.ServiceModel removed using find and replace.
using CoreWCF;
namespace Namespace.To.Use
{
[System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Tools.ServiceModel.Svcutil", "2.2.0-preview1.23462.5")]
[ServiceContractAttribute(Namespace="http://sample.xmlns.com", ConfigurationName="WcfService")]
public interface WcfService
{
...
}
...
}
Fix attribute convention
Another tidy-up step is to remove the Attribute suffix to follow C# convention. Once again, this can be done using find and replace e.g. ServiceContractAttribute becomes ServiceContract.
Remove OperationContract ReplyAction
The generated operations may include the attribute property ReplyAction.
[OperationContract(Action="MyOperation", ReplyAction="*")]
[XmlSerializerFormat(SupportFaults=true)]
System.Threading.Tasks.Task<myOperationResponse> myOperationAsync(myOperationRequest request);
This can cause issues when consuming the WSDL of the CoreWCF service. After some research, a StackOverflow post suggested removing these properties which has so far resolved the issue.
Implementing the service
With the WCF contract generated and tidied up, the service can now be implemented in a service class.
public class WcfService : IWcfService
{
private readonly ILogger _logger;
public WcfService(ILogger<WcfService> logger)
{
_logger = logger;
}
public string GetData(int value)
{
throw new NotImplementedException();
}
public CompositeType GetDataUsingDataContract(CompositeType composite)
{
throw new NotImplementedException();
}
}
To register the generated ServiceContract and implemented service, the simplest way is to follow the walkthrough provided by CoreWCF and replace the sample ServiceContract and implementation.
One thing to note, in the above C# snippet, ILogger is injected as is typical of an ASPNET Core app. The sample project in the walkthrough does demonstrate dependency injection. To allow services to be injected, the implemented WCF service needs to be added to the ServiceCollection
var builder = WebApplication.CreateBuilder();
builder.Services.AddServiceModelServices();
builder.Services.AddServiceModelMetadata();
builder.Services.AddSingleton<IServiceBehavior, UseRequestHeadersForMetadataAddressBehavior>();
builder.Services.AddTransient<WcfService>();
var app = builder.Build();
app.UseServiceModel(serviceBuilder =>
{
serviceBuilder.AddService<WcfService>();
serviceBuilder.AddServiceEndpoint<WcfService, IWcfService>(new BasicHttpBinding(BasicHttpSecurityMode.Transport), "/Service.svc");
var serviceMetadataBehavior = app.Services.GetRequiredService<ServiceMetadataBehavior>();
serviceMetadataBehavior.HttpsGetEnabled = true;
});
app.Run();
In the above code, the WcfService implementation is added as a transient service using builder.Services.AddTransient<WcfService>();
so that its dependencies are injected when the service is constructed.
Sum up
In this post, we’ve looked at how to create an equivalent WCF service hosted in ASPNET Core using CoreWCF by generating the contract from a WSDL. By maintaining the same WCF contract, the integration can be swapped out with minimal changes to the consuming application.
Comments