簡體   English   中英

WCF客戶端代理實施

[英]WCF Client proxy implementation

我最近出現在一次采訪中,被問到以下兩個問題:

  1. 您將如何創建WCF客戶端代理,當Service實現中發生某些變化時,該WCF客戶端代理會自動更新自身,而無需再次構建客戶端,也無需使用Visual Studio更新服務參考功能。

  2. 您將如何覆蓋會話管理( PerSessionPerCallSingle )到您的服務代碼繞過通過WCF以上3個會話管理選項提供的正常功能造成自定義功能。

任何幫助深表感謝。

為了回答您的第一個問題,我創建了一個簡單的原型項目來演示動態WCF代理實現: https : //github.com/knyu15/wcf-dynclient

這是非常基本的實現,只是某種概念驗證。 此服務功能實現了生成客戶端提供的字符串的MD5-哈希值。

基本思想是從服務器檢索元數據,在代理對象中對其進行編譯,然后使用動態對象來訪問它們。

它包含四個項目:

  • wcf_dynclient_lib :表示服務接口(IDynClientLib)和實現。 它由一種返回哈希算法名稱和哈希本身的方法組成,該方法在服務器端進行了計算:

    公共HashInfo GetHash(字符串值)

  • wcf_dynclient :該服務的控制台主機,公開了兩個端點-wsHttpBinding和元數據交換。

  • wcf_dynclient_proxy :這是一個核心代理實現。 要創建代理,您需要使用的服務地址和合同。

  • wcf_dynclient_proxy_client :這是一個客戶端。 請注意,它不包含對wcf_dynclient_lib的引用,而是使用代理(wcf_dynclient_proxy)訪問服務方法和數據。

關鍵問題之一是為服務代理實現自動更新方案。 在雙工綁定或WebSocket實現的情況下,可能是發送到代理客戶端以更新其元數據的信號。

但是在我的場景中,我將注意力放在最常見的情況上,即服務器和客戶端僅公開元數據時,是否需要更新。 如果需要,使用雙工通道為代理實現自動更新方案沒有問題。

這是完整的使用場景:

class Program
{
    // persistent proxy object
    private static readonly Proxy Proxy = new Proxy("http://localhost:8100/mex", "IDynClientLib");

    // persistent dynamic client-side instance of the WCF service contract implementation
    private static dynamic Instance;

    static void Main(string[] args)
    {
        const string testString = "test string";            
        Demo(testString);
    }

    private static void Demo(string testString)
    {
        // Check if we have an instance, create otherwise
        if (Instance == null)
            Instance = Proxy.CreateNewInstance();

        // Check if update of contract needed. 
        // Please note, that if contract was updated (some methods added, some removed, etc.) the
        // existent code may be not valid!
        if (Proxy.IsUpdateNeeded())
            Instance = Proxy.CreateNewInstance();

        // Get response of the server (HashInfo instance)
        var serverHash = Instance.GetHash(testString);

        // Server-side hash bytes
        var serverHashBytes = serverHash.Hash;

        // Client-side hash bytes
        var clientHashBytes = GetHash(testString);

        // Check if server returns correct hash algorithm info
        if (serverHash.HashAlgorithm != "MD5")
            throw new InvalidOperationException("Wrong hash algorithm");

        // Dump algorithm info to console
        Console.WriteLine(serverHash.HashAlgorithm);

        // Check if hash valid
        if (CompareHash(serverHashBytes, clientHashBytes) == false)
            throw new InvalidOperationException("Hash does not equals");
        else
            Console.WriteLine("Hash equals!");
    }

    static byte[] GetHash(string value)
    {
        using (MD5 hashAlgorithm = new MD5Cng())
        {
            return hashAlgorithm.ComputeHash(Encoding.UTF8.GetBytes(value));
        }
    }

    static bool CompareHash(byte[] hash1, byte[] hash2)
    {
        return StructuralComparisons.StructuralEqualityComparer.Equals(hash1, hash2);
    }
}

代理實際上從服務器檢索元數據,對其進行編譯,然后返回允許使用未知合約的動態對象包裝器。

以下是代理類的摘錄。 CreateInstance方法使用先前生成的元數據創建服務實例對象。

public dynamic CreateNewInstance()
{
    Debug.Assert(string.IsNullOrWhiteSpace(m_contractName) == false);

    // update metadata
    Update();

    // compile and return dynamic wrapper
    return Compile();
}

private object CreateInstance(CompilerResults compilerResults)
{
    ServiceEndpoint serviceEndpoint = GetServiceEndpoint();
    if (serviceEndpoint == null)
        throw new InvalidOperationException("ServiceEndpoint is not initialized");

    var clientProxyType = compilerResults.CompiledAssembly.GetTypes().First(
        t => t.IsClass &&
             t.GetInterface(m_contractName) != null &&
             t.GetInterface(typeof (ICommunicationObject).Name) != null);

    var instance = compilerResults.CompiledAssembly.CreateInstance(
        clientProxyType.Name,
        false,
        BindingFlags.CreateInstance,
        null,
        new object[] {serviceEndpoint.Binding, serviceEndpoint.Address},
        CultureInfo.CurrentCulture, null);

    return instance;
}

服務元數據的生成方式如下:

    // Create MetadataSet from address provided
    private MetadataSet GetMetadata()
    {
        Debug.Assert(string.IsNullOrWhiteSpace(m_mexAddress) == false);

        var mexUri = new Uri(m_mexAddress);
        var mexClient = new MetadataExchangeClient(mexUri, MetadataExchangeClientMode.MetadataExchange)
        {
            ResolveMetadataReferences = true
        };

        return mexClient.GetMetadata();
    }

    // Getting or updating contract descriptions and service endpoints
    private void UpdateMetadata(MetadataSet metadataSet)
    {
        if (metadataSet == null) 
            throw new ArgumentNullException("metadataSet");

        MetadataImporter metadataImporter = new WsdlImporter(metadataSet);
        m_contractDescriptions = metadataImporter.ImportAllContracts();
        m_serviceEndpoints = metadataImporter.ImportAllEndpoints();
    }

    // Compile metadata
    private CompilerResults CompileMetadata()
    {
        Debug.Assert(string.IsNullOrWhiteSpace(m_contractName) == false);
        Debug.Assert(m_contractDescriptions != null);
        Debug.Assert(m_serviceEndpoints != null);

        var generator = new ServiceContractGenerator();

        m_serviceContractEndpoints.Clear();
        foreach (var contract in m_contractDescriptions)
        {
            generator.GenerateServiceContractType(contract);
            m_serviceContractEndpoints[contract.Name] = m_serviceEndpoints.Where(
                se => se.Contract.Name == contract.Name).ToList();
        }

        if (generator.Errors.Count != 0)
            throw new InvalidOperationException("Compilation errors");

        var codeDomProvider = CodeDomProvider.CreateProvider("C#");
        var compilerParameters = new CompilerParameters(
            new[]
            {
                "System.dll", "System.ServiceModel.dll",
                "System.Runtime.Serialization.dll"
            }) { GenerateInMemory = true };


        var compilerResults = codeDomProvider.CompileAssemblyFromDom(compilerParameters,
            generator.TargetCompileUnit);

        if (compilerResults.Errors.Count > 0)
            throw new InvalidOperationException("Compilation errors");

        return compilerResults;
    }

我上面發布的鏈接提供了完整的源代碼。

檢查是否需要更新比較服務元數據。 這不是一種有效的方法,僅作為示例添加:

public bool IsUpdateNeeded()
{
    if (m_metadataSet == null)
        return true;

    var newServerMetadata = GetMetadata();

    var newMetadataString = SerializeMetadataSetToString(newServerMetadata);
    var currentMetadataString = SerializeMetadataSetToString(m_metadataSet);

    return newMetadataString == currentMetadataString;
}

而自定義動態對象實現只包裝了一個方法調用:

internal class DynamicProxy : DynamicObject
{
    public DynamicProxy(object wcfObjectInstance)
    {
        if (wcfObjectInstance == null) 
            throw new ArgumentNullException("wcfObjectInstance");

        m_wcfObjectInstance = wcfObjectInstance;
    }

    public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
    {
        Debug.Assert(m_wcfObjectInstance != null);

        result = null;

        var method = m_wcfObjectInstance.GetType().GetMethod(binder.Name);
        if (method == null)
            return false;

        result = method.Invoke(m_wcfObjectInstance, args);
        return true;
    }

    private readonly object m_wcfObjectInstance;
}

要回答第二個問題,您需要使用IInstanceProvider接口的自定義實現。 這是一篇有關該主題的好文章: http : //blogs.msdn.com/b/carlosfigueira/archive/2011/05/31/wcf-extensibility-iinstanceprovider.aspx

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM