To do this, we have created a ServiceFactory class that connects to a service given its endpoint and an appropriate IClientChannel
-derived interface. This assumes that you are using the WCF services directly, e.g. not via the VS-generated proxies, since you need to set the username and password values on each client channel creation.
The client channel interfaces are in an external "service library" along with the service factory, so they can be shared with the WCF service implementations and the clients. We store the credentials in a static state dictionary (though you also put it, for example, into the main resource dictionary) with the password being saved in a SecuredString
for a tiny bit of extra security.
I've described the basic process for creating such a service factory on by blog:
http://blog.kutulu.org/2012/03/proxy-free-wcf-ditching-proxy.html
In our case, we perform a setup routine in App.xaml.cs
that prompts for credentials and makes an attempt to call one of our services, looping until we get a successful login. That code is much more complex, but it's basically:
while (true)
{
var factory = new ChannelFactory<ITestChannel>(new WSHttpBinding("SecuredBinding"));
ITestChannel client = null;
try
{
factory.Credentials.UserName.UserName = logOnModel.UserName;
factory.Credentials.UserName.Password = logOnModel.Password;
var address = Settings.Default.TestServiceUrlBase));
client = factory.CreateChannel(address);
break;
}
// Catch various exceptions here.
}
The trick here is that, if your login or password is wrong and your UsernamePasswordValidator
fails your login, you'll get a MessageSecurityException
which will fault your channel, so you'll need to close it and open a new one. But you cannot change the credentials on a ChannelFactory
once you've opened the first channel, so you need to dispose and re-create a new factory and new client channel every time.
We also check for CommunicationException
and ArgumentException
here in case the URL is wrong.
Once that's done, you can use similar code in your service factory class to construct a client, given its channel interface, and set up the credentials for each call. (We actually cache the service factories for each distinct interface because we create and destroy channel frequently, but that's up to you.)