I admit, I'm very slow when it comes to absorbing new technologies, frameworks, or methodologies. I have a very simple 3-teir pattern that works very well and I've seen way too many debocles with projects that jumped head first into the latest craze and then everybody shrugs their shoulders when asked what went wrong. SOA and webservices, ORMs, Workflow Foundation, I've seen them all find their way into a project that would have faired much better with a more traditional platform. If you want to know if you're next project is going to be another IT failure statistic, then I would look carefully at the technologies you're including for the sake of being on the cutting edge.
And so now you have my reason for my delayed start into WCF. This is actually my second attempt. I had starting using it as the middle teir for a Windows Mobile project, but quickly found it's complexity overkill for something that didnt' support most of it's features anyway. I went back to good ole ASMX services with a custom authentication header. But now another service project has come up. It has the additional requirement of being publicly accessible, so I've decided to give WCF another chance for the sake of newer standards and better interoperability. And this time I really gave it the time that was needed. Which translates to: it wasn't easy.
The flexibility of WCF is impressive, which is another way of saying the configurability is daunting. I read an article that compared ASMX to a small Cessna and WCF to a 747. I will warn you now - if you're project only requires a 'flight' from Detroit to Chicago, then avoid the debocles I mentioned above and take the Cessna (ASMX). Most of the things I struggled configuring for the first time in WCF, I could have programmed faster with an ASMX service. But at some point I suppose my knowledge of this technology will catch up to it's underlying concepts.
Ok so enough babble. Rather than write a neat and tidy article that makes me out to be a genious that does no wrong and make WCF look like a piece of cake, I'm going to write this article as it happened so I might help people out of the same holes that I fell in.
I started out with a simple service that took two parameters and returned an object. There's a dozen tutorials that will walk you through the ServiceContract and DataContract attribs to do this, but I just renamed the default items created with a new WCF project inside of Visual studio 2010 and added in my own data. When following such a tutorial I ran into my first problem:
The 'Edit WCF Configuration' menu optionis not available when right clicking on the web.config.
I could go to Tools > WCF Service Configuration Editor, but doing so brought up an empty config. However, once I opened that, the 'Edit WCF Configuration' option was now available on my web.config.
That was simple enough. I pressed F5, and the WCF Test Client started up and I tested my service. I then added another file to the project. Next problem:
Debugging with F5 started a browser with the service page rather than the WCF test client.
Marking the page I wanted to test as the 'Start Page' fixed that. However, it wasn't long before I got sick of waiting for the test client to start, so eventually I started the executable for the test client manually and just used it's 'Refresh' option after each compile.
Transport Level Custom Authentication
But that was the end of simple. I knew my services were going to require custom authentication so that was the next task. This article makes this look like a simple task. I followed it but that's where my real education with WCF began. I first configured this service for transport security and that gave me my next error:
"Could not find a base address that matches scheme https for the endpoint with binding basichttpbinding." \
Configuring <security mode="Transport"> automatically configures your endpoint address to be https. Ok, so I tried to disable that by setting <security mode="TransportCredentialOnly"> and that brought me to:
"Security settings for this service require 'Basic' Authentication but it is not enabled for the IIS application that hosts this service."
Well the ASP.net dev server doesn't have a basic auth mode so at this point I switched to my local IIS server and enabled basic auth for the site.
"BasicHttp binding requires that BasicHttpBinding.Security.Message.ClientCredentialType be equivalent to the BasicHttpMessageCredentialType.Certificate credential type for secure messages. Select Transport or TransportWithMessageCredential security for UserName credentials."
Apparently if you set the transport layer to send credentials only and forget the ssl, WCF says 'Whoa, you can't send plaintext user names and passwords' and it wants to do the certificate encryption itself. At first this made no sense to me. Why does WCF care? SSL has always been handled by IIS OUTSIDE of the application. But then I realized that WCF can be self-hosted or hosted in windows service. In fact it probably should be since the only features that IIS adds is app pool recycling and auto-startup and I'm not sure either of those are worth the problems I ran in to. However, i wanted to avoid going outside the status quo for webservices (it had more to do with my stubborness), so I continued down the IIS route.
There is a new option called allowInsecureTransport (also availabile as a hotfix in 3.5 sp1) that is supposed to allow this. Not only is this desirable in terms of development, but many organizations terminate the ssl connection at the firewall or an ssl accelerator. It also erks the heck out of me that it took nearly 4 versions to realize this. It erks me even more that it breaks the WSDL generation and requires a hack to fix. Ok fine, I generated a self-signed cert and installed it on my IIS server.
The funny thing is that after all this I realized there is a note in custom authentication tutorial that says you can't configure a service for transport security if you're going to host it in IIS. This makes perfect sense. HTTP-Basic authentication is handled and validated against the server or active directory, long before the request is passed to IIS. Uggg!
Transport Level Message Level Custom Authentication
So at this point I decided to abandon passing the credentials via http and instead include them in the message. This meant setting the following in my basicHTTPBinding:
From there I could at least bring up the web service info page in a browser without error. However, the WCF test client cannot pass credentials, so I whipped up a quick nunit project instead. I added a service reference to my WCF service and added this quick test method:
public void GetInventoryDetail()
ItemServiceClient wsClient = new ItemServiceClient();
wsClient.ClientCredentials.UserName.UserName = "test1";
wsClient.ClientCredentials.UserName.Password = "1tset";
ItemInventoryDetail data = wsClient.GetInventoryDetail("64817", "11006.00-G");
I then got:
System.ServiceModel.Security.SecurityNegotiationException : Could not establish trust relationship for the SSL/TLS secure channel with authority 'mycomputername'.
This was because, my computer as a certificate authority is not trusted. So I added the following to the top of my test method...
System.Net.ServicePointManager.ServerCertificateValidationCallback += (se, cert, chain, sslerror) =>
I then got:
System.ServiceModel.Security.MessageSecurityException : An unsecured or incorrectly secured fault was received from the other party. See the inner FaultException for the fault code and detail.
----> System.ServiceModel.FaultException : At least one security token in the message could not be validated.
This didn't mean a whole lot so I enabled tracing for the service by adding the following to the web.config:
initializeData= "c:\temp\WCFTraces.svclog" />
Opening the tracefile, I found the following exceptions:
System.ServiceModel.Security.MessageSecurityException: Message security verification failed. --->; System.IdentityModel.Tokens.SecurityTokenValidationException: LogonUser failed for the 'test1' user. Ensure that the user has a valid Windows account. --->; System.ComponentModel.Win32Exception: Logon failure: unknown user name or bad password
Win32Exception? A helpful msdn poster then told me that I didn't link my behavior to my service:
This part was left out of the tutorial I was following as well. Another solution, was to remove the behavior name from the behavior so that the two could be matched on an empty string.
And after all that, it finally worked!
So after two attempts I'm finally sitting here with a basic working model for a WCF project. I still need to do a little research as far as structuring a service (many classes end up becoming a single service), but I have a lot more confidence this time to actually move forward with WCF. Am I going to use it for everything? No. But I have to admit I enjoy the idea of writting a local business service and later exposing it to the web with just a simple configuration change.