我們有一個使用 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 呢?
uj5u.com熱心網友回復:
我能夠找到一個使用 的解決方案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;
}
}
簡而言之,這種型別:
- 在 XML 請求中查找相關 ID。
- 查找
XmlRequestResponse具有相同關聯 ID 的 并將請求添加到其中。 - 洗掉相關 ID 元素,以便服務不會獲得他們不期望的元素。
- 收到回復后,用于
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.CaroogoInternalCorrelationId, 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,而不僅僅是您可以列印到控制臺或記錄到檔案的內容。
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/413700.html
標籤:
上一篇:走財運健步——青龍羊毛
