Question

I have a WCF client used in MVC application which can get data from multiple WCF services, the services are configured the same way and Implement the same Interface the only difference is the address of the exposed endpoint.

This is what I tried:

  builder.Register(c => new ChannelFactory<IService>(
                     new BasicHttpBinding(),
                     new EndpointAddress("http://service.com/Service")))
                     .InstancePerHttpRequest();

  builder.Register(c => c.Resolve<ChannelFactory<IService>>().CreateChannel())
      .UseWcfSafeRelease();

The thing here is that IService will always get data from http://service.com/Service since the address is hardcoded somewhere in the Application_Start method of the MVC application.

Then i tried using metadata:

    builder.Register(c => new ChannelFactory<IService>(
                    new BasicHttpBinding(),
                    new EndpointAddress("http://foo.com/Service")))
                    .SingleInstance().WithMetadata("name", "fooservice");

    builder.Register(c => new ChannelFactory<IService>(
                     new BasicHttpBinding(),
                     new EndpointAddress("http://bar.com/Service")))
                     .SingleInstance().WithMetadata("name", "barservice");

    builder.Register(c => c.Resolve<ChannelFactory<IService>>().CreateChannel())
      .UseWcfSafeRelease();

But this way I will have to edit the code every time I want to add the same WCF service implemented on a different server.Instead I want to get the address from the database.

Is there any way I can change the address per service call or at least when the instance of the client is created.

Additional explanation:

Lets say I have five exact copies of a website each with it's own domain name and database I want to be able to do the following:

foreach(Provider provider in providers)
{
    SetServiceAddress(provider.Address);//how can i do that
    _service.GetData()
}
Was it helpful?

Solution

Under the assumptions that:

  • The binding doesn't change when the address changes (e.g., it doesn't switch from HTTP to HTTPS)
  • The address might change on a per-request basis

Then I'd probably solve it with a combination of lambdas and a small interface.

First, you'd want something that retrieves the address from your data store:

public interface IAddressReader
{
  Uri GetAddress();
}

The implementation of that would read from the database (or environment, or XML config, or whatever).

Then I'd use that in my registrations:

builder
  .RegisterType<MyDatabaseAddressReader>()
  .As<IAddressReader>();
builder
  .Register(c => new ChannelFactory<IService>(new BasicHttpBinding()))
  .SingleInstance();
builder
  .Register(c =>
    {
      var reader = c.Resolve<IAddressReader>();
      var factory = c.Resolve<ChannelFactory<IService>();
      var endpoint = new EndpointAddress(reader.GetAddress());
      return factory.CreateChannel(endpoint);
    })
  .As<IService>()
  .UseWcfSafeRelease();

That way you can just take in an IService (or Func<IService>) as a constructor parameter and your calling class won't know about Autofac, service location, or endpoints.

If the binding also changes, it gets a little more complicated. You probably don't want a brand new channel factory spun up for every channel, so you'd want to have some sort of caching mechanism where you:

  1. Get the settings from the configuration source.
  2. Compares those settings against the settings currently in use.
  3. If the settings don't match...
    1. Dispose of the previous channel factory.
    2. Create a new channel factory with the new settings.
    3. Cache the channel factory for later reuse.
  4. Return the current channel factory.

If you can use cache dependencies on the settings, all the better, but not every configuration source supports that, so YMMV. I'd probably implement a custom module for that to encapsulate the logic, but I won't write all that out here.

OTHER TIPS

If you want to set the endpoint just before the call each time, you can do this:

containerBuilder
    .Register(c => new ChannelFactory<IService>(new BasicHttpBinding()))
    .SingleInstance();

containerBuilder.Register((c, p) =>
{
    var factory = c.Resolve<ChannelFactory<IService>>();
    var endpointAddress = p.TypedAs<string>();
    return factory.CreateChannel(new EndpointAddress(endpointAddress));
})
    .As<IService>()
    .UseWcfSafeRelease();

Then you inject this:

Func<string, IService> getService

Then call it like this:

string endpoint = getDataDependentEndpointFromSomewhere();
var service = getService(endpoint);

I have a service that is running on multiple sites, and at start-up the app needs to determine at which site it is running. It does so using a start-up parameter, and based on this the endpoint address can be set dynamically in a property or method, like GetEndPointAddressForService().

In your case it seems that you need to call n services at different sites consecutively. You could definitely configure these in a database or a simple configuration file on disk, load the service definitions including their endpoint addresses at start-up, keep them in a list and do a foreach when collecting data from all existing servers.

The key part of your logic is in the following part of your code:

new EndpointAddress("http://bar.com/Service")

Do a

foreach (ServiceDefinition sd in ServiceDefinitions)
{
    builder.Register(c => new ChannelFactory<IService>(
        new BasicHttpBinding(),
        new EndpointAddress(sd.EndPointAddress)))
        .InstancePerHttpRequest();

    builder.Register(c => c.Resolve<ChannelFactory<IService>>().CreateChannel())
        .UseWcfSafeRelease();

    GoGetTheData();
}

At the end I have used the following implementation:

On application start I register the ChannelFactory type without the endpoint address. And I use named parameter to register the client so i can be able to assign the address later when I actually call the service.

builder.RegisterType<ChannelFactory<IService>>(new BasicHttpBinding())
                     .SingleInstance();
builder.Register((c, p) => c.Resolve<ChannelFactory<IService>>().CreateChannel(p.Named<EndpointAddress>("address")))
                        .UseWcfSafeRelease();

and then I use the service client at runtime like this:

public Data GetData(string url)
{
     EndpointAddress address = new EndpointAddress(url);
     NamedParameter parameter = new NamedParameter("address", address);

     var service = _autofacContainer.Resolve<IService>(parameter);//this is what I have been looking for

     Response response = service.GetData();
     return CreateDataFromResponse(response);
}

this way I can call the GetData method for each address in the database. And I'm going to able to add more addresses at runtime without code or configuration editing.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top