We have a console application using the Azure WebJob SDK. The WebJob relies on a WCF service using SOAP, which it accesses through a DLL we wrote that wraps the auto-generated WCF types in something a bit more friendly.
For logging purposes, we want to save the request and response XML bodies for requests that we make. These XML bodies would be saved in our database. But, because the WCF code lives in a low-level DLL, it has no concept of our database and can't save to it.
The DLL uses Microsoft's DI extensions to register types, and the WebJob calls into it like this:
class WebJobClass
{
IWCFWrapperClient _wcfWrapperClient;
public WebJobClass(IWCFWrapperClient wcfWrapperClient)
{
_wcfWrapperClient = wcfWrapperClient;
}
public async Task DoThing()
{
var callResult = await _wcfWrapperClient.CallWCFService();
}
}
IWCFWrapperClient
looks like this:
class WCFWrapperClient : IWCFWrapperClient
{
IWCF _wcf; // auto-generated by VS, stored in Reference.cs
public async Task<object> CallWCFService()
{
return await _wcf.Call(); // another auto-generated method
}
}
I've implemented an IClientMessageInspector
, and it works fine to get me the XML request/response, but I don't have a way to pass it back up to WCFWrapperClient.CallWCFService
so that it can be returned to WebJobClass.DoThing()
, who could then save it to the database.
The problem is multithreading. WebJobs, IIRC, will run multiple requests in parallel, calling into the DLL from multiple threads. This means we can't, say, share a static property LastRequestXmlBody
since multiple threads could overwrite it. We also can't, say, give each call a Guid or something since there's no way to pass anything from IWCFWrapperClient.CallWCFService
into the auto-generated IWCF.Call
except what was auto-generated.
So, how can I return XML to WebJobClass.DoThing
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 propertyGuid InternalCorrelationId
. Since the auto-generated classes arepartial
, this can be done in separate files that aren't changed when the client is regenerated.Next, I made all my request DTO types derive from a type named
RequestBase
, and all my response DTO types derive from a typed namedResponseBase
, so I could handle them generically:I then added a type
RequestCorrelator
that simply holds on to aConcurrentDictionary<Guid, XmlRequestResponse>
:RequestCorrelator
is its own type for DI purposes - you may just be able to use aConcurrentDictionary<TKey, TValue>
directly.Finally, we have the code that actually grabs the XML, a type implementing
IClientMessageInspector
:In short, this type:
XmlRequestResponse
with the same correlation ID and adds the request to it.correlationState
to find theXmlRequestResponse
and write the response XML to it.Now all we have to do is change
IWCFWrapperClient
:WithRequestResponse
is implemented as follows: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.