How can I combine the WCF services config for both http and https in one web.config?

37,244

Solution 1

Well, one problem with your combined config is that your two endpoints are on the same address - that won't work.

If you're hosting in IIS, then your server, virtual directory and the *.svc file needed will determine your basic address - it'll be something like:

http://yourservername/VirtualDirectory/YourService.svc

If you want to have two endpoints, at least one of them needs to define a relative address:

<services>
    <service name="MyNamespace.MyService" 
             behaviorConfiguration="MyServiceBehavior">
       <endpoint 
           address="basic" 
           binding="basicHttpBinding" 
           contract="MyNamespace.IMyService"/>
       <endpoint 
           address="secure" 
           binding="basicHttpBinding" bindingConfiguration="HttpsBinding"  
           contract="MyNamespace.IMyService"/>
    </service>
</services>

In this case, you'd have your HTTP endpoint on:

http://yourservername/VirtualDirectory/YourService.svc/basic

and your secure HTTPS endpoint on:

https://yourservername/VirtualDirectory/YourService.svc/secure

Furthermore: your secure endpoint uses a HttpsBinding configuration - but you're lacking such a binding configuration - all you have is:

<bindings>
  <basicHttpBinding>
    <binding name="HttpBinding">
      <security mode="None">
        <transport clientCredentialType="None"></transport>
      </security>
    </binding>
  </basicHttpBinding>
</bindings>

You need to add the HttpsBinding configuration!!

<bindings>
  <basicHttpBinding>
    <binding name="HttpBinding">
      <security mode="None">
        <transport clientCredentialType="None"></transport>
      </security>
    </binding>
    <binding name="HttpsBinding">
      <security mode="Transport">
          <transport clientCredentialType="Windows" />
      </security>
    </binding>
  </basicHttpBinding>
</bindings>

Solution 2

I recently had to make a WCF 3.5 REST (webHttpBinding) service available on both HTTP and HTTPS in a Microsoft Azure App Service (IIS). It was a fun... and agonizing adventure. Here are my findings and my web.config's <system.serviceModel>:

Note: these notes are for a WCF REST web-service running using *.svc (@ServiceHost) files within a minimal ASP.NET 4.7 application (with Global.asax) within IIS 10 on Windows Server 2016. These notes do not apply to self-hosted WCF services, non-REST WCF services (i.e. SOAP), or platforms older than .NET Framework 4.7. This also does not apply to .NET Core.

  • Upgrade to .NET Framework 4.7.2 or later (that's just common-sense)
  • The most important part is using the <serviceHostingEnvironment> element is essential, ensure that multipleSiteBindingsEnabled="true" is set.
  • You do not need the <serviceMetadata> element in your web.config file at all.
    • Apparently this element is for WSDL metadata - and RESTful services do not support WSDL.
    • I point this out because there's at least a few top Google search results for articles and StackOverflow postings where people say this is required. Those people are incorrect.
  • Do not use absolute URIs in your <endpoint address="" attributes - just leave the attribute empty.
  • WCF will detect if it can be reached using both HTTP and HTTPS and will raise an error if the web.config file tells WCF to accept HTTPS connections when the hosting application (i.e. IIS) does not have HTTPS enabled in its parent website.
    • This was unexpected to me because all other web-application platforms I've used don't complain if they're configured for HTTPS but the parent webserver isn't.
      • Especially given that in this case, WCF runs with/inside the .NET request pipeline and ASP.NET certainly doesn't care about IIS' Website Binding (because ASP.NET is designed to be agnostic about parent Website bindings)
      • Note: "website bindings" refers to IIS Website Bindings, not "WCF bindings" which are a separate concept.
  • To get this working locally in Visual Studio and IIS Express, ensure that the parent Web Application Project has "SSL Enabled = True" in the "Properties" window (not the same thing as the "Project Properties" window (yes, I screamed too):
    • enter image description here
  • In .NET 4.6.1, Microsoft simplified WCF's configuration by allowing the <services> element to be omitted and be automatically generated behind-the-scenes, however I didn't have consistent results with RESTful services with dual-HTTP+HTTPS bindings, so I still specify my <services> manually.

TL;DR:

Here's my <system.serviceModel> element from my web.config file:

<system.serviceModel>
    <services>
        <service name="WcfService1.MainService">
            <endpoint address="" binding="webHttpBinding" contract="WcfService1.IMainService" behaviorConfiguration="myWebBehavior" bindingConfiguration="myWebHttpBindingInsecure" />
            <endpoint address="" binding="webHttpBinding" contract="WcfService1.IMainService" behaviorConfiguration="myWebBehavior" bindingConfiguration="myWebHttpBindingSecure" />
        </service>
        <service name="WcfService1.AnotherService">
            <endpoint address="" binding="webHttpBinding" contract="WcfService1.IAnotherService" behaviorConfiguration="myWebBehavior" bindingConfiguration="myWebHttpBindingInsecure" />
            <endpoint address="" binding="webHttpBinding" contract="WcfService1.IAnotherService" behaviorConfiguration="myWebBehavior" bindingConfiguration="myWebHttpBindingSecure" />
        </service>
        <!--  etc... -->
    </services>
    <behaviors>
        <endpointBehaviors>
            <behavior name="myWebBehavior">
                <webHttp />
            </behavior>
        </endpointBehaviors>
        <serviceBehaviors>
            <behavior>
                <serviceDebug includeExceptionDetailInFaults="true" />
            </behavior>
        </serviceBehaviors>
    </behaviors>
    <protocolMapping>
        <!-- By default (in machine.config), the 'http' scheme is mapped to 'basicHttpBinding' (SOAP), not 'webHttpBinding' (REST) and the 'https' scheme is not mapped. -->
        <add binding="webHttpBinding" scheme="https" bindingConfiguration="myWebHttpBindingSecure" />
        <add binding="webHttpBinding" scheme="http"  bindingConfiguration="myWebHttpBindingInsecure" />
    </protocolMapping>
    <serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" />
    <bindings>
        <webHttpBinding>
            <!-- maxReceivedMessageSize="104857600" is 100MiB -->
            <binding name="myWebHttpBindingInsecure" maxReceivedMessageSize="104857600" transferMode="Streamed">
                <security mode="None" />
            </binding>
            <binding name="myWebHttpBindingSecure" maxReceivedMessageSize="104857600" transferMode="Streamed">
                <security mode="Transport" />
            </binding>
        </webHttpBinding>
    </bindings>
</system.serviceModel>

Solution 3

The issue is not with the config file but with the IIS setup. You need to enable both HTTP & HTTPS in IIS. In IIS 7.5, go to your site and click on Bindings under Edit Site Action. Ensure that both http & https have been added. Then you need to create a binding for HTTP under <basicHttpBinding>, with security mode set to none. Add the newly created binding configuration to http endpoint. You are good to go. Let me know if you need further issue.

Share:
37,244
sohtimsso1970
Author by

sohtimsso1970

Updated on October 10, 2020

Comments

  • sohtimsso1970
    sohtimsso1970 over 3 years

    I spent a lot of time figuring out how to configure my WCF services so that they would work for https in the production environment.

    Basically, I needed to do this:

    <behaviors>
      <serviceBehaviors>
        <behavior name="MyServiceBehavior">
          <serviceMetadata httpGetEnabled="true" httpsGetEnabled="true" />
          <serviceDebug includeExceptionDetailInFaults="true" />
        </behavior>
      </serviceBehaviors>
    </behaviors>
    <serviceHostingEnvironment multipleSiteBindingsEnabled="true" aspNetCompatibilityEnabled="true" />
    <services>
      <service name="MyNamespace.MyService" behaviorConfiguration="MyServiceBehavior">
        <endpoint address="" bindingNamespace="https://secure.mydomain.com" binding="basicHttpBinding" bindingConfiguration="HttpsBinding" contract="MyNamespace.IMyService"/>
      </service>
    </services>
    <bindings>
      <basicHttpBinding>
        <binding name="HttpsBinding">
          <security mode="Transport">
            <transport clientCredentialType="None"></transport>
          </security>
        </binding>
      </basicHttpBinding>
    </bindings>
    

    Adding the bindingNamespace attribute to the endpoint is the final thing that made it work.

    But this config doesn't work in my local dev environment where I'm working under regular http. So my config there is:

    <behaviors>
      <serviceBehaviors>
        <behavior name="MyServiceBehavior">
          <serviceMetadata httpGetEnabled="true" httpsGetEnabled="false" />
          <serviceDebug includeExceptionDetailInFaults="true" />
        </behavior>
      </serviceBehaviors>
    </behaviors>
    <serviceHostingEnvironment multipleSiteBindingsEnabled="true" aspNetCompatibilityEnabled="true" />
    <services>
      <service name="MyNamespace.MyService" behaviorConfiguration="MyServiceBehavior">
        <endpoint address="" binding="basicHttpBinding" contract="MyNamespace.IMyService"/>
      </service>
    </services>
    

    The differences here are that I've set the httpsGetEnabled attribute to false, and I removed the bindingConfiguration and bindingNamespace.

    The problem is: how do I create one config block that handles BOTH?

    I really hate having to make lots of special modifications to the config every time I do a release. Yes, I know I could have a post-build task that automatically changes the values, but I'd like to merge the configs if possible.

    I tried something like this:

    <behaviors>
      <serviceBehaviors>
        <behavior name="MyServiceBehavior">
          <serviceMetadata httpGetEnabled="true" httpsGetEnabled="true" />
          <serviceDebug includeExceptionDetailInFaults="true" />
        </behavior>
      </serviceBehaviors>
    </behaviors>
    <serviceHostingEnvironment multipleSiteBindingsEnabled="true" aspNetCompatibilityEnabled="true" />
    <services>
      <service name="MyNamespace.MyService" behaviorConfiguration="MyServiceBehavior">
        <endpoint address="" binding="basicHttpBinding" contract="MyNamespace.IMyService"/>
        <endpoint address="" bindingNamespace="https://secure.mydomain.com" binding="basicHttpBinding" bindingConfiguration="HttpsBinding" contract="MyNamespace.IMyService"/>
      </service>
    </services>
    <bindings>
      <basicHttpBinding>
        <binding name="HttpsBinding">
          <security mode="Transport">
            <transport clientCredentialType="None"></transport>
          </security>
        </binding>
      </basicHttpBinding>
    </bindings>
    

    I figured that putting both endpoints would give it two options to look for when activating the service. However, this doesn't work. I get this error:

    Could not find a base address that matches scheme https for the endpoint with binding BasicHttpBinding. Registered base address schemes are [http].

    From looking around SO and the rest of the internet, it appears that others have had problems slaying this dragon.