Communities

Writing
Writing
Codidact Meta
Codidact Meta
The Great Outdoors
The Great Outdoors
Photography & Video
Photography & Video
Scientific Speculation
Scientific Speculation
Cooking
Cooking
Electrical Engineering
Electrical Engineering
Judaism
Judaism
Languages & Linguistics
Languages & Linguistics
Software Development
Software Development
Mathematics
Mathematics
Christianity
Christianity
Code Golf
Code Golf
Music
Music
Physics
Physics
Linux Systems
Linux Systems
Power Users
Power Users
Tabletop RPGs
Tabletop RPGs
Community Proposals
Community Proposals
tag:snake search within a tag
answers:0 unanswered questions
user:xxxx search by author id
score:0.5 posts with 0.5+ score
"snake oil" exact phrase
votes:4 posts with 4+ votes
created:<1w created < 1 week ago
post_type:xxxx type of post
Search help
Notifications
Mark all as read See all your notifications »
Q&A

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

60%
+1 −0
Q&A How can I return XML from BeforeSendRequest and AfterReceiveReply to the calling method in a thread-safe way?

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...

posted 1y ago by Celarix‭

Answer
#1: Initial revision by user avatar Celarix‭ · 2023-06-13T23:55:46Z (over 1 year ago)
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.