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:
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¶
- Use data contracts properly
- Mark all serializable properties with
[DataMember] - Set appropriate
IsRequiredandOrderproperties -
Use namespaces to avoid conflicts
-
Handle faults appropriately
- Use
FaultException<T>for SOAP faults - Include meaningful error codes
-
Don't expose internal implementation details
-
Define service contracts clearly
- Use
[ServiceContract]with appropriate namespaces - Document operations with XML comments
-
Use
[FaultContract]to declare possible faults -
Choose appropriate bindings
- Use
BasicHttpBindingfor simple HTTP/HTTPS scenarios - Use
WSHttpBindingfor WS-* protocol requirements -
Use
NetTcpBindingfor TCP-based communication -
Integrate with domain services
- Use Processors for commands
- Use Retrievers for queries
-
Maintain architectural boundaries
-
Implement proper logging
- Log operation entry and exit
- Log faults and errors
- Include correlation IDs
Don'ts¶
- Don't bypass domain services
- Always use Processors/Retrievers
- Don't access repositories directly
-
Maintain architectural boundaries
-
Don't expose internal exceptions
- Use fault contracts instead
- Don't include stack traces in production
-
Return meaningful error messages
-
Don't ignore versioning
- Use namespaces for versioning
- Consider backward compatibility
- 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.