Skip to content

CoreWCF in ConnectSoft Microservice Template

Purpose & Overview

CoreWCF is an open-source port of Windows Communication Foundation (WCF) that runs on .NET (Core, 5+, 6+, 7+, 8+, 9+). In the ConnectSoft Microservice Template, CoreWCF enables SOAP-based service communication, providing interoperability with legacy systems and enterprise services that require SOAP/WS-* protocols while integrating with the template's Clean Architecture and CQRS patterns.

Why CoreWCF?

CoreWCF provides several benefits for microservices:

  • SOAP/WSDL Support: Standardized XML-based protocol for enterprise integration
  • Interoperability: Works with Java, .NET Framework, and other SOAP-compatible systems
  • Contract-First Development: Define service contracts using WSDL
  • Multiple Bindings: Support for BasicHttpBinding, WSHttpBinding, NetTcpBinding, and more
  • Security: Built-in support for WS-Security, WS-Trust, and other WS-* specifications
  • Reliability: Support for WS-ReliableMessaging and transactions
  • Enterprise Integration: Ideal for integrating with legacy systems and enterprise services

Architecture Overview

CoreWCF integrates with the template's architecture:

SOAP Client
CoreWCF Service (ServiceModel.CoreWCF)
    ├── Service Contract (Interface)
    ├── Service Implementation
    └── Data Contracts (DTOs)
Domain Model (DomainModel)
    ├── Processors (Commands/Writes)
    └── Retrievers (Queries/Reads)
Repository Layer (PersistenceModel)
    └── Data Store

Core Components

1. Service Contract

Service contracts define the service interface:

// IMicroserviceAggregateRootsService.cs
[ServiceContract(Namespace = "http://connectsoft.com/microservice/v1")]
public interface IMicroserviceAggregateRootsService
{
    /// <summary>
    /// Creates a new MicroserviceAggregateRoot.
    /// </summary>
    [OperationContract]
    [FaultContract(typeof(ServiceFault))]
    Task<CreateMicroserviceAggregateRootResponse> CreateMicroserviceAggregateRootAsync(
        CreateMicroserviceAggregateRootRequest request);

    /// <summary>
    /// Retrieves a MicroserviceAggregateRoot by ID.
    /// </summary>
    [OperationContract]
    [FaultContract(typeof(ServiceFault))]
    Task<GetMicroserviceAggregateRootDetailsResponse> GetMicroserviceAggregateRootDetailsAsync(
        GetMicroserviceAggregateRootDetailsRequest request);

    /// <summary>
    /// Deletes a MicroserviceAggregateRoot.
    /// </summary>
    [OperationContract]
    [FaultContract(typeof(ServiceFault))]
    Task<DeleteMicroserviceAggregateRootResponse> DeleteMicroserviceAggregateRootAsync(
        DeleteMicroserviceAggregateRootRequest request);
}

2. Data Contracts

Data contracts define the message structure:

// Request/Response DTOs
[DataContract(Namespace = "http://connectsoft.com/microservice/v1")]
public class CreateMicroserviceAggregateRootRequest
{
    [DataMember(IsRequired = true, Order = 1)]
    public Guid ObjectId { get; set; }

    [DataMember(IsRequired = false, Order = 2)]
    public string? Name { get; set; }
}

[DataContract(Namespace = "http://connectsoft.com/microservice/v1")]
public class CreateMicroserviceAggregateRootResponse
{
    [DataMember(IsRequired = true, Order = 1)]
    public MicroserviceAggregateRootDto CreatedMicroserviceAggregateRoot { get; set; } = null!;
}

[DataContract(Namespace = "http://connectsoft.com/microservice/v1")]
public class MicroserviceAggregateRootDto
{
    [DataMember(IsRequired = true, Order = 1)]
    public Guid ObjectId { get; set; }

    [DataMember(IsRequired = false, Order = 2)]
    public string? Name { get; set; }

    [DataMember(IsRequired = false, Order = 3)]
    public string? Status { get; set; }

    [DataMember(IsRequired = false, Order = 4)]
    public DateTimeOffset CreatedOn { get; set; }
}

// Fault contract for error handling
[DataContract(Namespace = "http://connectsoft.com/microservice/v1")]
public class ServiceFault
{
    [DataMember(IsRequired = true, Order = 1)]
    public string Message { get; set; } = null!;

    [DataMember(IsRequired = false, Order = 2)]
    public string? ErrorCode { get; set; }

    [DataMember(IsRequired = false, Order = 3)]
    public string? StackTrace { get; set; }
}

3. Service Implementation

Service implementation integrates with domain services:

// MicroserviceAggregateRootsService.cs
public class MicroserviceAggregateRootsService : IMicroserviceAggregateRootsService
{
    private readonly ILogger<MicroserviceAggregateRootsService> logger;
    private readonly IMicroserviceAggregateRootsProcessor processor;
    private readonly IMicroserviceAggregateRootsRetriever retriever;
    private readonly IMapper mapper;

    public MicroserviceAggregateRootsService(
        ILogger<MicroserviceAggregateRootsService> logger,
        IMicroserviceAggregateRootsProcessor processor,
        IMicroserviceAggregateRootsRetriever retriever,
        IMapper mapper)
    {
        this.logger = logger;
        this.processor = processor;
        this.retriever = retriever;
        this.mapper = mapper;
    }

    /// <summary>
    /// Creates a new MicroserviceAggregateRoot.
    /// </summary>
    public async Task<CreateMicroserviceAggregateRootResponse> CreateMicroserviceAggregateRootAsync(
        CreateMicroserviceAggregateRootRequest request)
    {
        try
        {
            this.logger.LogInformation(
                "CreateMicroserviceAggregateRoot called for ObjectId: {ObjectId}",
                request.ObjectId);

            // Map SOAP request to domain input
            var input = this.mapper.Map<CreateMicroserviceAggregateRootInput>(request);

            // Execute domain service
            var entity = await this.processor.CreateMicroserviceAggregateRoot(
                input,
                CancellationToken.None);

            // Map domain output to SOAP response
            var response = new CreateMicroserviceAggregateRootResponse
            {
                CreatedMicroserviceAggregateRoot = this.mapper.Map<MicroserviceAggregateRootDto>(entity)
            };

            this.logger.LogInformation(
                "CreateMicroserviceAggregateRoot completed for ObjectId: {ObjectId}",
                request.ObjectId);

            return response;
        }
        catch (ValidationException ex)
        {
            this.logger.LogWarning(ex, "Validation error in CreateMicroserviceAggregateRoot");
            throw new FaultException<ServiceFault>(
                new ServiceFault
                {
                    Message = ex.Message,
                    ErrorCode = "VALIDATION_ERROR"
                },
                new FaultReason(ex.Message));
        }
        catch (DomainException ex)
        {
            this.logger.LogError(ex, "Domain error in CreateMicroserviceAggregateRoot");
            throw new FaultException<ServiceFault>(
                new ServiceFault
                {
                    Message = ex.Message,
                    ErrorCode = ex.ErrorCode
                },
                new FaultReason(ex.Message));
        }
        catch (Exception ex)
        {
            this.logger.LogError(ex, "Unexpected error in CreateMicroserviceAggregateRoot");
            throw new FaultException<ServiceFault>(
                new ServiceFault
                {
                    Message = "An internal error occurred",
                    ErrorCode = "INTERNAL_ERROR",
                    StackTrace = ex.StackTrace
                },
                new FaultReason("An internal error occurred"));
        }
    }

    /// <summary>
    /// Retrieves a MicroserviceAggregateRoot by ID.
    /// </summary>
    public async Task<GetMicroserviceAggregateRootDetailsResponse> GetMicroserviceAggregateRootDetailsAsync(
        GetMicroserviceAggregateRootDetailsRequest request)
    {
        try
        {
            this.logger.LogInformation(
                "GetMicroserviceAggregateRootDetails called for ObjectId: {ObjectId}",
                request.ObjectId);

            // Map SOAP request to domain input
            var input = this.mapper.Map<GetMicroserviceAggregateRootDetailsInput>(request);

            // Execute domain service
            var entity = await this.retriever.GetMicroserviceAggregateRootDetails(
                input,
                CancellationToken.None);

            if (entity == null)
            {
                throw new FaultException<ServiceFault>(
                    new ServiceFault
                    {
                        Message = $"MicroserviceAggregateRoot with ID {request.ObjectId} not found",
                        ErrorCode = "NOT_FOUND"
                    },
                    new FaultReason($"Resource not found: {request.ObjectId}"));
            }

            // Map domain output to SOAP response
            var response = new GetMicroserviceAggregateRootDetailsResponse
            {
                FoundMicroserviceAggregateRoot = this.mapper.Map<MicroserviceAggregateRootDto>(entity)
            };

            this.logger.LogInformation(
                "GetMicroserviceAggregateRootDetails completed for ObjectId: {ObjectId}",
                request.ObjectId);

            return response;
        }
        catch (FaultException<ServiceFault>)
        {
            throw; // Re-throw fault exceptions
        }
        catch (Exception ex)
        {
            this.logger.LogError(ex, "Unexpected error in GetMicroserviceAggregateRootDetails");
            throw new FaultException<ServiceFault>(
                new ServiceFault
                {
                    Message = "An internal error occurred",
                    ErrorCode = "INTERNAL_ERROR",
                    StackTrace = ex.StackTrace
                },
                new FaultReason("An internal error occurred"));
        }
    }

    /// <summary>
    /// Deletes a MicroserviceAggregateRoot.
    /// </summary>
    public async Task<DeleteMicroserviceAggregateRootResponse> DeleteMicroserviceAggregateRootAsync(
        DeleteMicroserviceAggregateRootRequest request)
    {
        try
        {
            this.logger.LogInformation(
                "DeleteMicroserviceAggregateRoot called for ObjectId: {ObjectId}",
                request.ObjectId);

            // Map SOAP request to domain input
            var input = this.mapper.Map<DeleteMicroserviceAggregateRootInput>(request);

            // Execute domain service
            await this.processor.DeleteMicroserviceAggregateRoot(
                input,
                CancellationToken.None);

            // Return response
            var response = new DeleteMicroserviceAggregateRootResponse
            {
                Success = true
            };

            this.logger.LogInformation(
                "DeleteMicroserviceAggregateRoot completed for ObjectId: {ObjectId}",
                request.ObjectId);

            return response;
        }
        catch (Exception ex)
        {
            this.logger.LogError(ex, "Unexpected error in DeleteMicroserviceAggregateRoot");
            throw new FaultException<ServiceFault>(
                new ServiceFault
                {
                    Message = "An internal error occurred",
                    ErrorCode = "INTERNAL_ERROR",
                    StackTrace = ex.StackTrace
                },
                new FaultReason("An internal error occurred"));
        }
    }
}

Configuration

Service Registration

// CoreWCFExtensions.cs
internal static IServiceCollection AddCoreWCFCommunication(this IServiceCollection services)
{
    ArgumentNullException.ThrowIfNull(services);

    // Register service implementation
    services.AddScoped<IMicroserviceAggregateRootsService, MicroserviceAggregateRootsService>();

    // Register CoreWCF
    services.AddServiceModelServices();

    return services;
}

internal static IApplicationBuilder UseCoreWCF(this IApplicationBuilder application)
{
    ArgumentNullException.ThrowIfNull(application);

    return application;
}

internal static IEndpointRouteBuilder MapCoreWCF(this IEndpointRouteBuilder endpoints)
{
    ArgumentNullException.ThrowIfNull(endpoints);

    // Map SOAP endpoints
    endpoints.UseSoapEndpoint<IMicroserviceAggregateRootsService>(
        "/MicroserviceAggregateRootsService",
        new SoapEncoderOptions
        {
            WriteEncoding = System.Text.Encoding.UTF8,
            MessageVersion = MessageVersion.Soap11WSAddressing10,
            ReaderQuotas = XmlDictionaryReaderQuotas.Max
        },
        SoapSerializer.DataContractSerializer,
        false, // Don't include exception detail in fault
        null, // ServiceHostLifetime
        metadata: new BasicHttpBinding()); // For WSDL generation

    return endpoints;
}

Program.cs Configuration

// Program.cs
var builder = WebApplication.CreateBuilder(args);

// Add services
builder.Services.AddControllers();
builder.Services.AddCoreWCFCommunication();

// Register domain services
builder.Services.AddMicroserviceDomainModel();
builder.Services.AddMicroservicePersistenceModel();

// Register AutoMapper
builder.Services.AddAutoMapper(typeof(Program));

var app = builder.Build();

// Configure pipeline
app.UseRouting();
app.UseEndpoints(endpoints =>
{
    endpoints.MapControllers();
    endpoints.MapCoreWCF();
});

app.Run();

Binding Configuration

BasicHttpBinding (HTTP/HTTPS)

endpoints.UseSoapEndpoint<IMicroserviceAggregateRootsService>(
    "/MicroserviceAggregateRootsService",
    new SoapEncoderOptions
    {
        WriteEncoding = System.Text.Encoding.UTF8,
        MessageVersion = MessageVersion.Soap11WSAddressing10
    },
    SoapSerializer.DataContractSerializer,
    false,
    null,
    metadata: new BasicHttpBinding
    {
        MaxReceivedMessageSize = 65536,
        MaxBufferSize = 65536,
        ReaderQuotas = new System.Xml.XmlDictionaryReaderQuotas
        {
            MaxArrayLength = 16384,
            MaxStringContentLength = 8192
        }
    });

WSHttpBinding (WS-* Protocols)

endpoints.UseSoapEndpoint<IMicroserviceAggregateRootsService>(
    "/MicroserviceAggregateRootsService",
    new SoapEncoderOptions
    {
        WriteEncoding = System.Text.Encoding.UTF8,
        MessageVersion = MessageVersion.Soap12WSAddressing10
    },
    SoapSerializer.DataContractSerializer,
    false,
    null,
    metadata: new WSHttpBinding
    {
        Security = new WSHttpSecurity
        {
            Mode = SecurityMode.Transport, // HTTPS
            Transport = new HttpTransportSecurity
            {
                ClientCredentialType = HttpClientCredentialType.None
            }
        }
    });

NetTcpBinding (TCP)

endpoints.UseSoapEndpoint<IMicroserviceAggregateRootsService>(
    "/MicroserviceAggregateRootsService",
    new SoapEncoderOptions
    {
        WriteEncoding = System.Text.Encoding.UTF8,
        MessageVersion = MessageVersion.Soap12WSAddressing10
    },
    SoapSerializer.DataContractSerializer,
    false,
    null,
    metadata: new NetTcpBinding
    {
        MaxReceivedMessageSize = 65536,
        ReaderQuotas = new System.Xml.XmlDictionaryReaderQuotas
        {
            MaxArrayLength = 16384,
            MaxStringContentLength = 8192
        }
    });

Integration with CQRS

CoreWCF integrates with CQRS the same way as REST API:

// Command Operation (Write)
public async Task<CreateMicroserviceAggregateRootResponse> CreateMicroserviceAggregateRootAsync(
    CreateMicroserviceAggregateRootRequest request)
{
    var input = this.mapper.Map<CreateMicroserviceAggregateRootInput>(request);

    // Use Processor (command side)
    var entity = await this.processor.CreateMicroserviceAggregateRoot(input, CancellationToken.None);

    return new CreateMicroserviceAggregateRootResponse
    {
        CreatedMicroserviceAggregateRoot = this.mapper.Map<MicroserviceAggregateRootDto>(entity)
    };
}

// Query Operation (Read)
public async Task<GetMicroserviceAggregateRootDetailsResponse> GetMicroserviceAggregateRootDetailsAsync(
    GetMicroserviceAggregateRootDetailsRequest request)
{
    var input = this.mapper.Map<GetMicroserviceAggregateRootDetailsInput>(request);

    // Use Retriever (query side)
    var entity = await this.retriever.GetMicroserviceAggregateRootDetails(input, CancellationToken.None);

    return new GetMicroserviceAggregateRootDetailsResponse
    {
        FoundMicroserviceAggregateRoot = this.mapper.Map<MicroserviceAggregateRootDto>(entity)
    };
}

Error Handling

CoreWCF uses FaultException for error handling:

try
{
    // Execute operation
    var result = await this.processor.CreateMicroserviceAggregateRoot(input, CancellationToken.None);
    return response;
}
catch (ValidationException ex)
{
    // Return SOAP fault for validation errors
    throw new FaultException<ServiceFault>(
        new ServiceFault
        {
            Message = ex.Message,
            ErrorCode = "VALIDATION_ERROR"
        },
        new FaultReason(ex.Message));
}
catch (DomainException ex)
{
    // Return SOAP fault for business logic errors
    throw new FaultException<ServiceFault>(
        new ServiceFault
        {
            Message = ex.Message,
            ErrorCode = ex.ErrorCode
        },
        new FaultReason(ex.Message));
}
catch (Exception ex)
{
    // Return SOAP fault for unexpected errors
    this.logger.LogError(ex, "Unexpected error");
    throw new FaultException<ServiceFault>(
        new ServiceFault
        {
            Message = "An internal error occurred",
            ErrorCode = "INTERNAL_ERROR"
        },
        new FaultReason("An internal error occurred"));
}

WSDL Generation

CoreWCF automatically generates WSDL at the service endpoint:

GET /MicroserviceAggregateRootsService?wsdl

The WSDL can be used to generate client proxies in various languages:

# Generate .NET client proxy
svcutil.exe http://localhost:5000/MicroserviceAggregateRootsService?wsdl

# Generate Java client proxy
wsimport.exe http://localhost:5000/MicroserviceAggregateRootsService?wsdl

Best Practices

Do's

  1. Use data contracts properly
  2. Mark all serializable properties with [DataMember]
  3. Set appropriate IsRequired and Order properties
  4. Use namespaces to avoid conflicts

  5. Handle faults appropriately

  6. Use FaultException<T> for SOAP faults
  7. Include meaningful error codes
  8. Don't expose internal implementation details

  9. Define service contracts clearly

  10. Use [ServiceContract] with appropriate namespaces
  11. Document operations with XML comments
  12. Use [FaultContract] to declare possible faults

  13. Choose appropriate bindings

  14. Use BasicHttpBinding for simple HTTP/HTTPS scenarios
  15. Use WSHttpBinding for WS-* protocol requirements
  16. Use NetTcpBinding for TCP-based communication

  17. Integrate with domain services

  18. Use Processors for commands
  19. Use Retrievers for queries
  20. Maintain architectural boundaries

  21. Implement proper logging

  22. Log operation entry and exit
  23. Log faults and errors
  24. Include correlation IDs

Don'ts

  1. Don't bypass domain services
  2. Always use Processors/Retrievers
  3. Don't access repositories directly
  4. Maintain architectural boundaries

  5. Don't expose internal exceptions

  6. Use fault contracts instead
  7. Don't include stack traces in production
  8. Return meaningful error messages

  9. Don't ignore versioning

  10. Use namespaces for versioning
  11. Consider backward compatibility
  12. Document breaking changes

Testing

Unit Testing Services

[Fact]
public async Task CreateMicroserviceAggregateRoot_Should_Return_Response()
{
    // Arrange
    var mockProcessor = new Mock<IMicroserviceAggregateRootsProcessor>();
    var mockMapper = new Mock<IMapper>();
    var service = new MicroserviceAggregateRootsService(
        Mock.Of<ILogger<MicroserviceAggregateRootsService>>(),
        mockProcessor.Object,
        Mock.Of<IMicroserviceAggregateRootsRetriever>(),
        mockMapper.Object);

    var request = new CreateMicroserviceAggregateRootRequest { ObjectId = Guid.NewGuid() };
    var input = new CreateMicroserviceAggregateRootInput { ObjectId = request.ObjectId };
    var entity = Mock.Of<IMicroserviceAggregateRoot>();
    var dto = new MicroserviceAggregateRootDto { ObjectId = request.ObjectId };

    mockMapper.Setup(m => m.Map<CreateMicroserviceAggregateRootInput>(request))
        .Returns(input);
    mockProcessor.Setup(p => p.CreateMicroserviceAggregateRoot(input, It.IsAny<CancellationToken>()))
        .ReturnsAsync(entity);
    mockMapper.Setup(m => m.Map<MicroserviceAggregateRootDto>(entity))
        .Returns(dto);

    // Act
    var result = await service.CreateMicroserviceAggregateRootAsync(request);

    // Assert
    Assert.NotNull(result);
    Assert.NotNull(result.CreatedMicroserviceAggregateRoot);
    Assert.Equal(request.ObjectId, result.CreatedMicroserviceAggregateRoot.ObjectId);
}

Summary

CoreWCF in the ConnectSoft Microservice Template provides:

  • SOAP/WSDL Support: Standardized XML-based protocol
  • Enterprise Integration: Interoperability with legacy systems
  • CQRS Integration: Processors for commands, Retrievers for queries
  • Multiple Bindings: HTTP, WS-*, TCP support
  • Error Handling: SOAP fault contracts
  • Security: WS-Security and transport security support
  • Contract-First: WSDL generation and client proxy support

By following these patterns, CoreWCF becomes a powerful SOAP service model that maintains architectural integrity while providing enterprise-grade interoperability.