In .NET core and .NET 5+, WCF service is not supported anymore. If you still need to develop SOAP based web service using .NET 6, you have two options:
- CoreWCF: CoreWCF is a port of the service side of Windows Communication Foundation (WCF) to .NET Core.
- SoapCore: SoapCore is a library for creating SOAP services with .NET Core/.NET 5+.
In this article, I will show you how to use SoapCore to create a SOAP based web service and how to consume the SOAP based web service.
Solution Structure
There are 3 projects in the demo solution:
- Service Layer: This is a library project. It contains the interface and implementation of the service functionalities. In this project, we have a ProfileService defined.
- SoapService: This is a web project created using “ASP.NET Core Empty” project template. In this project, we need to reference “Service Layer” project and SoapCore nuget package:
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="SoapCore" Version="1.1.0.37" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\ServiceLayer\ServiceLayer.csproj" />
</ItemGroup>
</Project>
3. SoapClient: This is a console application. It contains the auto generated profile service client code. This project demonstrates how would we consume an existing SOAP based web service.
Service Layer Project
In this project, we define the IProfileService interface and its implementation:
Profile:
namespace ServiceLayer
{
[DataContract]
public class Profile
{
public Profile() { }
[DataMember]
public int ProfileID { get; set; }
[DataMember]
public string FirstName { get; set; }
[DataMember]
public string LastName { get; set; }
[DataMember]
public string Email { get; set; }
[DataMember]
public string HomeAddress { get; set; }
}
}
IProfileService Interface:
using System.ServiceModel;
namespace ServiceLayer
{
[ServiceContract]
public interface IProfileService
{
[OperationContract]
[FaultContract(typeof(MissingProfileFault))]
Profile GetProfile(int profileID);
}
}
You need to decorate the interface with [ServiceContract] attribute and each method with [OperationContract] attribute. You need to add following NuGet package reference in order to use these attributes:
<ItemGroup>
<PackageReference Include="System.ServiceModel.Primitives" Version="4.8.1" />
</ItemGroup>
The GetProfile() method of IProfileService interface has one fault attribute to define what kind of the fault it may raise.
using System.Runtime.Serialization;
namespace ServiceLayer
{
[DataContract]
public class MissingProfileFault
{
public MissingProfileFault(string message)
{
this.Message = message;
}
[DataMember]
public string Message { get; set; }
}
}
IProfileService implementation:
namespace ServiceLayer
{
public class ProfileService : IProfileService
{
private List<Profile> _profiles = new List<Profile>()
{
new Profile() { FirstName = "Foo", LastName = "Profile1", Email = "fooprofiile1@yahoo.com", HomeAddress = "1 King Street West, Toronto, ON", ProfileID = 1 },
new Profile() { FirstName = "Bar", LastName = "Profile2", Email = "barprofiile2@gmail.com", HomeAddress = "1 Hollywood Avenue, Los Angeles, CA", ProfileID = 2 },
};
public Profile GetProfile(int profileID)
{
var profile = _profiles.FirstOrDefault(p => p.ProfileID == profileID);
if (profile == null)
{
throw new FaultException<MissingProfileFault>(new MissingProfileFault($"A profile with ID {profileID} is missing."),
new FaultReason($"A profile with ID {profileID} is missing."),
new FaultCode("MissingProfile"), null);
}
return profile;
}
}
}
Soap Service Project
This project uses SoapCore to expose the ProfileService as SOAP based web service. The program.cs file looks like following:
using Microsoft.Extensions.DependencyInjection.Extensions;
using ServiceLayer;
using SoapCore;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddSoapCore();
builder.Services.TryAddSingleton<ProfileService>();
var app = builder.Build();
app.UseRouting();
app.UseEndpoints(endpoints => {
endpoints.UseSoapEndpoint<ProfileService>(
path: "/ProfileService.asmx",
encoder: new SoapEncoderOptions(),
serializer: SoapSerializer.DataContractSerializer,
caseInsensitivePath: true); });
app.Run();
Note in endpoints.UseSoapEndpoint<ProfileService>() call:
- The “path” parameter specify the URL path of the service. You can specify any validate URL path (e.g., /profileservice.svc).
- By default, the path is case sensitive. If you specify the path as “/ProfileService.asmx” and in URL you type “profileservice.asmx”, you would get 404 error You can make it case insensitive by specifying the “caseInsensitivePath” parameter as true.
After you build and run this project, a browser will open and an 404 error will be displayed. This is because there is no content under the root of the web site.
You can append the service path we specified in the program.cs file in the address. You should be able to see the WSDL content of this service.
Save the above content into profileservice.wsdl file in your local drive.
Soap Client
This is the client app that consumes the SOAP based web service. You can generate the client service code in two different ways:
Use Visual Studio
Right click the project and then choose “Add” -> “Service Reference”
In the new “Add new WCF Web Service service reference” dialog, provide the service URL and specify the namespace you want the generated code in. Click Next -> Next -> Finish.
The service reference client code would be generated under “Connected Services” folder.
Use dotnet-svcutil tool
Generate code using Visual Studio requires you to have the access to the live service. This may not be true in large company with multi-tier infrastructure. You may only just get the WSDL file from the service team. In that case, you need to use dotnet CLI tool to generate the code manually.
You can use the tool called dotnet-svcutil. It is similar to the Service Model Metadata — svcutil tool for .NET Framework projects. You can Install the dotnet-svcutil NuGet package as a CLI tool:
dotnet tool install --global dotnet-svcutil
After this CLI tool is successfully installed, you can use it to generate the client service reference code.
dotnet-svcutil C:\Downloads\ProfileService.wsdl -n "*,SoapServiceDemo.ProfileService"
The -n parameter specify the namespace of the generated code. The first part “*” means to map all targetNamespaces without an explicit mapping to the CLR namespace (the second part: “SoapServiceDemo.ProfileService”).
The code is generated in current directory\ServiceReference\Reference.cs.
Calling the SOAP Web Service
Once you have added the reference.cs file into SoapClient project, you can add following code into program.cs file.
using (var client = new SoapServiceDemo.ProfileService.ProfileServiceClient())
{
try
{
var profile = await client.GetProfileAsync(3);
if (profile != null)
{
Console.WriteLine($"First Name: {profile.FirstName}");
Console.WriteLine($"Last Name: {profile.LastName}");
Console.WriteLine($"Email Address: {profile.Email}");
Console.WriteLine($"Home Address: {profile.HomeAddress}");
}
}
catch (FaultException<MissingProfileFault> ex)
{
Console.WriteLine($"Fault reason: {ex.Reason.ToString()}, fault code: {ex.Code.Name}, detail: {ex.Detail.Message}");
}
catch (FaultException ex)
{
Console.WriteLine(ex.ToString());
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
}
Handling Fault Exception
The fault thrown by the GetProfile() method is MissingProfileFault (defined by FaultContract attribute in IProfileService interface). In the client, the exception caught would be FaultException<MissingProfileFault>. The Detail property of the FaultException is the instance of MissingProfileFault.
Happy coding!