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>
</s开发者_开发知识库ecurity>
</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.
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>
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 thatmultipleSiteBindingsEnabled="true"
is set.- This is documented here:
- https://learn.microsoft.com/en-us/dotnet/framework/wcf/feature-details/supporting-multiple-iis-site-bindings
- https://learn.microsoft.com/en-us/dotnet/framework/configure-apps/file-schema/wcf/servicehostingenvironment
- It looks like this was added in .NET Framework 4.0 ( https://blogs.msdn.microsoft.com/saurabs/2012/11/21/wcf-handling-multiple-iis-bindings/ ), in .NET 3.5 you needed to use
<baseAddressPrefixFilters>
which are not necessary anymore.
- This is documented here:
- You do not need the
<serviceMetadata>
element in yourweb.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.- This was only necessary in WCF 3.5.
- Microsoft says to leave it blank (or use a relative URI for
PATH_INFO
-style endpoints) when hosting WCF in IIS:- Read this article: https://learn.microsoft.com/en-us/dotnet/framework/wcf/feature-details/deploying-an-internet-information-services-hosted-wcf-service
You must always use relative endpoint addresses for IIS-hosted service endpoints. Supplying a fully-qualified endpoint address (for example, http://localhost/MyService.svc) can lead to errors in the deployment of the service if the endpoint address does not point to the IIS-application that hosts the service exposing the endpoint. Using relative endpoint addresses for hosted services avoids these potential conflicts.
- Read this article: https://learn.microsoft.com/en-us/dotnet/framework/wcf/feature-details/deploying-an-internet-information-services-hosted-wcf-service
- 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.
- 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.
- 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):
- 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.- This is documented here: https://learn.microsoft.com/en-us/dotnet/framework/wcf/simplified-configuration
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>
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.
The solution for running in a local domain and also running on production and other environments, without relying on memory to change anything, is config transformations. They transform the compiled web.config based on the selected configuration profile. Locally I run in Debug
mode, on the testing environment I publish to a TestRelease
profile, and production I have another profile:
If you can't expand your web.config, you can right click and add config tranforms. In order to get more than Debug and Release, you add more configurations via the manager:
Here's example transformations:
Web.Debug.config
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
<!--...-->
<system.serviceModel>
<protocolMapping>
<add binding="basicHttpBinding" scheme="http" xdt:Transform="SetAttributes" />
</protocolMapping>
<bindings>
<basicHttpBinding>
<binding xdt:Locator="Match(name)" name="basicHttpBindingConfiguration">
<security xdt:Transform="Remove">
<transport xdt:Transform="Remove"/>
</security>
</binding>
<binding xdt:Locator="Match(name)" name="fileTransferBinding">
<security xdt:Transform="Remove">
<transport xdt:Transform="Remove"/>
</security>
</binding>
</basicHttpBinding>
</bindings>
</system.serviceModel>
</configuration>
Web.Release.config
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
<!--...-->
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior>
<serviceMetadata httpGetEnabled="false" httpsGetEnabled="false" xdt:Transform="Replace"/>
<serviceDebug includeExceptionDetailInFaults="false" xdt:Transform="Replace"/>
</behavior>
</serviceBehaviors>
</behaviors>
<protocolMapping>
<add binding="basicHttpsBinding" scheme="https" xdt:Transform="Replace"/>
</protocolMapping>
<bindings>
<basicHttpBinding>
<binding xdt:Locator="Match(name)" name="basicHttpBindingConfiguration">
<security mode="Transport" xdt:Transform="Insert">
<transport clientCredentialType="None" proxyCredentialType="None" />
</security>
</binding>
<binding xdt:Locator="Match(name)" name="fileTransferBinding">
<security mode="Transport" xdt:Transform="Insert">
<transport clientCredentialType="None" proxyCredentialType="None" />
</security>
</binding>
</basicHttpBinding>
</bindings>
</system.serviceModel>
<system.webServer>
<directoryBrowse enabled="false" xdt:Transform="Replace"/>
</system.webServer>
</configuration>
精彩评论