Tag Archives: wcf

In hell with SSL and WCF

Standard

 image

A long time ago I decided that WCF was one of those things I wouldn’t try and learn about in preference to learning other things, such as Silverlight, MVC and how to cook cookies properly.  Now that decision has come back to haunt me.

It seemed simple.  A client server prototype using wsHttpBindings and WCF.  My initial Project’s configuration files worked well on my local development machine (http).

The time came to put this on the server using SSL (https).

Much of the documentation on the web says that the simplest way of doing this is to configure IIS (IIS6 in my case) to work with SSL, then copy it over.  Words cannot express how wrong this proved to be.

For future reference, rest assured you will need very different versions of these files for local development as opposed to a production server.

I’ve also learnt that WCF gives as good as it gets in the ongoing competition between software development technologies in providing weird and useless error messages that are no hope at all in solving problems.  (Although handy for doing google searches to find others as miserable as you are)

Before

image

My original (Visual Studio generated) client app.config file looked like this:

<system.serviceModel>
  <bindings>
    <wsHttpBinding>
      <binding name="WSHttpBinding_IClientConnect" closeTimeout="00:01:00"
          openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00"
          bypassProxyOnLocal="false" transactionFlow="false" hostNameComparisonMode="StrongWildcard"
          maxBufferPoolSize="524288" maxReceivedMessageSize="65536"
          messageEncoding="Text" textEncoding="utf-8" useDefaultWebProxy="true"
          allowCookies="false">
        <readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384"
            maxBytesPerRead="4096" maxNameTableCharCount="16384" />
        <reliableSession ordered="true" inactivityTimeout="00:10:00"
            enabled="false" />
        <security mode="Message">
          <transport clientCredentialType="Windows" proxyCredentialType="None"
              realm="" />
          <message clientCredentialType="Windows" negotiateServiceCredential="true"
              algorithmSuite="Default" />
        </security>
      </binding>
    </wsHttpBinding>
  </bindings>
  <client>
    <endpoint address="http://localhost:10916/SecureClientDemoServer/ClientConnect.svc"
        binding="wsHttpBinding" bindingConfiguration="WSHttpBinding_IClientConnect"
        contract="ClientService.IClientConnect" name="WSHttpBinding_IClientConnect">
      <identity>
        <dns value="localhost" />
      </identity>
    </endpoint>
  </client>
</system.serviceModel>

My server web.config file looked like this:

    <system.serviceModel>
    <serviceHostingEnvironment aspNetCompatibilityEnabled="true"/>
        <services>
   <service behaviorConfiguration="ServiceBehavior" name="ClientConnect">
    <endpoint address="" binding="wsHttpBinding" contract="IClientConnect">
     <identity>
      <dns value="localhost" />
     </identity>
    </endpoint>
    <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
   </service>
  </services>
        <behaviors>
            <serviceBehaviors>
                <behavior name="ServiceBehavior">
                    <!– To avoid disclosing metadata information, set the value below to false and remove the metadata endpoint above before deployment –>
                    <serviceMetadata httpGetEnabled="true"/>
                    <!– To receive exception details in faults for debugging purposes, set the value below to true.  Set to false before deployment to avoid disclosing exception information –>
                    <serviceDebug includeExceptionDetailInFaults="false"/>
                </behavior>
            </serviceBehaviors>
        </behaviors>
    </system.serviceModel>

 

After

 image

app.config

<system.serviceModel>
   <bindings>
    <wsHttpBinding>
      <binding name="WSHttpBinding_IClientConnect" closeTimeout="00:01:00" openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00" bypassProxyOnLocal="false" transactionFlow="false" hostNameComparisonMode="StrongWildcard" maxBufferPoolSize="524288" maxReceivedMessageSize="65536" messageEncoding="Text" textEncoding="utf-8" useDefaultWebProxy="true" allowCookies="false">
        <readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384" maxBytesPerRead="4096" maxNameTableCharCount="16384"/>
        <reliableSession ordered="true" inactivityTimeout="00:10:00" enabled="false"/>
        <security mode="Transport">
          <transport clientCredentialType="None" proxyCredentialType="None" realm=""/>
          <message clientCredentialType="None"  algorithmSuite="Default" negotiateServiceCredential="false" establishSecurityContext="false" /> 
        </security>
      </binding>
    </wsHttpBinding>
  </bindings>
  <client>
    <endpoint address=https://remoteserver/SecureClientDemo/ClientConnect.svc binding="wsHttpBinding" bindingConfiguration="WSHttpBinding_IClientConnect" contract="ClientService.IClientConnect" name="WSHttpBinding_IClientConnect">
    </endpoint>
  </client>
</system.serviceModel>

 

web.config

    <system.serviceModel>
        <serviceHostingEnvironment aspNetCompatibilityEnabled="true"/>
        <services>
            <service behaviorConfiguration="ServiceBehavior" name="ClientConnect">
                     <endpoint address=https://remoteserver/SecureClientDemo/ClientConnect.svc bindingConfiguration="HttpsBinding" binding="wsHttpBinding" contract="IClientConnect">
                </endpoint>
                <endpoint address="mex" binding="mexHttpsBinding" contract="IMetadataExchange"
                                 name="MexHttpsBindingEndpoint" 
                                 />
            </service>
        </services>
        <behaviors>
            <serviceBehaviors>
                <behavior name="ServiceBehavior">
                    <!– To avoid disclosing metadata information, set the value below to false and remove the metadata endpoint above before deployment –>
                    <serviceMetadata httpGetEnabled="false" httpsGetEnabled="true"/>
                    <!– To receive exception details in faults for debugging purposes, set the value below to true.  Set to false before deployment to avoid disclosing exception information –>
                    <serviceDebug includeExceptionDetailInFaults="false"/>
                </behavior>
            </serviceBehaviors>
        </behaviors>
           <bindings>
         <wsHttpBinding>
          <binding name="HttpsBinding">
            <security mode="Transport">
              <transport clientCredentialType="None"/>
            </security>
          </binding>
        </wsHttpBinding>
            </bindings>
    </system.serviceModel>

 

 

Conclusion

This article is intended to show on the web a working set of configuration parameters, so for the time being I don’t have time to go into and explanation I only half understand.  I hope people find this helpful.

 

Links

http://msdn.microsoft.com/en-us/library/ms729700.aspx

http://stackoverflow.com/questions/1521117/wcf-over-ssl-404-error

http://stackoverflow.com/questions/2435823/the-provided-uri-scheme-https-is-invalid-expected-http-parameter-name-via

http://msdn.microsoft.com/en-us/library/ff648840.aspx

Advertisements

Adding the User-Agent when calling a Web Service with WCF

Standard

 image

“Every computing problem can be solved with yet another layer of indirection”

One of the cool things about the original Visual Studio (Visual Studio.Net) was how easy it was to create and consume web services. 

Over time, this support has been replaced by the trendy WCF services and WCF Soapclient infrastructure.    These new “Service References” differ from the original “Web References” in that they completely abstract the communications layer.  So it isn’t really a web reference anymore is it?

Recently I had to create a client program that called a .net 1.1 asmx web service.  This web service accessed the HTTP-Header variable “User-Agent”.   The original web service client proxies (Web References) that were generated by Visual Studio used to supply a User-Agent.  This is no longer the case.

It appears that a bug has been logged on Microsoft Connect, but it seems unlikely this will be fixed.

Most remedies to the problem discuss using code like this:

Dim httpRequestMessage As New HttpRequestMessageProperty()
httpRequestMessage.Headers.Add(USER_AGENT_HTTP_HEADER, Me.m_userAgent) requestMessage.Properties.Add(HttpRequestMessageProperty.Name, httpRequestMessage)

Sadly when you’re working with a generated Service Reference I could see no way of using this code.  I assume it’s intended for a lower layer than the one I was working with.

Fortunately Paul Morgado has posted some code that I manipulated to solve the problem.    This article shows how to create a “behaviour” which can be then added to the service reference instance.

Like this:

Dim loService As New MyServiceReferenceSoapClient()
loService.Endpoint.Behaviors.Add(New HttpUserAgentEndpointBehavior("SomeUserAgentString"))
loService.SomeWebMethod()

Here’s the code for the HttpUserAgentEndpointBehaviour:

Imports System.ServiceModel
Imports System.ServiceModel.Channels
Imports System.ServiceModel.Dispatcher
Imports System.ServiceModel.Description
Imports System.ComponentModel
Public Class HttpUserAgentMessageInspector

    Implements IClientMessageInspector
    Private Const USER_AGENT_HTTP_HEADER As String = "user-agent"

    Private m_userAgent As String

    Public Sub New(ByVal userAgent As String)
        Me.m_userAgent = userAgent
    End Sub

#Region "IClientMessageInspector Members"

    Public Sub AfterReceiveReply(ByRef reply As System.ServiceModel.Channels.Message, ByVal correlationState As Object) Implements IClientMessageInspector.AfterReceiveReply
    End Sub

    Public Function BeforeSendRequest(ByRef request As System.ServiceModel.Channels.Message, ByVal channel As System.ServiceModel.IClientChannel) As Object Implements IClientMessageInspector.BeforeSendRequest
        Dim httpRequestMessage As HttpRequestMessageProperty
        Dim httpRequestMessageObject As Object
        If request.Properties.TryGetValue(HttpRequestMessageProperty.Name, httpRequestMessageObject) Then
            httpRequestMessage = TryCast(httpRequestMessageObject, HttpRequestMessageProperty)
            If String.IsNullOrEmpty(httpRequestMessage.Headers(USER_AGENT_HTTP_HEADER)) Then
                httpRequestMessage.Headers(USER_AGENT_HTTP_HEADER) = Me.m_userAgent
            End If
        Else
            httpRequestMessage = New HttpRequestMessageProperty()
            httpRequestMessage.Headers.Add(USER_AGENT_HTTP_HEADER, Me.m_userAgent)
            request.Properties.Add(HttpRequestMessageProperty.Name, httpRequestMessage)
        End If
        Return Nothing
    End Function

#End Region
End Class
Public Class HttpUserAgentEndpointBehavior
    Implements IEndpointBehavior
    Private m_userAgent As String

    Public Sub New(ByVal userAgent As String)
        Me.m_userAgent = userAgent
    End Sub

#Region "IEndpointBehavior Members"

    Public Sub AddBindingParameters(ByVal endpoint As ServiceEndpoint, ByVal bindingParameters As System.ServiceModel.Channels.BindingParameterCollection) Implements IEndpointBehavior.AddBindingParameters
    End Sub

    Public Sub ApplyClientBehavior(ByVal endpoint As ServiceEndpoint, ByVal clientRuntime As System.ServiceModel.Dispatcher.ClientRuntime) Implements IEndpointBehavior.ApplyClientBehavior
        Dim inspector As New HttpUserAgentMessageInspector(Me.m_userAgent)
        clientRuntime.MessageInspectors.Add(inspector)
    End Sub

    Public Sub ApplyDispatchBehavior(ByVal endpoint As ServiceEndpoint, ByVal endpointDispatcher As System.ServiceModel.Dispatcher.EndpointDispatcher) Implements IEndpointBehavior.ApplyDispatchBehavior
    End Sub

    Public Sub Validate(ByVal endpoint As ServiceEndpoint) Implements IEndpointBehavior.Validate
    End Sub

#End Region
End Class