Welcome to Software Development on Codidact!
Will you help us build our independent community of developers helping developers? We're small and trying to grow. We welcome questions about all aspects of software development, from design to code to QA and more. Got questions? Got answers? Got code you'd like someone to review? Please join us.
Post History
I was able to find a solution that uses ConcurrentDictionary<TKey, TValue>, but it's a bit ugly. First, I amended the auto-generated classes in Reference.cs with a new property Guid Internal...
Answer
#1: Initial revision
I was able to find a solution that uses `ConcurrentDictionary<TKey, TValue>`, but it's a bit ugly. First, I amended the auto-generated classes in `Reference.cs` with a new property `Guid InternalCorrelationId`. Since the auto-generated classes are `partial`, this can be done in separate files that aren't changed when the client is regenerated. ```csharp public partial class AutoGeneratedWCFType { private Guid InternalCorrelationIdField; [System.Runtime.Serialization.DataMember()] public Guid InternalCorrelationId { get { return InternalCorrelationIdField; } set { InternalCorrelationIdField = value; } } } ``` Next, I made all my request DTO types derive from a type named `RequestBase`, and all my response DTO types derive from a typed named `ResponseBase`, so I could handle them generically: ``` public abstract class RequestBase { public Guid InternalCorrelationId { get; set; } } public abstract class ResponseBase { public string RequestXml { get; set; } public string ResponseXml { get; set; } } ``` I then added a type `RequestCorrelator` that simply holds on to a `ConcurrentDictionary<Guid, XmlRequestResponse>`: ```csharp public sealed class RequestCorrelator : IRequestCorrelator { public ConcurrentDictionary<Guid, XmlRequestResponse> PendingCalls { get; } public RequestCorrelator() => PendingCalls = new ConcurrentDictionary<Guid, XmlRequestResponse>(); } public sealed class XmlRequestResponse { public string RequestXml { get; set; } public string ResponseXml { get; set; } } ``` `RequestCorrelator` is its own type for DI purposes - you may just be able to use a `ConcurrentDictionary<TKey, TValue>` directly. Finally, we have the code that actually grabs the XML, a type implementing `IClientMessageInspector`: ```csharp public sealed class ClientMessageProvider : IClientMessageInspector { private readonly IRequestCorrelator _requestCorrelator; public ClientMessageProvider(IRequestCorrelator requestCorrelator) => _requestCorrelator = requestCorrelator; public object BeforeSendRequest(ref Message request, IClientChannel channel) { var requestXml = request.ToString(); var internalCorrelationId = GetInternalCorrelationId(requestXml); if (internalCorrelationId != null) { if (_requestCorrelator.PendingCalls.TryGetValue(internalCorrelationId.Value, out var requestResponse)) { requestResponse.RequestXml = requestXml; } request = RemoveInternalCorrelationId(request); } return internalCorrelationId; } public void AfterReceiveReply(ref Message reply, object correlationState) { // WCF can internally correlate a request between BeforeSendRequest and // AfterReceiveReply. We reuse the same correlation ID we added to the // XML as our correlation state. var responseXml = reply.ToString(); var internalCorrelationId = (correlationState is Guid guid) ? guid : default; if (_requestCorrelator.PendingCalls.TryGetValue(internalCorrelationId, out var requestResponse)) { requestResponse.ResponseXml = responseXml; } } private static Guid? GetInternalCorrelationId(string requestXml) { var document = XDocument.Parse(requestXml); var internalCorrelationIdElement = /* You'll have to write this yourself; every WCF XML request is different. */ return internalCorrelationIdElement != null ? Guid.Parse(internalCorrelationIdElement.Value) : null; } private static Message RemoveInternalCorrelationId(Message oldMessage) { // https://stackoverflow.com/a/35639900/2709212 var buffer = oldMessage.CreateBufferedCopy(2 * 1024 * 1024); var tempMessage = buffer.CreateMessage(); var dictionaryReader = tempMessage.GetReaderAtBodyContents(); var document = new XmlDocument(); document.Load(dictionaryReader); dictionaryReader.Close(); var internalCorrelationIdNode = /* You'll also have to write this yourself. */ var parent = internalCorrelationIdNode.ParentNode; parent.RemoveChild(internalCorrelationIdNode); var memoryStream = new MemoryStream(); var xmlWriter = XmlWriter.Create(memoryStream); document.Save(xmlWriter); xmlWriter.Flush(); xmlWriter.Close(); memoryStream.Position = 0; var xmlReader = XmlReader.Create(memoryStream); var newMessage = Message.CreateMessage(oldMessage.Version, null, xmlReader); newMessage.Headers.CopyHeadersFrom(oldMessage); newMessage.Properties.CopyProperties(oldMessage.Properties); return newMessage; } } ``` In short, this type: 1. Finds the correlation ID in the XML request. 2. Finds the `XmlRequestResponse` with the same correlation ID and adds the request to it. 3. Removes the correlation ID element so that the service doesn't get elements they didn't expect. 4. After receiving a reply, uses `correlationState` to find the `XmlRequestResponse` and write the response XML to it. Now all we have to do is change `IWCFWrapperClient`: ```csharp private async Task<TDtoResult> ExecuteCallWithLogging<TDtoRequest, TWcfRequest, TWcfResponse, TDtoResult>(TDtoRequest request, Func<TDtoRequest, TWcfRequest> dtoToWcfConverter, Func<TWcfRequest, Task<TWcfResponse>> wcfCall, Func<TWcfResponse, TDtoResult> wcfToDtoConverter) where TDtoRequest : CorrelationBase where TDtoResult : WcfBase { request.InternalCorrelationId = Guid.NewGuid(); var xmlRequestResponse = new XmlRequestResponse(); _requestCorrelator.PendingCalls.GetOrAdd(request.InternalCorrelationId, xmlRequestResponse); var response = await contractingCall(dtoToWcfConverter(request)); _requestCorrelator.PendingCalls.TryRemove(request.InternalCorrelationId, out _); return wcfToDtoConverter(response).WithRequestResponse(xmlRequestResponse); } public async Task<DoThingResponseDto> DoThing(DoThingRequestDto request) => await ExecuteCallWithLogging(request, r => r.ToWcfModel(), async d => await _wcf.Call(d), d => d.ToDtoModel()); ``` `WithRequestResponse` is implemented as follows: ```csharp public static T WithRequestResponse<T>(this T item, XmlRequestResponse requestResponse) where T : ResponseBase { item.RequestXml = requestResponse?.RequestXml; item.ResponseXml = requestResponse?.ResponseXml; return item; } ``` And there we go. WCF calls that return their XML in the response object rather than just something you can print to console or log to a file.