views:

2188

answers:

3

I have a set of WCF web services connected to dynamically by a desktop application.

My problem is the really detailed config settings that WCF requires to work. Getting SSL to work involves custom settings. Getting MTOM or anything else to work requires more. You want compression? Here we go again...

WCF is really powerful - you can use a host of different ways to connect, but all seem to involve lots of detailed config. If host and client don't match perfectly you get hard to decipher errors.

I want to make the desktop app far easier to configure - ideally some kind of auto-discovery. The users of the desktop app should just be able to enter the URL and it do the rest.

Does anyone know a good way to do this?

I know Visual Studio can set the config up for you, but I want the desktop app to be able to do it based on a wide variety of different server set-ups.

I know that VS's tools can be used externally, but I'm looking for users of the desktop apps to not have to be WCF experts. I know MS made this intentionally over complicated.

Is there any way, mechanism, 3rd party library or anything to make auto-discovery of WCF settings possible?

+7  A: 

All information about the endpoint is available in metadata of a service, you can write a client what will explore the meta data of the service and will configure the client. For a code example you can look into this excellent Mex Explorer from Juval Lowy.

MichaelT
+1  A: 

Thanks, that was useful code (+1).

It's more than a little bit messy though, has some bugs (case sensitive checks that shouldn't be, for instance), has a load of UI functionality that I don't need and repeats a lot of code.

I've taken from it the actual discovery mechanism, re-wrote it and almost got it working (connects, but needs some finessing).

First some util functions used by the main method:

/// <summary>If the url doesn't end with a WSDL query string append it</summary>
static string AddWsdlQueryStringIfMissing( string input )
{
    return input.EndsWith( "?wsdl", StringComparison.OrdinalIgnoreCase ) ?
     input : input + "?wsdl";
}

/// <summary>Imports the meta data from the specified location</summary>
static ServiceEndpointCollection GetEndpoints( BindingElement bindingElement, Uri address, MetadataExchangeClientMode mode )
{
    CustomBinding binding = new CustomBinding( bindingElement );
    MetadataSet metadata = new MetadataExchangeClient( binding ).GetMetadata( address, mode );
    return new WsdlImporter( metadata ).ImportAllEndpoints();
}

Then a method that tries different way to connect and returns the endpoints:

public static ServiceEndpointCollection Discover( string url )
{
    Uri address = new Uri( url );
    ServiceEndpointCollection endpoints = null;

    if ( string.Equals( address.Scheme, "http", StringComparison.OrdinalIgnoreCase ) )
    {
     var httpBindingElement = new HttpTransportBindingElement();

     //Try the HTTP MEX Endpoint
     try { endpoints = GetEndpoints( httpBindingElement, address, MetadataExchangeClientMode.MetadataExchange ); }
     catch { }

     //Try over HTTP-GET
     if ( endpoints == null )
      endpoints = GetEndpoints( httpBindingElement,
       new Uri( AddWsdlQueryStringIfMissing( url ) ), MetadataExchangeClientMode.HttpGet );
    }
    else if ( string.Equals( address.Scheme, "https", StringComparison.OrdinalIgnoreCase ) )
    {
     var httpsBindingElement = new HttpsTransportBindingElement();

     //Try the HTTPS MEX Endpoint
     try { endpoints = GetEndpoints( httpsBindingElement, address, MetadataExchangeClientMode.MetadataExchange ); }
     catch { }

     //Try over HTTP-GET
     if ( endpoints == null )
      endpoints = GetEndpoints( httpsBindingElement,
       new Uri( AddWsdlQueryStringIfMissing( url ) ), MetadataExchangeClientMode.HttpGet );
    }
    else if ( string.Equals( address.Scheme, "net.tcp", StringComparison.OrdinalIgnoreCase ) )
     endpoints = GetEndpoints( new TcpTransportBindingElement(), 
      address, MetadataExchangeClientMode.MetadataExchange );

    else if ( string.Equals( address.Scheme, "net.pipe", StringComparison.OrdinalIgnoreCase ) )
     endpoints = GetEndpoints( new NamedPipeTransportBindingElement(), 
      address, MetadataExchangeClientMode.MetadataExchange );

    return endpoints;
}
Keith
A: 

There is now another way to do this that wasn't available when I asked the original question. Microsoft now supports REST for WCF services.

  • The downside of using REST is that you lose the WSDL.
  • The upside is minimal config and your WCF contract interfaces will still work!

You'll need a new reference to System.ServiceModel.Web

Mark your operations with either WebInvoke or WebGet

//get a user - note that this can be cached by IIS and proxies
[WebGet]
User GetUser(string id )

//post changes to a user
[WebInvoke]
void SaveUser(string id, User changes )

Adding these to a site is easy - add a .svc file:

<%@ServiceHost 
   Service="MyNamespace.MyServiceImplementationClass" 
   Factory="System.ServiceModel.Activation.WebServiceHostFactory" %>

The factory line tells ASP.net how to activate the endpoint - you need no server side config at all!

Then constructing your ChannelFactory is pretty much unchanged, except that you don't need to specify an endpoint any more (or auto-discover one as I have in the other answers)

var cf = new WebChannelFactory<IMyContractInterface>();
var binding = new WebHttpBinding();

cf.Endpoint.Binding = binding;
cf.Endpoint.Address = new EndpointAddress(new Uri("mywebsite.com/myservice.svc"));
cf.Endpoint.Behaviors.Add(new WebHttpBehavior());

IMyContractInterface wcfClient = cf.CreateChannel();

var usr = wcfClient.GetUser("demouser");
// and so on...

Note that I haven't specified or discovered the client config - there's no local config needed!

Another big upside is that you can easily switch to JSON serialisation - that allows the same WCF services to be consumed by Java, ActionScript, Javascript, Silverlight or anything else that can handle JSON and REST easily.

Keith
I've also blogged on why this has changed: http://bizvprog.blogspot.com/2009/11/giving-up-on-soap-for-good.html
Keith