Changing WCF Service reference URL based on environment - c#

I have a web application that uses a number of WCF Services. I deploy my web application in various environments (dev, UAT, production etc). The URL of each WCF Service is different for each environment. I am using .NET 3.5 andbasicHttpBindings
The web application uses a framework to support machine-specific settings in my web.config file. When instantiating an instance of a WCF Service client I call a function that creates the instance of the WCF Service client using the constructor overload that takes the arguments:
System.ServiceModel.Channels.Binding binding,
System.ServiceModel.EndpointAddress remoteAddress
In essence the <system.serviceModel><bindings><basicHttpBinding><binding> configuration in web.config has been replicated in C# code.
This approach works well.
However, I now have to enhance this approach to work with a WCF service that uses an X509 certificate. This means that I have to replicate the following additional settings in web.config in C# code:
<!-- inside the binding section -->
<security mode="Message">
<transport clientCredentialType="None" proxyCredentialType="None" realm="" />
<message clientCredentialType="Certificate" algorithmSuite="Default" />
</security>
<behaviors>
<endpointBehaviors>
<behavior name="MyServiceBehaviour">
<clientCredentials>
<clientCertificate storeLocation="LocalMachine" storeName="My"
x509FindType="FindByThumbprint" findValue="1234abcd" />
<serviceCertificate>
<defaultCertificate storeLocation="LocalMachine" storeName="My"
x509FindType="FindByThumbprint" findValue="5678efgh" />
<authentication trustedStoreLocation="LocalMachine"
certificateValidationMode="None" />
</serviceCertificate>
</clientCredentials>
</behavior>
</endpointBehaviors>
</behaviors>
I am having some difficulty figuring out how to code this configuration in C#.
Two questions
Can anyone recommend a better approach for managing WCF Service reference URLs across multiple environments?
Alternatively, any suggestions on how to replicate the above web.config section in C# will be welcomed

One possible approach would be to "externalize" certain parts of your <system.serviceModel> configuration into external files, one per environment.
E.g. we have "bindings.dev.config" and "bindings.test.config", which we then reference in our main web.config like this:
<system.serviceModel>
<bindings configSource="bindings.dev.config" />
</system.serviceModel>
That way, all you need to change from DEV to PROD is this one line of config XML.
Basically, in .NET 2.0 config, any configuration element can be "externalized". You cannot however externalize configGroups (such as "system.serviceModel") directly - you have to be on the "configuration element" level.
Marc
EDIT: OK, so NO config edit changes to switch between environments.....
In that case, you probably have to dream up a naming scheme, e.g. name your bindings, behaviors and endpoints in such a way, that you can distinguish them at runtime.
Something like:
<bindings>
<binding name="Default_DEV">
.....
</binding>
<binding name="Default_PROD">
.....
</binding>
</bindings>
that way, you could build up the name of the element you want (e.g. binding "Default_PROD") from your code and the environment you're running in, and then grab the according config from the config file which contains all the config settings for all environments.

The following code replicates the configuration in my original question:
myClient.ClientCredentials.ClientCertificate.SetCertificate(
StoreLocation.LocalMachine,
StoreName.My,
X509FindType.FindByThumbprint,
"1234abcd");
myClient.ClientCredentials.ServiceCertificate.SetDefaultCertificate(
StoreLocation.LocalMachine,
StoreName.My,
X509FindType.FindByThumbprint,
"5678efgh");
myClient.ClientCredentials.ServiceCertificate.Authentication.TrustedStoreLocation = StoreLocation.LocalMachine;
myClient.ClientCredentials.ServiceCertificate.Authentication.CertificateValidationMode = X509CertificateValidationMode.None;
In the production code the two thumbprint values are stored in appSettings in the web.config file.

We don't use web.config files at all, we specify everything programmatically and load all configuration from a centralized database.

Related

Mono WCF app can't find X509 certificates in stores on Linux

I'm working on a WCF application that must make calls to a web-service. The program works fine when testing with http. When https is used, I'm getting the exception "Specified X509 certificate with find type ... was not found in X509 store ....". The program works fine with https when run on Windows and .Net, but throws the exception when run in a Linux/Mono setup. I have added the certificates on the Linux system using the certmgr.exe utility, and can list them using that same utility, so they seem to be saved in the stores ok. The application gets its configuration info from its app.config file. I've attempted to find the certificate using various different x509FindTypes and findValues (FindBySubjectName, FindBySerialNumber, FindByThumbprint), and tried adding the certificate to both the machine and personal stores, but keep getting the same exception, referencing whichever find type, location, and store I setup. The relevant part of the app.config is below:
<system.serviceModel>
<client>
<endpoint address="https://webserviceaddress"
binding="basicHttpBinding"
bindingConfiguration="secureBasicHttpBinding"
behaviorConfiguration="secureClientBehavior"
contract="IWebServiceContract">
</endpoint>
</client>
<behaviors>
<endpointBehaviors>
<behavior name="secureClientBehavior" >
<clientCredentials>
<clientCertificate storeLocation="CurrentUser" storeName="My" x509FindType="FindBySerialNumber" findValue="AFCB6CC0417B2D459BB1F859CE070661" />
<serviceCertificate>
<authentication revocationMode="NoCheck" />
</serviceCertificate>
</clientCredentials>
</behavior>
</endpointBehaviors>
</behaviors>
<bindings>
<basicHttpBinding>
<binding name="secureBasicHttpBinding"
receiveTimeout="00:10:00" sendTimeout="00:10:00"
maxReceivedMessageSize="2147483647"
maxBufferSize="2147483647">
<security mode="TransportWithMessageCredential">
<message clientCredentialType="Certificate" />
</security>
</binding>
</basicHttpBinding>
</bindings>
</system.serviceModel>
If anyone can offer any help on getting the certificates to be found in the stores, it would be much appreciated.
Thanks in advance for any replies.

Could not find default endpoint element that references contract

I have a ASP .NET MVC app that I want to be able to connect to SOAP API.
I have created a wrapper project were I have my common methods that are working with the API. I have created this wrapper because the file generated by tool is so huge the project build would take ages.
From api doc site:
The tool then generates a single file named EconomicWebService.cs with the proxy code. This file can then be included directly in a project (this can slow down your Visual Studio as it is a rather big file) or built into a dll that can be referenced from your project)
I have referenced this wrapper as dll in my class library (middle layer) that is referenced into my MVC application.
Sadly it is not working, and I am getting this error:
Could not find default endpoint element that references contract 'S2s.Economic.WebService.EconomicWebServiceSoap' in the ServiceModel client configuration section. This might be because no configuration file was found for your application, or because no endpoint element matching this contract could be found in the client element.
Webconfig
<system.web>
.....
</system.web>
<runtime>
...
</runtime>
<system.serviceModel>
<bindings>
<basicHttpBinding>
<binding name="EconomicWebServiceSoap">
<security mode="Transport" />
</binding>
<binding name="EconomicWebServiceSoap1" />
</basicHttpBinding>
</bindings>
<client>
<endpoint address="https://api.e-conomic.com/secure/api1/economicwebservice.asmx"
binding="basicHttpBinding" bindingConfiguration="EconomicWebServiceSoap"
contract="PTS.S2s.Economic.WebService.EconomicWebServiceSoap"
name="EconomicWebServiceSoap" />
</client>
</system.serviceModel>
</configuration>
I have managed to find a workarround with manual endpoint setup in the code.
EndpointAddress endpoint = new EndpointAddress( "https://api.e-conomic.com/secure/api1/economicwebservice.asmx" );

Visual Studio - Adding two web references

I'm using VS2012 and have successfully added a service reference to a web service, lets call it Web Service A. I can connect and interact with that webservice, it all works ok.
There's another version of the webservice (Web Service B) that I need to connect to, it's essentially the same but one is used for live and the other for testing. The URLs are different so I thought I could add a second reference without an issue.
However, when I do add Web Service B everything appears to work fine (web.config is modified etc.) but all my existing code that interacts with Web service A breaks, visual studio acts like it doesn't know what classes I'm trying to instantiate.
Can I have two very similar web references (different URLs) that I can easily switch between by changing the code? I would have thought I could but maybe not?
When you add a Service or Web Reference, along with all the code that VS create, the important bit is this thing in the web.config (or App.Config):
<system.serviceModel>
<bindings>
<basicHttpBinding>
<binding name="TempConvertSoap" />
</basicHttpBinding>
<customBinding>
<binding name="TempConvertSoap12">
<textMessageEncoding messageVersion="Soap12" />
<httpTransport />
</binding>
</customBinding>
</bindings>
<client>
<endpoint address="http://www.w3schools.com/webservices/tempconvert.asmx"
binding="basicHttpBinding" bindingConfiguration="TempConvertSoap"
contract="ServiceReferenceA.TempConvertSoap" name="TempConvertSoap" />
<endpoint address="http://www.w3schools.com/webservices/tempconvert.asmx"
binding="customBinding" bindingConfiguration="TempConvertSoap12"
contract="ServiceReferenceA.TempConvertSoap" name="TempConvertSoap12" />
</client>
</system.serviceModel>
The example is from the Temperature Convesion service in w3schools.
In your scenario, you only need to add a single web reference and then, to connect to the other one, change this section, in particular the endpoint -> address attribute; as long as the webservice are IDENTICAL, it will work without problems.
As a bonus, being part of the web.config or app.config, you could use config tranforms to replace this with the proper address when building your Release.
If one is for live and another for test, I would add web.config transformations and then run your code under a 'test' configuration.
More info on web.config transforms here:
http://msdn.microsoft.com/en-us/library/dd465326(v=vs.110).aspx

WCF service returning 404 on method requests

I have a WCF service page running only WebGets/WebInvokes over SSL - it works fine on my local machine (self signed cert). On production, however, I can reach service.svc (and it gives me the message about how to consume) but service.svc/AnyRequest returns a 404. Both environments are hosted in IIS 7.5.
I've enabled tracing and the service isn't even picking up any of the method requests (e.g. service.svc/SomeRequest), however it is processing service.svc just fine. It's also listening at https://computername.domain.net/path/service.svc - is this normal? Should it normally be pointing to https://publicfacing.com/path/service.svc?
Also note that the production server is hosting multiple sites within IIS.
Below is the system.serviceModel section of my web.config. The SSLBehave was suggested from here.
<system.serviceModel>
<bindings>
<webHttpBinding>
<binding name="TransportSecurity">
<security mode="Transport">
<transport clientCredentialType="None"></transport>
</security>
</binding>
</webHttpBinding>
</bindings>
<behaviors>
<serviceBehaviors>
<behavior name="SSLBehave">
<useRequestHeadersForMetadataAddress>
<defaultPorts>
<add scheme="https" port="443"/>
</defaultPorts>
</useRequestHeadersForMetadataAddress>
</behavior>
</serviceBehaviors>
<endpointBehaviors>
<behavior name="UserManagement.ajaxAspNetAjaxBehavior">
<webHttp defaultOutgoingResponseFormat="Json" defaultBodyStyle="Wrapped" />
</behavior>
</endpointBehaviors>
</behaviors>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true"
multipleSiteBindingsEnabled="true" />
<services>
<service name="UserManagement.ajax" behaviorConfiguration="SSLBehave">
<endpoint address="" behaviorConfiguration="UserManagement.ajaxAspNetAjaxBehavior"
binding="webHttpBinding" bindingConfiguration="TransportSecurity" contract="UserManagement.ajax" />
</service>
</services>
</system.serviceModel>
The first thing I do whenever I hit a 404 with a newly-developed WCF Web Service is checking the handler mapping required to interpret this type of call, because it's often the cause of the issue. There are several ways to work around the problem, many of which require a manual execution of the ServiceModelReg.exe console command: these are undoubtedly valid procedures but might also not work – or create additional problems – if your development machine has a particularly complex configuration. The resolution method I propose below is slightly longer to pull off, but has the advantage of solving the problem more safely and securely.
Open the Server Manager interface for machine management, usually present in both the Task Bar and the Start menu.
Go to the Dashboard (or Control Panel) and select Add Role or Feature to open the Wizard.
Select the Role-based or Feature-based installation type and the server you want to work on, that is, your local / local server.
Go to the Features section: Once there, expand the .NET Framework 3.5 Features node and / or the .NET Framework 4.5 Features node, depending on what you have installed: if you have both, you should perform the following step twice (for each one of them).
Expand the WCF Services section (if available), then select HTTP Activation (see screenshot below).
Continue until you complete the Wizard, then click Install.
Once the installation is complete, you should be able to run your WCF Service without incurring in the 404 error ever again.
For additional info regarding this specific issue and how to fix it, you can also read this post on my blog.
I would start by checking a number of things;
Permissions on the hosted directory?
.Net version is correct?
Have you added the certificate to the site?
Try putting an image in the same path, can you navigate to that (rule out the odd occasional path mappings)
Good luck!
You can implement transport level security using WsHttp bindings. See this article; in your bindings try this biding instead:
<wsHttpBinding>
<binding name="TransportSecurity">
<security mode="Transport">
<transport clientCredentialType="None"/>
</security>
</binding>
</wsHttpBinding>
The article mentions you should tie up the bindings with the end points.
I had the same problem. From what I read, WCF isnt NT Authenticated authorization (or HTTPContext compatible) by default.
I had to add this to my config file for the WCF service web.config in the section:
<serviceHostingEnvironment aspNetCompatibilityEnabled="true"/>
Which you did, plus this:
And on the actual service class definiation I had to add:
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
public class DataService : IDataDeliveryServiceContract
This fixed my problem.
Perhaps in your RouteConfig.cs file add this line:
routes.IgnoreRoute("{resource}.svc/{*pathInfo}");
So long as your .svc file is in the root of the application.
As you mentioned you can access your service by .svc extension service.svc but not in REST format service.svc/AnyRequest, the problem must be in routing integration.
add this to your web.config
<system.webServer>
<modules runAllManagedModulesForAllRequests="true">
<add name="UrlRoutingModule" type="System.Web.Routing.UrlRoutingModule, System.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
</modules>
<handlers>
<add name="UrlRoutingHandler" preCondition="integratedMode" verb="*" path="UrlRouting.axd"/>
</handlers>
</system.webServer>
In the IIS 6 The cause of this error must be Check that file exists setting of svc extention, make sure "Check that file exists is unchecked". For more information see IIS Hosted Service Fails.
To help others that find themselves stuck with this - It may be that your service name is not the fully qualified name, which it must be.
The following setting in web.config fixed a WCF .svc 404 on a HTTPS web site :
<webHttpBinding>
<!-- https -->
<security mode="Transport">
<transport clientCredentialType = "None" proxyCredentialType="None"/>
</security>
</binding>
</webHttpBinding>
I tried the above solutions, installing WCF Services, ensuring that there were proper permissions in the API directory and several other things.
While some of those were issues there was one issue for me that isn't mentioned above.
If Request Filtering is enabled for the entire server or the given site, make sure that .svc is a trusted file name extension, otherwise it will be blocked. Go to IIS > Request Filtering. Click on Edit Feature Settings. Check to see if "allow unlisted file extensions" is checked. If so, make sure that there is an entry in the list for .svc. Otherwise IIS will block the file from being served.

WCF-Ajax enabled Web service. Service is not defined when deployed

I have an Ajax enabled web service within my website.
The service sits in a WebServices folder within the Root of the website, the folder also contains it's own Web.config (as the bindings for the Ajax web.config conflict the configs on another layer of my site).
I have added the web service to the scriptmanager in my master page and used JQuery to call the service within a page.
This is all working fine running locally in IIS 7 (Vista).
However when I publish the Website (locally to file system as we have to copy the files manually to our test environment) then copy the files to our test environment (running IIs 7.5 on Windows Server 2008 R2), the web service doesn't work and I get a Javascript error saying "Service is not defined".
If I browse to the service then I can view the wsdl with no problems however if I try and view service.svc/js (the same url the page is looking for) then I recieve a 404 not found error.
I've done a lot of Googling on the subject and while there are loads of suggested Web.config fixes, I have tried multiple combinations and so far nothing seems to be working.
The service it's self is very basic.
[ServiceContract(Namespace = "MyService")]
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Required)]
public class MyService
{
[OperationContract]
public JSONObject DoWork()
{
...Do some logic
return JSONObject;
}
}
JSONObject is a class I created that just holds some properties to be sent to the page. As I said this is all working hosted in IIS locally.
---EDIT
Here's the Web.Config that sits in the same directory as the web service:
<?xml version="1.0"?>
<configuration>
<system.serviceModel>
<bindings>
<webHttpBinding>
<binding name="default" />
</webHttpBinding>
</bindings>
<services>
<service name="MyWebsite.WebServices.MyService"
behaviorConfiguration="MyWebsite.WebServices.MyServiceBehavior" >
<endpoint address="" behaviorConfiguration="MyWebsite.WebServices.MyServiceAspNetAjaxBehavior" binding="webHttpBinding" contract="MyWebsite.WebServices.MyService" />
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="MyWebsite.WebServices.MyServiceBehavior">
<serviceMetadata httpGetEnabled="true"/>
</behavior>
</serviceBehaviors>
<endpointBehaviors>
<behavior name="MyWebsite.WebServices.MyServiceAspNetAjaxBehavior">
<enableWebScript />
</behavior>
</endpointBehaviors>
</behaviors>
</system.serviceModel>
</configuration>
I also have the following section in my root web.config:
<system.serviceModel>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" />
</system.serviceModel>
I’ve managed to track down the issue.
It comes down to patching. The following article mentions an update to fix the issue in IIS 7.5 and Windows 7 but the patch is also applicable to Vista and Windows Server 2008.
http://support.microsoft.com/kb/2520479
There is a second option to fix the issue which involves reordering the Handlers in the IIS applicationHost.config file on the machine affected, I have tried this and it does in fact solve the issue.
The second option is described in the article above.

Categories