0

我们有一个使用 Azure WebJob SDK 的控制台应用程序。WebJob 依赖于使用 SOAP 的 WCF 服务,它通过我们编写的 DLL 访问该服务,该 DLL 将自动生成的 WCF 类型包装在更友好的东西中。

出于日志记录的目的,我们希望为我们发出的请求保存请求和响应 XML 正文。这些 XML 正文将保存在我们的数据库中。但是,因为 WCF 代码存在于一个低级 DLL 中,它没有我们数据库的概念,也无法保存到它。

DLL 使用 Microsoft 的 DI 扩展来注册类型,WebJob 像这样调用它:

class WebJobClass
{
    IWCFWrapperClient _wcfWrapperClient;

    public WebJobClass(IWCFWrapperClient wcfWrapperClient)
    {
        _wcfWrapperClient = wcfWrapperClient;
    }

    public async Task DoThing()
    {
        var callResult = await _wcfWrapperClient.CallWCFService();
    }
}

IWCFWrapperClient看起来像这样:

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

我已经实现了一个IClientMessageInspector,它可以很好地获取 XML 请求/响应,但是我没有办法将它传回给它,WCFWrapperClient.CallWCFService以便它可以返回给WebJobClass.DoThing(),然后谁可以将它保存到数据库中。

问题是多线程。WebJobs,IIRC,将并行运行多个请求,从多个线程调用 DLL。这意味着我们不能,比如说,共享一个静态属性,LastRequestXmlBody因为多个线程可能会覆盖它。我们也不能,比如说,给每个调用一个 Guid 或其他东西,因为除了IWCFWrapperClient.CallWCFService自动生成的东西之外,没有办法将任何东西传递给自动生成的IWCF.Call东西。

那么,如何WebJobClass.DoThing以线程安全的方式返回 XML 呢?

4

1 回答 1

0

我能够找到一个使用 的解决方案ConcurrentDictionary<TKey, TValue>,但它有点难看。

Reference.cs首先,我使用新属性修改了自动生成的类Guid InternalCorrelationId。由于自动生成的类是partial,这可以在重新生成客户端时不会更改的单独文件中完成。

public partial class AutoGeneratedWCFType
{
    private Guid InternalCorrelationIdField;

    [System.Runtime.Serialization.DataMember()]
    public Guid InternalCorrelationId
    {
        get { return InternalCorrelationIdField; }
        set { InternalCorrelationIdField = value; }
    }
}

接下来,我让我的所有请求 DTO 类型都派生自一个名为 的类型RequestBase,而我所有的响应 DTO 类型都派生自一个类型化的 named ResponseBase,所以我可以通用地处理它们:

public abstract class RequestBase
{
    public Guid InternalCorrelationId { get; set; }
}

public abstract class ResponseBase
{
    public string RequestXml { get; set; }
    public string ResponseXml { get; set; }
}

然后我添加了一个RequestCorrelator简单地保留a 的类型ConcurrentDictionary<Guid, XmlRequestResponse>

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是用于 DI 目的的自己的类型 - 您可能只能ConcurrentDictionary<TKey, TValue>直接使用 a。

最后,我们得到了实际抓取 XML 的代码,这是一种实现IClientMessageInspector

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;
    }
}

简而言之,这种类型:

  1. 在 XML 请求中查找相关 ID。
  2. 查找XmlRequestResponse具有相同关联 ID 的 并将请求添加到其中。
  3. 删除相关 ID 元素,以便服务不会获得他们不期望的元素。
  4. 收到回复后,用于correlationState查找XmlRequestResponse并写入响应 XML。

现在我们要做的就是改变IWCFWrapperClient

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实现如下:

public static T WithRequestResponse<T>(this T item, XmlRequestResponse requestResponse)
    where T : ResponseBase
{
    item.RequestXml = requestResponse?.RequestXml;
    item.ResponseXml = requestResponse?.ResponseXml;

    return item;
}

我们开始了。WCF 调用在响应对象中返回其 XML,而不仅仅是您可以打印到控制台或记录到文件的内容。

于 2022-01-10T16:24:50.687 回答