简体   繁体   English

WCF客户端代理实施

[英]WCF Client proxy implementation

I recently appeared for an interview where I was asked following two questions:- 我最近出现在一次采访中,被问到以下两个问题:

  1. How will you create a WCF client proxy that will automatically update itself when something changes in the Service implementation without building the client again and without using the Visual studio Update Service Reference functionality. 您将如何创建WCF客户端代理,当Service实现中发生某些变化时,该WCF客户端代理会自动更新自身,而无需再次构建客户端,也无需使用Visual Studio更新服务参考功能。

  2. How will you override the session management ( PerSession , PerCall and Single ) to create custom functionality in your service code bypassing the normal functionality provided by WCF for above 3 session management options. 您将如何覆盖会话管理( PerSessionPerCallSingle )到您的服务代码绕过通过WCF以上3个会话管理选项提供的正常功能造成自定义功能。

Any help is much appreciated. 任何帮助深表感谢。

To answer you first question I create a simple prototype project to demonstrate a dynamic WCF proxy implementation: https://github.com/knyu15/wcf-dynclient 为了回答您的第一个问题,我创建了一个简单的原型项目来演示动态WCF代理实现: https : //github.com/knyu15/wcf-dynclient

This is very basic implementation, just some kind of proof-of-concept. 这是非常基本的实现,只是某种概念验证。 This service functionality implements generating MD5-hash of string provided by a client. 此服务功能实现了生成客户端提供的字符串的MD5-哈希值。

The basic idea is to retrieve metadata from the server, compile it in the proxy object and use dynamic object to access them. 基本思想是从服务器检索元数据,在代理对象中对其进行编译,然后使用动态对象来访问它们。

It consists of four projects: 它包含四个项目:

  • wcf_dynclient_lib : represents service interface (IDynClientLib) and implementation. wcf_dynclient_lib :表示服务接口(IDynClientLib)和实现。 It consists of one method returning hash algorithm name and hash itself, computed on the server-side: 它由一种返回哈希算法名称和哈希本身的方法组成,该方法在服务器端进行了计算:

    public HashInfo GetHash(string value) 公共HashInfo GetHash(字符串值)

  • wcf_dynclient : just a console host of the service, exposing two endpoints - wsHttpBinding and metadata exchange. wcf_dynclient :该服务的控制台主机,公开了两个端点-wsHttpBinding和元数据交换。

  • wcf_dynclient_proxy : this is a core proxy implementation. wcf_dynclient_proxy :这是一个核心代理实现。 To create a proxy you need a service address and contract you want to use. 要创建代理,您需要使用的服务地址和合同。

  • wcf_dynclient_proxy_client : this is a client. wcf_dynclient_proxy_client :这是一个客户端。 Please note, that it is not contains a reference to wcf_dynclient_lib and use a proxy (wcf_dynclient_proxy) to access service method and data. 请注意,它不包含对wcf_dynclient_lib的引用,而是使用代理(wcf_dynclient_proxy)访问服务方法和数据。

One of the key issue is to implement auto-update scenario for the service proxy. 关键问题之一是为服务代理实现自动更新方案。 In case of duplex binding or WebSocket implementation it may be a signal sending to the proxy client to update it's metadata. 在双工绑定或WebSocket实现的情况下,可能是发送到代理客户端以更新其元数据的信号。

But in my scenario I put attention on most common situation, when metadata just exposed by the server and client checks if update needed. 但是在我的场景中,我将注意力放在最常见的情况上,即服务器和客户端仅公开元数据时,是否需要更新。 There are no problem to implement auto-update scenario for proxy using duplex channel if needed. 如果需要,使用双工通道为代理实现自动更新方案没有问题。

Here is a full usage scenario: 这是完整的使用场景:

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

Proxy actually retreive metadata from the server, compiles it and returns dynamic object wrapper which allows to use an unknown contract. 代理实际上从服务器检索元数据,对其进行编译,然后返回允许使用未知合约的动态对象包装器。

Below are snippets from proxy class. 以下是代理类的摘录。 CreateInstance method creates service instance object using previously generated metadata. 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;
}

This is how service metadata generated: 服务元数据的生成方式如下:

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

The full source code available by link I posted above. 我上面发布的链接提供了完整的源代码。

Check if update needed compares service metadata. 检查是否需要更新比较服务元数据。 It is not efficient way to do this, and added just as an example: 这不是一种有效的方法,仅作为示例添加:

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

    var newServerMetadata = GetMetadata();

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

    return newMetadataString == currentMetadataString;
}

And custom dynamic object implementation just wraps a method calls: 而自定义动态对象实现只包装了一个方法调用:

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

To answer you second question you need to use custom implementation of IInstanceProvider Interface. 要回答第二个问题,您需要使用IInstanceProvider接口的自定义实现。 Here is a good article on this topic: http://blogs.msdn.com/b/carlosfigueira/archive/2011/05/31/wcf-extensibility-iinstanceprovider.aspx . 这是一篇有关该主题的好文章: 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