OK, I found a solution so I'm going to answer my own question:
First, I found that by using the new WCF-style proxy class "Service Reference" allows you to set security settings in the app.config file. The key (in my scenario) is to set the mode for transport credentials only, specify Windows as the type, and identify the domain in the realm attribute:
<system.serviceModel>
<bindings>
<basicHttpBinding>
<binding name="Service1Soap" closeTimeout="00:01:00" openTimeout="00:01:00"
receiveTimeout="00:10:00" sendTimeout="00:01:00" allowCookies="false"
bypassProxyOnLocal="false" hostNameComparisonMode="StrongWildcard"
maxBufferSize="65536" maxBufferPoolSize="524288" maxReceivedMessageSize="65536"
messageEncoding="Text" textEncoding="utf-8" transferMode="Buffered"
useDefaultWebProxy="true">
<readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384"
maxBytesPerRead="4096" maxNameTableCharCount="16384" />
<security mode="TransportCredentialOnly">
<transport clientCredentialType="Windows" realm="mydomain.com" />
</security>
</binding>
</basicHttpBinding>
</bindings>
<client>
<endpoint address="http://server.mydomain.com/HelloWorldSvc/Service1.asmx"
binding="basicHttpBinding" bindingConfiguration="Service1Soap"
contract="ServiceReference1.Service1Soap" name="Service1Soap" />
</client>
</system.serviceModel>
Next I used Hernan de Lahitte's excellent wrapper for the CredUIPromptForCredentials API function to prompt the user for their credentials if a security exception is thrown and store it in their profile:
ServiceReference1.Service1SoapClient c = new Service1SoapClient();
retry:
try
{
MessageBox.Show(this, c.HelloWorld());
}
catch (System.ServiceModel.Security.MessageSecurityException securityException)
{
UserCredentialsDialog creds = new UserCredentialsDialog("*.mydomain.com", "My App",
"Enter your enterprise credentials. Enter your user name as \"MyDomain\\username\"");
creds.Flags = UserCredentialsDialogFlags.Persist;
if (creds.ShowDialog() == DialogResult.OK)
{
goto retry;
}
}
c.Close();
Yes, that's a "goto". **GASP** :)