Reputation: 322
I have a small WCF Client which I'm trying to configure for service discovery via the application configuration file - however whenever I do this:
// Create a new DiscoveryClient instance from the 'DiscoveryEndpoint'
// configuration in App.config
DiscoveryClient discoveryClient = new DiscoveryClient("DiscoveryEndpoint");
I get an ArgumentNullException
, Value cannot be null. Parameter name: contract
. There is no contract
parameter for this overload of the DiscoveryClient
constructor, and the contract is correctly specified in the App.config (see below).
Here are the relevant sections of the App.config:
<system.serviceModel>
<client>
<endpoint name="DiscoveryEndpoint"
contract="IExampleContract"
kind="dynamicEndpoint"
endpointConfiguration="DynamicEndpointConfiguration"/>
</client>
<standardEndpoints>
<dynamicEndpoint>
<standardEndpoint name="DynamicEndpointConfiguration">
<discoveryClientSettings>
<endpoint kind="udpDiscoveryEndpoint"/>
<findCriteria duration="00:00:02">
<types>
<add name="IExampleContract"/>
</types>
<scopes>
<add scope="urn://wcf.test.com/examples/exampleContract/development"/>
</scopes>
</findCriteria>
</discoveryClientSettings>
</standardEndpoint>
</dynamicEndpoint>
</standardEndpoints>
</system.serviceModel>
This is targetted at .NET Framework 4.0, using Visual Studio 2010 SP1.
The documentation for this overload of the DiscoveryClient(string) constructor indicates that this should create a new DiscoveryClient
instance with the configuration identified in App.config.
Has anyone else encountered this behaviour, and if so how did you resolve it?
Upvotes: 1
Views: 1973
Reputation: 21
The "DiscoveryEndpoint" that you defined in the config file is actually a service client end point, not the DiscoveryClient end point.
The following should work:
var exampleContractChannelFactory = new ChannelFactory<IExampleContract>("DiscoveryEndpoint");
var exampleContractClient = exampleContractChannelFactory.CreateChannel();
// You can now invoke methods on the exampleContractClient
// The actual service endpoint used by the client will be looked up dynamically
// by the proxy using the DiscoveryClient class internally.
Upvotes: 1
Reputation: 322
OK, I've spent a lot of time in the debugger with the Enable .NET Framework source stepping
option enabled, and I've found that the cause of this exception being thrown is (probably) a bug in the implementation of the way that the DiscoveryClient
is instantiated from the configuration file - there is a call way down the call stack which passes a hard-coded value of null
into a contract
parameter, which is where the originating exception is thrown.
So, after much head-scratching, and lots of searching, I've come up with the following workaround (well, more like a total HACK:
!) - which I'm posting here to help out anyone else who might encounter the same issue.
// HACK: The following is a workaround for a bug in .NET Framework 4.0
// Discovery should be possible when setup from the App.config with the
// following three lines of code:
//
// discoveryClient = new DiscoveryClient("DiscoveryEndpoint");
// Collection<EndpointDiscoveryMetadata> serviceCollection = discoveryClient.Find(new FindCriteria(typeof(IExampleContract))).Endpoints;
// discoveryClient.Close();
//
// However, a bug in the Discovery Client implementation results in an
// ArgumentNullException when running discovery in this way.
//
// The following code overcomes this limitation by manually parsing the
// standard WCF configuration sections of App.config, and then configuring
// the appropriate FindCriteria for a programmatically configured discovery
// cycle. This code can be replaced by the above three lines when either
// 1. The bug in .NET Framework 4.0 is resolved (unlikely), or
// 2. The application is retargeted to .NET Framework 4.5 / 4.5.1
//
// To aid future developers, this HACK will be extensively documented
// Load the App.config file into a ConfigurationManager instance and load the configuration
Configuration configuration = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
// Get the ServiceModel configuration group
ServiceModelSectionGroup serviceModelGroup = ServiceModelSectionGroup.GetSectionGroup(configuration);
// Get the StandardEndpoints configuration node
StandardEndpointsSection section = serviceModelGroup.StandardEndpoints;
// Get the DynamicEndpoint configuration node
Configuration dynamicEndpointConfiguration = section["dynamicEndpoint"].CurrentConfiguration;
// Get the first DynamicEndpoint configuration
// HACK: This assumes only one DynamicEndpoint configuration exists
// No additional configurations will be interpreted. This should
// not pose a problem as typically a client will only access a
// single service instance. This can be extended if necessary
// at a later time.
DynamicEndpointElement element = ((DynamicEndpointElement)serviceModelGroup.StandardEndpoints["dynamicEndpoint"].ConfiguredEndpoints[0]);
// Set the required Contract Type
// HACK: This is currently hard-coded to prevent the need to specify
// an AssemblyQualifiedName in the App.config file. This will
// not typically pose a problem as each client will typically
// only open a single service exposing a single, well-known,
// Contract Type
FindCriteria criteria = new FindCriteria(typeof(IExampleContract));
// Add all required Scopes to the FindCriteria instance
foreach (ScopeElement scopeElement in element.DiscoveryClientSettings.FindCriteria.Scopes)
{
criteria.Scopes.Add(scopeElement.Scope);
}
// Get the Discovery Duration
criteria.Duration = element.DiscoveryClientSettings.FindCriteria.Duration;
// Create a new Discovery Client instance
DiscoveryClient discoveryClient = new DiscoveryClient(new UdpDiscoveryEndpoint());
// Retrieve the matching Service Endpoints via Dynamic Search
Collection<EndpointDiscoveryMetadata> serviceCollection = discoveryClient.Find(criteria).Endpoints;
// Close the Discovery Client
discoveryClient.Close();
// HACK: END -- Process the results of Discovery
Note that I'm assuming that this issue is resolved in .NET Framework 4.5 / 4.5.1 - it may not be, but I cannot test that at the moment.
If anyone else has an alternative solution, more elegant or efficient than this, please post it to help others.
Upvotes: 0