Showing posts with label Binding. Show all posts
Showing posts with label Binding. Show all posts

Thursday, January 19, 2012

Custom ADFS Login Form for SharePoint 2010 Claims

This week I've been involved in creating a custom login page for SharePoint 2010 to bypass the standard "select a login method" page for multi-mode claims-enabled web-applications. What we wanted was similar to the Claims Login Web Part for SharePoint Server 2010 for Forms-Based Authentication (FBA) by Jeremy Jameson, but for a trusted ADFS 2.0 identity provider instead.


Having a custom login page allows you to stay in your site and avoid the passive STS authN redirect dance back and forth between SP and the ADFS STS for authentication. This requires you to use active mode (WS-Trust) rather than the passive mode used by SharePoint. Note that this active approach won't give you single sign-on, because you won't get the MSISAuth ADFS SSO cookies - it will simply authenticate you first and then give you the SharePoint FedAuth cookie.

The code you need to call ADFS to make it authenticate you, and thus issue a claims token for use with SharePoint, can be found at Using an Active Endpoint to sign into a Web Application by Dominick Baier or Making a web application use an active STS by Koen Willemse. The missing detail not shown in their code is the URL to the ADFS endpoint, which needs to match the chosen client credentials and security mode; when using UserNameWSTrustBinding and sending the username and password in the WCF message secured using SSL (i.e. mixed), the URL should be like "http://adfs.pzl/adfs/services/trust/13/usernamemixed/" including the important ending / slash to avoid "405 method not allowed" error from IIS.

protected void btnLogin_Click(object sender, EventArgs e)
{
    // authenticate with WS-Trust endpoint
    var factory = new WSTrustChannelFactory(
        new UserNameWSTrustBinding(SecurityMode.TransportWithMessageCredential),
        new EndpointAddress(
"https://adfs.puzzlepart.com/adfs/services/trust/13/usernamemixed/"));
 
 
    factory.Credentials.UserName.UserName = txtUserName.Text;
    factory.Credentials.UserName.Password = txtPassword.Text;
 
    var channel = factory.CreateChannel();
 
    var rst = new RequestSecurityToken
    {
        RequestType = RequestTypes.Issue,
        AppliesTo = new EndpointAddress("urn:sharepoint:puzzlepart"),
        KeyType = KeyTypes.Bearer
    };
 
    var genericToken = channel.Issue(rst) as GenericXmlSecurityToken;
 
 
    // parse token
    var handlers = FederatedAuthentication.ServiceConfiguration
.SecurityTokenHandlers;
    var token = handlers.ReadToken(new XmlTextReader(
       new StringReader(genericToken.TokenXml.OuterXml)));
         
    SPSecurity.RunWithElevatedPrivileges(delegate(){
        SPFederationAuthenticationModule.Current
.SetPrincipalAndWriteSessionToken(token);
    });
    Response.Redirect("~/pages/default.aspx");
}

After getting authenticated against the ADFS STS when calling Issue on the WS-Trust channel with a RequestSecurityToken, the returned SAML security token must first be parsed and then written to a FedAuth cookie created from the SAML token. The SharePoint FAM wrapper will both set the thread principal and write the cookie, making the user a logged in SharePoint user.

Note how the writing of the cookie is wrapped with RunWithElevatedPrivileges to ensure that  it runs as the app-pool identity and not as the impersonated SharePoint user. This is to avoid the dreaded "CryptographicException: The system cannot find the file specified" error in the internal ProtectedDataCookieTransform call.

When calling ValidateToken you will run into the SecurityTokenException: Issuer of the Token is not a Trusted Issuer error if your STS is not trusted by SharePoint. SharePoint is configured to use its own SPPassiveIssuerNameRegistry and that will either validate against the built-in SharePoint STS or the set of trusted STS token issuers. See how to add your STS certificate(s) at SharePoint 2010 Claims-Based Auth with ADFS v2 by Eric Kraus. The trusted providers are apparently only used if the login page is located under the /_trust/ folder that is part of the above "redirect dance" when authenticating against a trusted identity provider.

Over at stack overflow, Matt Whetton had run into the same exception as us and solved it by replacing the passive <issuerNameRegistry> with the Windows Identity Foundation (WIF) ConfigurationBasedIssuerNameRegistry instead. The Configuration of WIF post shows how to add the set of certificate names and thumbprints to the <trustedIssuers> list. This is how your web.config list of trusted STS token issuers may look like:

<issuerNameRegistry type="Microsoft.IdentityModel.Tokens.ConfigurationBasedIssuerNameRegistry, 
Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"> 
  <trustedIssuers> 
    <add thumbprint="1337133713371337" name="CN=adfs-puzzlepart" /> 
    <add thumbprint="0000000000000000" name="CN=SharePoint Security Token Service" />
  </trustedIssuers> 
</issuerNameRegistry> 

Remember to add the SharePoint self-issued certificates such as the "SharePoint Security Token Service" certificate to the list of trusted issuers, in addition to your own STS.

I strongly recommend putting your custom ADFS login page under /_trust/ to avoid having to change the SharePoint web.config files. We chose this approach to minimize risks.

Note that Fiddler seems to break the ADFS login process, at least when decrypting SSL. The customized rule provided in Fiddler and Channel Binding Tokens Revisited by Eric Lawrence alleviates this problem. Just make sure you click "remember my credentials" when logging in so that Fiddler can get it from the Windows Credential Manager.

Disclaimer: Note that even if things seems to work as normal after this configuration change, there is no guarantee that nothing was affected in the huge platform that SharePoint 2010 is. The combination of SP2010 claims and WIF is not very well documented, and any changes beyond supported configuration involves risks. Do not apply these changes if you are not sure that it will not break any of your SharePoint solutions or services.

Wednesday, October 01, 2008

WCF: WS-Security using Kerberos over SSL

WCF support typical scenarios with the system provided bindings such as basicHttpBinding and wsHttpBinding. These bindings work very well in WCF-only environments and for interop scenarios where all parties are completely WS* standard compliant.

However, interop always seems to need some tweaking, and the ootb bindings allow you to change many aspects of the binding configuration. But there will always be scenarios where these bindings just cannot be configured to fit your needs. E.g. if you need a binding that supports custom wsHttp over SOAP1.1 using a non-negotiated/direct Kerberos WS-Security token secured by TLS. In this case you need a custom binding, because e.g. even if you can turn of spnego for wsHttpBinding, you cannot tweak it into using SOAP1.1

WCF <customBinding> to the rescue:

<customBinding>
<binding name="wsBindingCustom">
<security authenticationMode="KerberosOverTransport"

requireDerivedKeys="false"
includeTimestamp="true">
</security>
<textMessageEncoding messageVersion="Soap11" />
<httpsTransport />
</binding>
</customBinding>


This custom binding combines HTTPS transport with direct Kerberos.
The timestamp is required for security reasons when using authentication mode Kerberos over SSL. Note that using the UPN/SPN type <identity> element is only supported NTLM or negotiated security, and cannot be used with direct Kerberos.

WCF by default requires that the response always contain a timestamp when it is in the request. This is a typical interop issue with e.g. WSS4J, Spring-WS and WebSphere DataPower.

The response with the signed security header timestamp should be like this:
<soapenv:Header>
<wsse:Security soapenv:mustUnderstand="1" xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
<wsu:Timestamp wsu:Id="uuid-c9f9cf30-2685-4090-911b-785d59718267" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
<wsu:Created>2008-09-29T12:02:59Z</wsu:Created>
<wsu:Expires>2008-09-29T12:12:59Z</wsu:Expires>
</wsu:Timestamp>



Note the casing of the wsse: and wsu: namespace elements and attributes. This replay attack detection timestamp can be turned off using the includeTimestamp attribute, or it can be configured using the <localClientSettings> and <localServiceSetting> security elements.

Refer to the <security> element of the <customBinding> documentation for more details. Also review the settings for initiating a WS-SecureConversation if a session is needed.

Monday, March 03, 2008

WCF: WS-Security, SSL, TransportWithMessageCredentials, Message Logging

I'm currently on a project where we're using WCF to interop with services provided by Spring-WS hosted on WebSphere. The basic WS-Security UsernameToken credential type over a basicHttpBinding with SSL was chosen for authentication.

The first problem we got was that the self-signed SSL certificate was not accepted by SvcUtil.exe or "Add service reference":

The remote certificate is invalid according to the validation procedure. Could not establish trust relationship for the SSL/TLS secure channel. The remote certificate is invalid according to the validation procedure.

Sure enough, opening the URL in MSIE showed two certificate errors: issuer trust and certificate common name/site name mismatch. The first issue was easily solved, the latter was not. However, a workaround is to save the WSDL to a file and use that file as the input to SvcUtil.exe. Of course, the same invalid SSL certificate will come back and bite you when trying to use the proxy - more on this later.

I took the generated the proxy code and config output and merged it into my code. Unsurprisingly, the unit test failed with the message "no WS-Security headers found in request". I turned on message logging on both service and transport level using SvcConfigEditor.exe, remembering to set LogEntireMessage to true. Sure enough, no wsse:UsernameToken element in the soap:Header. Grief...

A lot of research lead me to the knowledge that WCF will by design not allow any sensitive data to be sent on an unsecure channel. Time to look at the generated output.config. After a few hours of frustrated config tuning and log inspections, the issue was solved: SvcUtil.exe generates config that combines Username credentials with Transport mode security. This looks OK and gives no config validation exception, but it is absolutely not correct. This is the correct config for using WS-Security over basicHttpBinding and SSL:

<security mode="TransportWithMessageCredential">
<transport clientCredentialType="None" proxyCredentialType="None"realm="" />
<message clientCredentialType="UserName" algorithmSuite="Default" />
</security>

WCF will not put any wsse: headers into the request unless TransportWithMessageCredential is used as the security mode. In addition, add the username and password using the .Username and .Password properties of the proxy.ClientCredentials.UserName object.

After a while it occurred to me that the Spring-WS generated WSDL contained no WS-Policy statements, thus there was no guidance in the WSDL for creating the security artifacts for the proxy and config.
Now my request went through to WebSphere! But, how long was I in paradise? Until the response came back:

System.ServiceModel.Security.MessageSecurityException: Security processor was unable to find a security header in the message. This might be because the message is an unsecured fault or because there is a binding mismatch between the communicating parties. This can occur if the service is configured for security and the client is not using security.

The problem is that WCF expects a timestamp in the response when it puts a wsse: timestamp in the request (see the transport level message log). I have not been able to turn the timestamp off using config for <basicHttpBinding>, but it can be done using a <customBinding> (see Kristian Kristensen's blog). Nicholas Allen provides code and a deeper explanation of the timestamp mechanism in his blog:

BindingElementCollection elements = proxy.Endpoint.Binding.CreateBindingElements();
elements.Find<SecurityBindingElement>().IncludeTimestamp = false;
proxy.Endpoint.Binding = new CustomBinding(elements);

While you wait for the WebSphere folks to install a valid certificate, you can intercept the certificate validation and override any SSL policy violations:

using System.Net;
using System.Security.Cryptography.X509Certificates;
using System.Net.Security;...
ServicePointManager.ServerCertificateValidationCallback = 
delegate(object sender, X509Certificate certificate, X509Chain chain, 
         SslPolicyErrors sslPolicyErrors)
{
//http://msdn2.microsoft.com/en-us/library/system.net.security.remotecertificatevalidationcallback.aspx
//ignore invalid SSL: allow this client to communicate with unauthenticated serversreturn true;
}

At last my WCF client is able to invoke the web-service providing the expected WS-Security wsse:UsernameToken. What a wonderful interop world it is using WS-*.

Articles by IBM: Achieving Web services interoperability between the WebSphere Web Services Feature Pack and Windows Communication Foundation Part 1 and Part 2.

Sunday, October 21, 2007

BizTalk Services SDK, RelayBinding: Firewall Issue

I recommend that you download and try the samples of the BizTalk Services SDK (identity & access control, connectivity, web-style stuff) to get a deeper understanding of what the "internet service bus" concept provides to service-oriented solutions - both for the SaaS approach and for the Software+Service approach.

An error that you might run into when trying any of the connectivity samples is the "No DNS entries exist for host connect.biztalk.net" exception when calling host.Open() from e.g. the EchoSample server. You'll get a really lost feeling when googling for this error gives no hits:

System.ServiceModel.EndpointNotFoundException was unhandled
Message="No DNS entries exist for host connect.biztalk.net."
Source="System.ServiceBus"

StackTrace: at System.ServiceBus.RelayedOnewayClient.Connect()

The cause of this problem is that the RelayBinding uses an outbound TCP connection to connect to sb://connect.biztalk.net/ to set up your hosted endpoint; and if you're behind a firewall, this will fail unless permitted by the firewall. Refer to the ReadMe file of the SDK for more details.

Wednesday, February 07, 2007

WCF Streaming: Upload files over HTTP

We have a web application that requires a robust and reliable document upload mechanism for very large files, and are currently using Microsoft BITS technology to achieve this. With the introduction of WCF in the solution, I set out to implement an upload service prototype using WCF and a HTTP binding.

You have two main options for uploading files in WCF: streamed or buffered/chunked. The latter option is the one that provides reliable data transfer as you can use wsHttpBinding with WS-ReliableMessaging turned on. Reliable messaging cannot be used with streaming as the WS-RM mechanism requires processing the data as a unity to apply checksums, etc. Processing a large file this way would require a huge buffer and thus a lot of available memory on both client and server; denial-of-service comes to mind. The workaround for this is chunking: splitting the file into e.g. 64KB fragments and using reliable, ordered messaging to transfer the data.

If you do not need the robustness of reliable messaging, streaming lets you transfer large amount of data using small message buffers without the overhead of implementing chuncking. Streaming over HTTP requires you to use basicHttpBinding; thus you will need SSL to encrypt the transferred data. Buffered transfer, on the other hand, can use wsHttpBinding, which by default provides integrity and confidentiality for your messages; thus there is no need for SSL then.

Read more about 'Large Data and Streaming' at MSDN.

So I implemented my upload prototype following the 'How to: Enable Streaming' guidelines, using a VS2005 web application (Cassini) as the service host. I made my operation a OneWay=true void operation with some SOAP headers to provide meta-data:

[OperationContract(Action = "UploadFile", IsOneWay = true)]
void UploadFile(ServiceContracts.FileUploadMessage request);
[MessageContract]
public class FileUploadMessage
{
[MessageHeader(MustUnderstand = true)]
public DataContracts.DnvsDnvxSession DnvxSession
[MessageHeader(MustUnderstand = true)]
public DataContracts.EApprovalContext Context
[MessageHeader(MustUnderstand = true)]
public DataContracts.FileMetaData FileMetaData
[MessageBodyMember(Order = 1)]
public System.IO.Stream FileByteStream
}

The reason for using message headers for meta-data is that WCF requires that the stream object is the only item in the message body for a streamed operation. Headers are the recommended way for sending meta-data when streaming. Note that headers are always sent before the body, thus you can depend on receving the header data before processing the streamed data.

Note that you should provide a separate endpoint (address, binding, contract) for your streamed services. The main reason for this is that configuration such as transferMode = "Streamed" applies to all operations in the endpoint. The same goes for transferMode, maxBufferSize, maxReceivedMessageSize, receiveTimeout, etc.

I use MTOM as the encoding format for the streamed data and set the max file size to 64MB. I also set the buffer size to 64KB even if I read the input stream in 4KB chucks, this to avoid receive buffer underruns. My binding config looks like this:

<basicHttpBinding>
<!-- buffer: 64KB; max size: 64MB -->
<binding name="FileTransferServicesBinding"
closeTimeout="00:01:00" openTimeout="00:01:00" 
receiveTimeout="00:10:00" sendTimeout="00:01:00"
transferMode="Streamed" messageEncoding="Mtom"
maxBufferSize="65536" maxReceivedMessageSize="67108864">
<security mode="None">
<transport clientCredentialType="None"/>
</security>
</binding>
</basicHttpBinding>

I have set the transport security to none as I am testing the prototype without using SSL. I have not modified the service behavior; the default authentication (credentials) and authorization behaviors of the binding is used.

Note that the setting for transferMode does not propagate to clients when using a HTTP binding. You must manually edit the client config file to set transferMode = "Streamed" after using 'Add service reference' or running SVCUTIL.EXE. If you forget to do this, the transfer mode will be "Buffered" and you will get an error like this:

System.ServiceModel.CommunicationException: An error occurred while receiving the HTTP response to http://localhost:1508/Host/FileTransferService.svc. This could be due to the service endpoint binding not using the HTTP protocol. This could also be due to an HTTP request context being aborted by the server (possibly due to the service shutting down). See server logs for more details.



After ensuring that the client basicHttpBinding config was correct, I ran the unit test once more. Now I got this error from VS2005 Cassini:

System.ServiceModel.ProtocolException: The remote server returned an unexpected response: (400) Bad Request.
System.Net.WebException: The remote server returned an error: (400) Bad Request.



I have inspected the HTTP traffic using Fiddler and can see nothing wrong with the MTOM encoded request. Googling lead me to a lot of frustrated "streamed" entries at the forums.microsoft.com "Indigo" group, but no solution to my problem. So I made a self-hosted service using a console application, using the code provided in the WCF samples (see below), and it worked like a charm. I expect that it is just VS2005 Cassini that does not support streamed transfers/MTOM encoding.

I then installed IIS to check whether HTTP streaming works better with IIS. The first error I ran into when uploading my 1KB test file was this:

System.ServiceModel.CommunicationException: The underlying connection was closed: The connection was closed unexpectedly.
System.Net.WebException: The underlying connection was closed: The connection was closed unexpectedly.



Inspecting the traffic with Fiddler, I found the HTTP request to be fine, but no HTTP response. Knowing that this is a sure sign of a server-side exception, I debugged the service operation. The error was caused by the IIS application pool identity (ASPNET worker process) lacking NTFS rights to store the uploaded file on disk. Note that WCF by default will not impersonate the caller, thus the identity used to run your service will need to have sufficient permissions on all resources that your service needs to access. This includes the temp folder, which is used to provide the WSDL file for HTTP GET.

As the UploadFile operation has [OperationContract(IsOneWay = true)], it cannot have a response and neither a fault contract. Thus, there will be no HTTP response when a server-side exception occurs, just the famous "
The underlying connection was closed" message.

I assigned the correct NTFS rights to my upload folder and re-ran the unit test: streamed upload to a WCF service hosted by IIS worked. The next unit test used a 43MB file to check if uploading data larger than the WCF buffer size works. The test gave me this error:



System.ServiceModel.CommunicationException: The socket connection was aborted. This could be caused by an error processing your message or a receive timeout being exceeded by the remote host, or an underlying network resource issue. Local socket timeout was '00:00:59.8590000'.System.IO.IOException: Unable to write data to the transport connection: An existing connection was forcibly closed by the remote host.
System.Net.Sockets.SocketException: An existing connection was forcibly closed by the remote host



I was a bit puzzled as the file was less than the WCF maxReceivedMessageSize limit set to 64MB, and the stream read buffer was only 4KB as compared to the WCF maxBufferSize being 64KB. Knowing that IIS/ASP.NET has a system enforced limit on the size of a HTTP request to prevent denial-of-service attacks, which seems to be unrelated to WCF as I do not use the AspNetCompatibilityRequirements service setting, I decided to set the maxRequestLength limit to 64MB also (default: 4096KB). This time the "stream large file upload" test passed!

So for WCF streaming to IIS to work properly, you need this <system.web> config in addition to the <system.serviceModel> config in the IIS web.config file:


<!-- maxRequestLength (in KB) max size: 2048MB -->
<httpRuntime 
maxRequestLength="65536"
/>

Make the maximum HTTP request size equal to the WCF maximum request size.

I have to recommend the 'Service Trace Viewer' (SvcTraceViewer.EXE) and the 'Service Configuration Editor' (SvcConfigEditor.EXE) provided in the WCF toolbox (download .NET3 SDK). These tools make it really simple to add tracing and logging to your service for debugging and diagnosing errors. Start the WCF config editor, open your WCF config file, select the 'Diagnostics' node and just click "Enable tracing" in the "Tracing" section to turn on tracing. The figure shows some typical settings (click to enlarge):




Save the config file and run the unit test to invoke the service. This will generate the web_tracelog.svclog file to use as input for the trace viewer. Open the trace file in the trace viewer and look for the errors (red text). Click an error to see the exception details. The figure shows some of the details available for the "Maximum request length exceeded" error (click to enlarge):


The WCF tools installed by the SDK is located here:
C:\Program Files\Microsoft SDKs\Windows\v6.0\Bin\

The code to process the incoming stream is rather simple, storing the uploaded files to a single folder on disk and overwriting any existing files:

public void UploadFile(FileUploadMessage request)
{
FileStream targetStream = null;
Stream sourceStream = request.FileByteStream;
 
string uploadFolder = @"C:\work\temp\";
string filename = request.FileMetaData.Filename;
string filePath = Path.Combine(uploadFolder, filename);
 
using (targetStream = new FileStream(filePath, FileMode.Create, 
                          FileAccess.Write, FileShare.None))
{
//read from the input stream in 4K chunks
//and save to output stream
const int bufferLen = 4096;
byte[] buffer = new byte[bufferLen];
int count = 0;
while ((count = sourceStream.Read(buffer, 0, bufferLen)) > 0)
{
targetStream.Write(buffer, 0, count);
}
targetStream.Close();
sourceStream.Close();
}
}

The 'Stream Sample' available on MSDN contains all the code you need to upload a file as a stream to a self-hosted WCF service and then save it to disk on the server by reading the stream in 4KB chunks. The download contains about 150 solutions with more than 4800 files, so there is a lot of stuff in it. Absolutely worth a look. The streaming sample is located here:
<unzip folder> \TechnologySamples\Basic\Contract\Service\Stream


Please let me know if you have been able to get HTTP streaming with MTOM encoding to work with Microsoft VS2005 Cassini.
Finally, a useful tip for streaming download: How to release streams and files using Disponse() on the the message contract.

Thursday, January 25, 2007

WCF Security: wsHttpBinding with brokered Kerberos

Now that I have implemented a separate ASMX endpoint for interoperability with Flex 2, it was time to switch from basicHttpBinding to wsHttpBinding for better security (authentication, encryption, signing). Using this binding will provide message level security, preventing e.g. HTTP spies on the client from snooping the data exchange with our services.

Editing the WCF client and server configuration files is a quite daunting task, but luckily the
new version of WSSF offers nice wizards for configuring security for your services. The 'WCF security' menu allows you to easily add support for the most common providers: client X.509 certificates, Kerberos, ADAM, SQL Server, ActiveDirectory, server certificate.


Among the available providers, the Kerberos provider is the simplest to use if you don't want to use a certificate nor HTTPS/SSL, or you want/has to use Cassini (the VS2005 developer web-server), as shown in the figure (click to enlarge):


The wizard is straightforward, just remember to include metadata (MEX) in your service if you want to expose a WSDL file. Adding a list of authorized clients is not mandatory for this security mode. I recommend that you delete all content in the <system.serviceModel> element in the service config file before using the 'WCF Security' menu. This ensures a clean configuration settings section afterwards.

I then updated the service reference of our SmartClient application to get the correct settings for the client config file. This step is important, as the common configuration settings must be equal on both client and server for WCF communication to work.

With all the configuration finished, it was time for some testing. I got this error when invoking a WCF operation:

System.ServiceModel.Security.MessageSecurityException: The token provider cannot get tokens for target 'http://localhost.:1508/eApproval.Host/ProjectDocumentServices.svc'. ---> System.IdentityModel.Tokens.SecurityTokenValidationException: The NetworkCredentials provided were unable to create a Kerberos credential, see inner execption for details. ---> System.IdentityModel.Tokens.SecurityTokenException: InitializeSecurityContent failed. Ensure the service principal name is correct. ---> System.ComponentModel.Win32Exception: No credentials are available in the security package
--- End of inner exception stack trace ---


My client config file contained these security settings, which to me looked fine and dandy:

<security mode="Message">
<transport clientCredentialType="Windows" proxyCredentialType="None" realm="" />
<message clientCredentialType="Windows" negotiateServiceCredential="false" algorithmSuite="Default" establishSecurityContext="false" />
</security>

My suspects were the extra <message> element attributes not covered in Keith Brown's "Security in WCF" article: negotiateServiceCredential and establishSecurityContext. As usual with new Microsoft technology, the online help is rather lame, so some research was required.

Googling lead me to this excellent article by Michèle Leroux Bustamante: "Fundamentals of WCF Security" (6 pages). The 'Service Credentials and Negotiation' section contained the explanation for my error: "When negotiation is disabled for Windows client credentials, a Kerberos domain must exist". So by turning service identity negotiation on in both the client and the server config files, my client can now communicate securely with our WCF service. I also recommend turning on the 'secure session' context for better performance, unless your service is rarely used by the client.

A confusing part of the client side authentication settings for the 'Windows' security mode is the <endpoint/identity> element, which will typically contain your "user principal name" (UPN) in the <userPrincipalName> element when hosting with Cassini or self-hosting. The identity setting is not for specifying who the client user is, but for identityfying and authenticating the service itself. If you use IIS as the host, the identity must contain the "service principal name" (SPN). Note that if your IIS app pool identity is not NETWORK SERVICE, you must typically create the SPN explicitly.

The <identity> element is only used with NTLM or negotiated security. If you use non-negotiated security such as direct Kerberos, this element is not supported.

The security config now looks like this (server side settings):

<bindings><wsHttpBinding>

<binding name="BrokeredAuthenticationKerberosBinding">
<security mode="Message">
<message clientCredentialType="Windows" negotiateServiceCredential="true" establishSecurityContext="true" />

</security>
</binding>
</wsHttpBinding>
</bindings>

Checking the on-the-wire format with Fiddler shows that the request and response messages now are encrypted; without using HTTPS or SSL transport level security:

The system provided bindings and the WSSF wizards do a good job in guiding you in the WCF settings jungle; but when your automated guide gets lost, you'd better have some survival skills. Knowing the inner workings of WCF is a must for professional service developers, and I strongly recommend reading
Michèle's article. The same goes for her blogs and the WCF stuff at IDesign.

Fiddler is highly recommended and can be downloaded from http://www.fiddlertool.com/fiddler/.

Friday, December 22, 2006

How to: Expose WCF service also as ASMX web-service

In my last post, I wrote about my endeavors to consume a WCF service from an ASMX client to support non-WCF clients such as .NET 2 clients. The WCF service is exposed using a BasicHttpBinding endpoint and our main consumer is a Flex 2 web-application.

As the support for WS-I Basic Profile 1.x compliant web-services is quite limited in Flex, I had to make ASMX-style clients works. What made it worse, is that there is a bug in Flex regarding the support for xsd:import: Flex will repeatedly request the XSD files on .LoadWSDL(), causing bad performance for the web-service and the client. Use Fiddler to watch all the HTTP GETs issued by Flex. The solution is to ensure that the generated WSDL is a single file, without any xsd:import, and this is just what a classic .NET 2 ASMX web-service provides.

WCF has good support for migrating ASMX web-services to WCF, and this feature can also be used to actually expose a native WCF service also as an ASMX web-service. Start by adding the classic [WebService] and [WebMethod] attributes to your service interface:

[ServiceContractAttribute(Namespace = "http://kjellsj.blogspot.com/2006/11"
Name = "IProjectDocumentServices")]
[ServiceKnownTypeAttribute(
typeof(DNVS.DNVX.eApproval.FaultContracts.DefaultFaultContract))]
[WebService(Name = "ProjectDocumentServices")]
[WebServiceBinding(Name = "ProjectDocumentServices"
ConformsTo = WsiProfiles.BasicProfile1_1, EmitConformanceClaims = true)]
public interface IProjectDocumentServices
{
[OperationContractAttribute(Action = "GetDocumentCard")]
[FaultContractAttribute(
typeof(DNVS.DNVX.eApproval.FaultContracts.DefaultFaultContract))]
[WebMethod]
DocumentCardResponse GetDocumentCard(KeyCriteriaMessage request);
. . .

Note that there is no need for adding [XmlSerializerFormat] to the [ServiceContract] attribute, just leave the WCF service as-is. Note also that you must specify the web-service binding Name parameter to avoid getting duplicate wsdl:port elements in the generated WSDL file. You should apply the [WebService] attribute using the same Name and Namespace parameters as for the [WebServiceBinding], to the class implementing the service. If not, you might get a wsdl:include and and xsd:import in the generated WSDL file.

Then add a new .ASMX file to the IIS host used for your WCF service, using the service implementation assembly to provide the web-services. I added a file called "ProjectDocumentWebService.asmx" with this content:


<%@ WebService Language="C#" Class="DNVS.DNVX.eApproval.ServiceImplementation
.ProjectDocumentServices, DNVS.DNVX.eApproval.ServiceImplementation" %>

That's it. You now have a classic ASMX web-service at your disposal. WCF supports running WCF services and ASMX web-services side-by-side in the same IIS web-application: Hosting WCF Side-by-Side with ASP.NET.

Most likely you must also apply [XmlElement], [XmlArray] and [XmlIgnore] at relevant places in your message and data contracts to ensure correct serialization (XmlSerializer) and ensure consistent naming for both WCF and ASMX clients/proxies. Remember that the XmlSerializer works only with public members and that it does not support read-only properties. Do not mark message and data contract classes as [Serializable] as this changes the wire-format of the XML, which might cause problems in Flex 2.Note that the ASMX-style WSDL will not respect the [DataMember (IsRequired=true)] setting or any other WCF settings because it uses the XmlSerializer and not the DataContractSerializer. You will get minOccurs=0 in the generated WSDL for string fields, as string is a reference type. This is because the XmlSerializer by default makes reference types optional, while value types by default are mandatory. Read more about XmlSerializer 'MinOccurs Attribute Binding Support' at MSDN.

Note the difference between optional elements (minOccurs) and non-required data (nillable) in XSD schemas. This is especially useful in query specification objects, make all the elements optional and apply only the specified elements as criteria - even "is null" criteria.


The unsolved WSDL problem described in my last post is no longer an issue for ASMX-clients, as the WSDL generated by the classic ASMX web-service of course natively supports .NET 2 proxies generated by WSDL.EXE. The issue with the Name parameter for the [CollectionDataContract] attribute is still a problem, it must be removed.

Thanks to Mattavi for the "Migrating .NET Remoting to WCF (and even ASMX!)" article that got me started.

Friday, February 24, 2006

DataGridView - SelectionChanged escapade

We use the new .NET 2.0 DataGridView extensively in my current project, in combination with object bindingsources. We have several user-controls where the grid is the position/current source for other controls in the UC, e.g. master-details grids, selected grid-row edit form, etc. All of our grids are in single select/FullRowSelect mode. Thus, the when the selected row changes, so do all the controls bound to the grid (actually bound to the same bindingsource as the grid).

The SelectionChanged event of the DataGridView tells you when the selected row changes, and this event trigger quite often, in fact more often than you would expect. It triggers during:

  • binding: the first row always gets selected
  • unloading: the selections are cleared
  • resize: the selections are first cleared, then all of them gets selected again
  • sorting: the selections are first cleared, then the grid selects whatever rows got the same row index in the grid, which most likely are not the same records that were selected before sorting
Typically, a user-control gets instantiated and initialized by its parent, and then the parent does .Dock=Fill on the UC. Thus, the the SelectionChanged event will trigger three times during load: when a user-controls gets loaded, the DataGridView gets bound and the first row gets selected (first), then the resize event causes unselecting the row (second) and then the reselection og the first row (third).

We do layz loading of the details data, i.e. we do not access the application services to get the detail data until the user selects a row in the grid. I noted that the UC took a long time to load, and when I added a breakpoint to see why, I was quite surprised when the data got loaded three times during load. The extra load was caused by me setting a selected row after binding, as this had to be done in .NET 1.1 grids from Microsoft, but is no longer needed as the DataGridView always select the first row. I wish there was an option on the grid to turn the auto-select off.

Thus, what is needed is some extra logic to remember which record is the current in the binding source (note: not which row in the grid), then check in the SelectionChanged event whether the current record actually was changed, or whether it is just the eager event triggering. Use a helper class member to keep the ID of the selected record. Do your data loading only when the current record changes, otherwise, just rebind to the already fetched data.

The next (expected) surprise I got was when I added sorting to the binding source of the DataGridView. The same effect happended as during the load resizing: the selections got cleared and then set again, but it does not select the same records again, it only selects the same rows as before. As the records are now sorted a different way, the record previously at row 4 is now e.g. at row 17, but the grid "dutifully" selects row 4 anyway. Thus, you need to implement some find logic, or just call .ClearSelection() on the grid.

I chose to clear the selection after sorting, and wanted to suppress the reselect SelectionChanged event that happens during sorting, as this caused unnecessary binding to random record which I would immediately clear. The grid has a Sorted event that happens when the sorting has completed and after the selection events, but there is no begin sorting event. I needed another helper class member to tell the SelectionChanged event hander that the grid is currently sorting. I thought the ColumnHeaderMouseClick event would let med set an _isSorting boolean flag, but this event happens after the Sorted event, far to late for my use.

I ended up using the MouseDown event and some classical x,y hit testing to deduce that a sort operation was about to begin:

Private Sub dgwMaster_MouseDown(ByVal sender As System.Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles dgwMaster.MouseDown

Dim hti As DataGridView.HitTestInfo = dgwMaster.HitTest(e.X, e.Y)
If hti.Type = DataGridViewHitTestType.ColumnHeader Then
_isSorting = True
End If

End Sub


Private Sub dgwMaster_Sorted(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles dgwMaster.Sorted

_isSorting = False

End Sub

The final surprise when it comes to binding is about the impedance difference between object binding sources and the DataGridView: a DataGridView can have no selection (just CTRL+click a selected row), while an object bindingsource always have a .Current item when the source is not empty. Setting .Postion to -1 on a bindingsource has no other effect that selecting the first item in the binding source. Thus, to allow for no selection in bound controls, you have to remove the .DataSource of those controls temporarily when the grid have no selection.
I wish there was an option to enforce the grid to always have a selection. You can make a dervied grid and override MouseDown and then not call the base class OnMouseDown method to cancel the mouse click.

Due to this impedance difference, always remember to check that a row is selected in the grid before using the bindingsource .Current property to get the currently selected record:

If _isSorting = True Then Exit Sub


'ensure that a row is selected

If dgwMaster.SelectedRows.Count = 0 Then

dgwDetails.DataSource = Nothing

Else

Dim response As OrderDetailsResponse

'get details

Dim master As Order = CType(OrderBindingSource.Current, Order)

If master.orderId = _lastOrderId Then

'avoid reget on resize, just reconnect bindingsource

dgwDetails.DataSource = OrderOverføringBindingSource

Else

_lastOrderId = master.orderId

response = _orderMgr.GetOrderDetails(master.orderId)

_details = response.ItemList

If dgwDetails.DataSource Is Nothing Then

'sorting changes current master, need to reconnect bindingsource

dgwDetails.DataSource = OrderDetailsBindingSource

End If

End If

End If


No wonder that the DataGridView program manager Mark Rideout is quite busy answering questions at the Windows Form DataBinding forum.

Thursday, February 02, 2006

VS2005 inherited user control, abstract factory/dependency injection pattern

Yesterday the need arised for inheriting a WinForms user control to implement four specializations of a user control. Creating inherited user controls is very simple in Visual Studio, just click 'Add-New item' and select 'Inherited user control', name the new user control appropiately and click 'Add'. I then selected the user control to derive from (note that the class must be plain public, while the constructor can be protected) and clicked OK.

VS2005 dutifully added the new user control files, but the VS designer failed with this error message:


One or more errors encountered while loading the designer. The errors are listed below. Some errors can be fixed by rebuilding your project, while others may require code changes.

Object reference not set to an instance of an object.

at System.Reflection.Assembly.nLoad(AssemblyName fileName, String codeBase, Evidence assemblySecurity, Assembly locationHint, StackCrawlMark& stackMark, Boolean throwOnFileNotFound, Boolean forIntrospection)
. . .
at Microsoft.VisualStudio.Design.Serialization.CodeDom. VSCodeDomDesignerLoader.DeferredLoadHandler.Microsoft.VisualStudio. TextManager.Interop.IVsTextBufferDataEvents.OnLoadCompleted(Int32 fReload)


Some googling did provided me with only a few similar problems, but none of the suggested solutions worked for me. Even adding two new user controls with no code or children would fail when trying to inherit one from the other.

The designer always calls the user control's parameterless constructor New and the control's Load event handler (provided that they exists), thus these methods should not do any operations that depend on some run-time resources.

I was quite puzzled when even added the two new plain user controls failed, as they of course had no constructor or load event handler. Then I noted that I could no longer open any user controls or forms in the whole solution. The VS designer had gone into a zombie state. A restart of Visual Studio, plus clearing the TFS workspace cache, corrected the VS designer problem for all the forms and the non-inherited user controls. Before I even tried to add a derived user control again, I did the recommended rebuild (see error message above) to ensure that coding errors did not cause the problem.

This time I added a standard user control and manually added the Inherits statement to the class code file and the designer partial class file. Then I opened the user control in designer mode, and I still got the famous designer error page, but this time with useful error details pointing me to a specific line in the base class.

The offending line was a private class member that created a biz-logic component using the abstract factory pattern:

Public Class GenericUCBase
. . .
Private _bizManager As IBizManager = ObjectFactory.BizManager()
. . .

Protected Sub New()

' This call is required by the Windows Form Designer.
InitializeComponent()
End Sub
. . .

The factory uses a Public Shared (static) method that creates objects using the dependency injection pattern (interface based) from pre-compiled assemblies deployed to the VS project location. It was the Type.GetType(typeName as String) method that failed when called from the private member initialization. I don't know why the VS designer failed to locate my assemblies during initialization, VS has no problems with non-derived controls, or with running or debugging the project.

All class members that are "declare initialized" gets called when the designer loads a user control, along with the constructor and the load event handler. Knowing this, I moved the initialization of the biz-logic component to my InitControl method, and this solved the VS designer problem.

The TFS workspace cache is located here:

\Documents and Settings\%user%\Local Settings\Application Data\Microsoft\Team Foundation\1.0\Cache

I clear it whenever I have problems with VS or TFVC, typically when the 'pending check-ins' list contains bogus entries from other solutions that cannot be removed in any other way.

Wednesday, December 21, 2005

VS2005 Design-time set DataSource error III

Design-time data-binding in .NET 2.0 WinForms is a real productivity booster that I use extensively. I have, however, had some problems with using the new object binding source mechanism (error I, error II) and today yet another problem. I began designing a new user-control a few days ago, and being a lazy developer, I copied some data bound comboboxes that I needed from another user-control.

I should have know better, copying controls was a recipy for immediate disaster in the VS2005 betas; you could be sure that the Visual Studio designer would fail when you later on re-opened the user-control. The same applied to renaming a control after modifying some of its properties. These problems have been fixed in the release version of VS, although opening a form still randomly causes the cursor to go into a blink frenzy for quite some time while re-syncing with the .Designer file.

Copying those combos kept some of the data binding properties (ValueMember, DisplayMember), but did not copy the binding sources (more on this below). I added the object binding sources in the 'Data Sources' explorer, and after some refreshing of the project data sources (error II), object binding seemed to work properly.

Today, I added another object binding source to the 'Data Sources' explorer of the user-control. If I then tried to use the DataSource property at design-time, I got this error:

Object reference not set to an instance of an object

Setting the data binding at run-time work fine. So does copying the original data source binding code from the hidden .Designer file, it even shows correctly in the control properties explorer, but cannot be changed due to the above error. But this is not how I want to do it, I wanted design-time binding to work. Any attempt to modify data binding on any controls in the form would cause the design-time error.

In an attempt to fix the binding problems, I used an old trick when working with software and computers; I restarted Visual Studio, opened the solution and cleaned it. Then I clicked the DataSource property of my failing combo. Now I got this error, which anyone who have parsed XML will recognize:

Root element is missing

After Visual Studio had shown this puzzling error (as usual no intelligble info provided in the message), all design-time data binding worked properly again and the combos were filled at run-time. Oh joy! . . . But the object binding source that I had added lastly, was gone from the 'Data Sources' explorer. As long as the design-time binding kind of worked again, I could live with that.

I suspected that there was a problem with one XML files in the solution and checked the various .RESX files and some other files with XML content, but I was not been able to find any error. When I had just about given up, I noticed that the three listed data sources in the 'Data Sources' explorer also happened to be the three first .datasource files in the \My Project\DataSources\ folder (use 'Show all files' in the 'Solution Explorer'). Thus, I opened the fourth .datasource file, and voila, it was empty! Visual Studio clearly just stops looping through the data sources when it encounters an error.

I excluded the empty file from the project, reloaded the solution, and then all the project data sources were listed in the 'Data Sources' explorer. Design-time binding now works as expected again. Oh, even more joy!

I am still sceptical to copying object bound controls from one WinForms user-control to another. The VS designer might punish you. Maybe I am just being paraniod.

Monday, December 19, 2005

VS2005 Crash on binding to data source II

Today I got a problem with VSTS 8.0.50727.42 that caused Visual Studio to just die, sometimes even without opening the error reporting dialog. This happened when trying to bind a DataGridView to a newly added object data source by using the popup toolbox of the grid. VS would vanish when I clicked the 'Choose data source' combo. No error message.

As I have had problems with the VS2005 data source mechanism before (data source crash I), I have some experience in troubleshooting data sources. I stared out checking for invalid, zombie data sources in the \My Project\DataSources\ folder (VB.NET), and removed an old, unused data source. This did not solve the problem, but I prefer to diagnose a clean solution.

When I made the new business entity object that I was adding as the new binding source, I also refactored some of the other entity objects to remove some obsolete properties and changed some property names to reflect gained knowledge about the domain.

I found out that VS2005 is not fond of such changes in the assemblies used as object binding sources. It is however, quite easy to make the data sources reflect the changes:
  • Select the project in the solution explorer
  • Click the 'Show all files' button
  • Navigate to 'My Project' and expand it
  • Navigate to the 'DataSource' child node and expand it
  • Select each of the data sources in turn
  • Right-click the data source and select 'Refresh'
Note that there is no refresh option on the 'DataSources' node in solution explorer, you have to manually refresh every single data source in your solution.

After refreshing the data source definition cache as described, VS2005 no longer performs harakiri in response to refactoring objects used as data sources.

Saturday, November 19, 2005

Noogen.Validation - WinForms validation made easy

After doing mostly ASP.NET and SharePoint solutions, I was quite pleased with the validation mechanisms of ASP.NET. I was very surprised and disappointed when I moved to developing WinForms solutions, and had to downgrade from the ASP.NET validator and validation summary controls to the WinForms stuff.

Gone were the validators and I had to use an ErrorProvider on my forms, as if I need something to provide me with errors. The worst was the need for iterating recursively over all controls on a form when clicking OK to ensure that all error had been corrected and that validation events returned success, before e.g. calling my biz-logic to save changes. I just wanted to have my Page.IsValid property back.

At my current project we agreed that the standard validation mechanism was to awkward for us, and one of the developers did some research to find a component that would make WinForms validation as simple as WebForms validation. This lead us to Noogen.Validation at CodeProject, a control that we have been using for some time now.

I just love the simplicity and flexibility of the Noogen.Validation component, and I strongly recommend it. It is the best add-on component I have used since the Farpoint Spread control for VB6. Thanks, Noogen!

Friday, November 11, 2005

VS2005 Add new data source wizard crash

Today I had a strange problem with VSTS 8.0.50727.42: I could not add a new data source of type 'object' to my Windows control library project. Adding a database or web-service data source worked fine.

The wizard would crash when trying to open the 'Select the object you wish to bind to' page of the wizard. The error message was this:

An unexpected error has occured.
Error message: Object reference not set to an instance of an object.


If I made a new Windows control library project and added classes from my business entity assembly as object data sources, the wizard worked as expected (kind of stupid that several classes cannot be added in one go, though). After several hours of experimenting with project references, structure, and even names, I finally saw the pattern of when the wizard worked and when it crashed. The wizard is dependent on your project having at least one public class in the root folder of your project.

In my Windows control library project I have structured the source code into several folders with no classes in the root. VS by default generates a class called 'UserControl1' in the root, and if you delete this class, the wizard will fail.

I now use a dummy class 'XDummyClassForDataBinding' in my project.

Thursday, October 13, 2005

.NET2.0 ObjectDataSource and BindingList<T> VS typed datasets

I am currently at a project where some VB6 and .NET1.1 WinForms applications are to be ported and upgraded to .NET2.0. The current architecture is layered and employs (custom) business entity objects modelled on the domain of the customer. The solution is distributed over several tiers, and the communications technology of the architecure (.NET remoting) is also up for review. In addition, it is a design goal to enable the solution to provide services and entity data to external systems, both within the company and to e.g. partners.

Yesterday some of the developers started to argue for replacing the existing business entity objects with typed datasets. Their main reason for this was to get "maximum" developer productivity through two-way WinForms data binding in the user controls. This is a typical way of thinking for developers that have worked mainly with databases and ASP.NET solutions. In addition, scrapping the domain entity objects would be a step in the wrong direction from 'domain driven design' (DDD) and from 'service orientation' enabling the solution. This discussion is not a new one, refer to this MSDN article by Dino Esposito for pros & cons of datasets vs business entity objects. I also recommend reading the articles referenced at the end of the 'Cutting Edge' article. Note how they all agree on that exposing datasets in a service is a bad idea.

.NET2.0 provides several new mechanisms aimed at bringing the ease of dataset binding to business entity objects, allowing the developers to use design time tools and wizards to bind their GUI to entity objects. The ObjectDataSource (BindingSource) and the generic BindingList<T> are the main enabling object binding mechanisms in .NET2.0. Note that it is possible to provide object binding also in .NET1.1 as shown in this MDSN article by Paul Ballard, but it will require more coding and will not give full design time support for setting up the binding.

What you will find out is that the BindingList<T> has some 'last mile' problems when compared to the binding mechanisms provided by the DataView/DataSet combination. In our prototype/spike the lack of built-in implementation for sorting and filtering immediately surfaced. These methods are defined in the IBindingList interface, but is not implemented by BindingList<T>. A bit of googling led me to this blog entry and this GotDotNet workspace. Andrew Davey's BindingListView<T> provides a data bindable view for a BindingList, in the same way that a DataView provides a bindable view of a DataTable. This component implements much of the stuff described in Paul Ballard's article. One less argument for the typed dataset clan :-)

Typed datasets still has the upper hand when it comes to batch updates, optimistic concurrency, etc, when compared to custom business entity objects and collections. What the solution might need is an ORM framework that handles all the object-to-database stuff (the DDD repository pattern). I might be in for a classic turf war with the dataset-all-the-way clansmen!