[英]WCF Client proxy implementation
我最近出现在一次采访中,被问到以下两个问题:
您将如何创建WCF客户端代理,当Service实现中发生某些变化时,该WCF客户端代理会自动更新自身,而无需再次构建客户端,也无需使用Visual Studio更新服务参考功能。
您将如何覆盖会话管理( PerSession
, PerCall
和Single
)到您的服务代码绕过通过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.