Reputation: 21
I recently appeared for an interview where I was asked following two questions:-
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.
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.
Any help is much appreciated.
Upvotes: 2
Views: 850
Reputation: 996
To answer you first question I create a simple prototype project to demonstrate a dynamic WCF proxy implementation: 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.
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. It consists of one method returning hash algorithm name and hash itself, computed on the server-side:
public HashInfo GetHash(string value)
wcf_dynclient: just a console host of the service, exposing two endpoints - wsHttpBinding and metadata exchange.
wcf_dynclient_proxy: this is a core proxy implementation. To create a proxy you need a service address and contract you want to use.
wcf_dynclient_proxy_client: this is a 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.
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.
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.
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. Here is a good article on this topic:http://blogs.msdn.com/b/carlosfigueira/archive/2011/05/31/wcf-extensibility-iinstanceprovider.aspx.
Upvotes: 2