WCF RESTful Web Service under IIS - URL Rewriting (getting 404 errors) - wcf

I have a WCF RESTful web service (using the webHttpBinding) running under IIS, exposed via an SVC file (accessed at http://projectserver/BlarghService/BlarghService.svc).
I can access service endpoints through the URI templates defined on each service by using the BlarghService.svc file, like so:
http://projectserver/BlarghService/BlarghService.svc/accounts/
http://projectserver/BlarghService/BlarghService.svc/accounts/342432
Both of these work fine.
However it's important I remove implementation details from the URIs, so I used IIS URI rewriting module and set up a wildcard rewriting rule which tacks everything after /BlarghService/ to the end of BlarghService.svc, so the following are transformed into the earlier representations:
http://projectserver/BlarghService/accounts/
http://projectserver/BlarghService/accounts/342432
However, when I request these resources I get 404 errors. These are not IIS-generated 404 errors, but those generated by ASP.NET itself (so IIS is correctly rewriting the URL). However ASP.NET decides to check if the specified file ("BlarghService.svc/accounts/342432") exists in the filesystem which it should be passing it on to my web service.
But the strange thing is when I copy the reported "Requested URL" (from the ASP.NET 404 error) back into the address bar, it works fine.
So what's going on?
EDIT: Here is my web.config (located in the "BlarghService" directory)
<system.web>
<compilation debug="true" />
<authentication mode="None" />
<customErrors mode="Off" />
</system.web>
<system.webServer>
<validation validateIntegratedModeConfiguration="false" />
<!-- Currently ASP.NET steps in says it's a 404 error when it should be handled by BlarghService.svc, I've no idea why. -->
<rewrite>
<rules>
<rule name="BlarghServiceRewriteRule" patternSyntax="Wildcard">
<match url="*" />
<action type="Rewrite" url="BlarghService.svc/{R:0}" />
</rule>
</rules>
</rewrite>
</system.webServer>
<system.serviceModel>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true">
<baseAddressPrefixFilters>
<add prefix="http://projectserver/BlarghService" />
</baseAddressPrefixFilters>
</serviceHostingEnvironment>
<services>
<service name="Foo.Blargh.Api.BlarghService" behaviorConfiguration="BlarghServiceBehavior">
<endpoint name="BlarghEndpoint" address="BlarghService.svc" behaviorConfiguration="BlarghEndpointBehavior" binding="webHttpBinding" contract="RH.Blargh.Api.BlarghService" />
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="BlarghServiceBehavior">
<serviceMetadata httpGetEnabled="false" />
<serviceDebug includeExceptionDetailInFaults="true" />
</behavior>
</serviceBehaviors>
<endpointBehaviors>
<behavior name="BlarghEndpointBehavior">
<webHttp />
</behavior>
</endpointBehaviors>
</behaviors>
</system.serviceModel>

UPDATE: It turns out that WCF can only respond on one particular HTTP host header at a time, and that my URL rewriting was involved in responding to some host headers but not others. By standardising on a single set of host-headers and reformulating my rewrite rules accordingly it started working.

Related

Azure hosted WCF azure active directory authentication returns 404

I'm new to WCF. I created WCF method that returns file. I deployed it to azure App Service and it worked when i called it like this
https://myapp.azurewebsites.net/myService.svc/MyMethod?MyParam=MyValue
Than i turned on Azure active directory authentication for azure App Service and now i get 404 error. But authentication against AAD works - i get redirected to login page if i'm not singed in user.
I tried searching SO and google and i can't figure out what i'm doing wrong or if it is just not possible to set up this way with WCF.
Web config:
<?xml version="1.0"?>
<configuration>
<appSettings>
<add key="aspnet:UseTaskFriendlySynchronizationContext" value="true"/>
</appSettings>
<system.web>
<customErrors mode="Off"/>
<compilation targetFramework="4.5.2"/>
<httpRuntime targetFramework="4.5.2"/>
<httpModules>
<add name="ApplicationInsightsWebTracking" type="Microsoft.ApplicationInsights.Web.ApplicationInsightsHttpModule, Microsoft.AI.Web"/>
</httpModules>
</system.web>
<system.serviceModel>
<serviceHostingEnvironment multipleSiteBindingsEnabled="true" />
<bindings>
<webHttpBinding>
<binding name="ServiceWebBindingName" transferMode="Streamed" maxReceivedMessageSize="2147483647" >
<readerQuotas maxArrayLength="2147483647" maxStringContentLength="2147483647" />
</binding>
</webHttpBinding>
</bindings>
<behaviors>
<endpointBehaviors>
<behavior name="DefaultRestServiceBehavior">
<webHttp defaultOutgoingResponseFormat="Json" defaultBodyStyle="Wrapped" automaticFormatSelectionEnabled="false"/>
</behavior>
</endpointBehaviors>
<serviceBehaviors>
<behavior name="">
<serviceMetadata httpGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="true" />
</behavior>
</serviceBehaviors>
</behaviors>
<services>
<service name="My.App.Service">
<endpoint address="myService.svc"
binding="webHttpBinding"
bindingConfiguration="ServiceWebBindingName"
behaviorConfiguration="DefaultRestServiceBehavior"
name="FileManagerServiceEndpoint"
contract="My.App.IService"/>
</service>
</services>
</system.serviceModel>
<system.webServer>
<modules runAllManagedModulesForAllRequests="true">
<remove name="ApplicationInsightsWebTracking"/>
<add name="ApplicationInsightsWebTracking" type="Microsoft.ApplicationInsights.Web.ApplicationInsightsHttpModule, Microsoft.AI.Web"
preCondition="managedHandler"/>
</modules>
<!--
To browse web app root directory during debugging, set the value below to true.
Set to false before deployment to avoid disclosing web app folder information.
-->
<directoryBrowse enabled="false"/>
<validation validateIntegratedModeConfiguration="false"/>
</system.webServer>
</configuration>
So i found out i did not notice, that my original service url was
http://myapp.azurewebsites.net/myService.svc/MyMethod?MyParam=MyValue
and one that i was redirected to after adding azure ad authentication was
https://myapp.azurewebsites.net/myService.svc/MyMethod?MyParam=MyValue
So redirect points to HTTPS and not HTTP.
It is probably possible to set azure ad application not enforce HTTPS traffic, however in spirit of security and to avoid possible IE zone switching issues I added HTTPS transport to my WCF binding in web.config. Here is great example how to do it How to enable HTTPS on WCF RESTful Service? and it now works flawlessly.

WCF only accepts localhost

I'm writing my first WCF in Visual Studio Express, and configured it to run under IIS Express. My Web.config is as bellow. From my browser I can access the service if I do a Get request on http://localhost:50000/Service1.svc, but not http://10.0.0.26:50000/Service1.svc where 10.0.0.26 is my ip. How to configure WCF of IIS Express to accept IP addresses. Ultimatly my service is tio be reached accross the network.
<?xml version="1.0"?>
<configuration>
<appSettings>
<add key="aspnet:UseTaskFriendlySynchronizationContext" value="true" />
</appSettings>
<system.web>
<compilation debug="true" targetFramework="4.5" />
<httpRuntime targetFramework="4.5"/>
</system.web>
<system.serviceModel>
<services>
<service name="medSaveWCF.Service1">
<endpoint address="../Service1.svc"
binding="webHttpBinding"
contract="medSaveWCF.IService1"
behaviorConfiguration="webBehaviour" />
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior>
<!-- To avoid disclosing metadata information, set the values below to false before deployment -->
<serviceMetadata httpGetEnabled="true" 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>
<endpointBehaviors>
<behavior name="webBehaviour">
<webHttp/>
</behavior>
</endpointBehaviors>
</behaviors>
<protocolMapping>
<add binding="basicHttpsBinding" scheme="https" />
</protocolMapping>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" />
</system.serviceModel>
<system.webServer>
<httpProtocol>
<customHeaders>
<add name="Access-Control-Allow-Origin" value="*" />
<add name="Access-Control-Allow-Headers" value="Content-Type, Accept" />
</customHeaders>
</httpProtocol>
<modules runAllManagedModulesForAllRequests="true"/>
<!--
To browse web app root directory during debugging, set the value below to true.
Set to false before deployment to avoid disclosing web app folder information.
-->
<directoryBrowse enabled="true"/>
</system.webServer>
</configuration>
(Side note: I have blogged about this, including additional setup steps you need to use SSL: http://blog.kutulu.org/2015/01/using-iis-express-with-remote-systems.html)
The problem is that IIS Express only listens on the localhost address, by default. The reason is, IIS runs as a user process, but uses the same HTTPD.SYS system library that the full IIS does. By default the HTTPD.SYS configuration does not allow user processes to bind to an external address. To fix this you'll need to do three things:
Edit the IIS configuration to bind to a new port
Update HTTPD.SYS to permit your user to use that new binding.
Tell WCF you have multiple bindings.
Step One: IIS Express Setup
The IIS Express configuration is done directly through the XML configuration file, which is found at:
C:\Users\[username]\Documents\IISExpress\config\applicationhost.config
If your project is already set up to work with IIS Express, you'll find a configuration block starting around 150 lines into the file -- look for the XML <sites> tag, and you'll find a <site> element:
<site name="MySolution.MyProject" id="2">
<application path="/" applicationPool="Clr4IntegratedAppPool">
<virtualDirectory path="/"
physicalPath="C:\Projects\MySolution\MyProject" />
</application>
<bindings>
<binding protocol="http" bindingInformation="*:50000:localhost" />
</bindings>
</site>
Inside that <bindings> element is the list of ports and hostnames that IIS Express binds to when running that particular site, you just need to add a new binding element:
<binding protocol="http" bindingInformation="*:50000:10.0.0.26" />
Step Two: HTTPD.SYS Permissions
Full disclosure: this step is optional if you are willing to run Visual Studio and IIS Express as an admin user. But that defeats the entire purpose of IIS Express, which is a user-mode web server, so don't do that.
Instead, you just need to use the netsh command to reconfigure HTTPD.SYS to allow you to bind to the ports you want. Specifically, you need to use the http add urlacl command.
Launch an administrative command prompt and/or PowerShell prompt and do this:
netsh http add urlacl url=http://10.0.0.26:5000 user=Everyone
Once both are done, shut down IIS Express so VS will restart it, and you should be all set.
I wrote myself a small Powershell script to go through and do this for a whole range of ports:
$LowPort = 50000
$RangeSize = 99
for ( $i = 0; $i -le $RangeSize; $i++ )
{
netsh http delete urlacl url="http://${IISHost}:$($LowPort + $i)/"
netsh http add urlacl url="http://${IISHost}:$($LowPort + $i)/" user=Everyone
}
That way I don't have to remember to do this every time, I just need to use a port in the 50000 - 50100 range.
Step Three: Inform WCF
By default, WCF only binds to one site per project. For real IIS this is fine, because that's the *:80 binding you probably want. For IIS Express, you need separate bindings per IP address so you need to tell WCF to use them all. This is easy, just add this to your WCF configuration:
<configuration>
<system.serviceModel>
<serviceHostingEnvironment multipleSiteBindingsEnabled="true" />
</system.serviceModel>
</configuration>
Once all that's done, shut down IIS Express and let VS restart it and you should be all set.

WCF Discovery working only wcf service called

Ok let's start.
I make wcf service and publish it in IIS 7.5 via Visual Studio 2012.
The service is running and works well when I use WCF test client.
To enable UDP discovery, i make some adjustment on app.config as follow:
<system.serviceModel>
<services>
<service name="System_Core.wcf_service">
<endpoint address="" binding="wsHttpBinding" contract="System_Core.IWCF_Service" />
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
<endpoint isSystemEndpoint="true" kind="udpDiscoveryEndpoint" />
<host>
<baseAddresses>
<add baseAddress="http://localhost:8888" />
</baseAddresses>
</host>
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior>
<!-- To avoid disclosing metadata information,
set the values below to false before deployment -->
<serviceMetadata httpGetEnabled="False" httpsGetEnabled="False" />
<!-- 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="True" />
<serviceDiscovery />
</behavior>
</serviceBehaviors>
</behaviors>
That file also copied to web.config when publish. In short, the wcf service itself works good.
The problem is this:
I make another project which need to discover wcf service on network. But the discovery only works AFTER first calling or navigate manually to the .svc in the browser. When I restart the IIS or wcf in IIS7 manager, discovery back to failed/no any response received. It seems the wcf is not automatically started. I check on IIS config it start automatically. The site itself also set to start automatically. I even put some of these line in applicationHost.config base on some receommendation on the web:
<site name="WCF" id="1" serverAutoStart="true">
<application path="/" serviceAutoStartEnabled="true" >
<virtualDirectory path="/" physicalPath="C:\inetpub\wwwroot\WCF" />
</application>
<bindings>
<binding protocol="http" bindingInformation="*:8888:" />
</bindings>
</site>
<applicationPools>
<add name="DefaultAppPool" startMode="AlwaysRunning" managedRuntimeVersion="v4.0" />
<add name="ASP.NET v4.0" managedRuntimeVersion="v4.0" />
<add name="ASP.NET v4.0 Classic" managedRuntimeVersion="v4.0" managedPipelineMode="Classic" />
<applicationPoolDefaults>
<processModel identityType="ApplicationPoolIdentity" loadUserProfile="true" setProfileEnvironment="false" />
</applicationPoolDefaults>
</applicationPools>
Still didn't work! IIS said it 'Started', Site said it 'Started', but discovery still only works after when the service .svc file called manually in the browser. When you restart the server or site, it backs to not work. I believe any service SHOULD be start when IIS start, but it won't.
I found on the internet some others have same problem and still no resolve.
I give up and skip all the confusing and install the initialization page extension for IIS, described by the great Schwammy in this page:
http://www.schwammysays.net/auto-starting-websites-on-iis-7-5/
It works great, simple!

Seeting the default page of the svc-less WCF service

I made a 'Hello World' WCF service. The service doesn't have a svc file.
Therefore the web.config file determines its address and other settings.
However, I'm stuck with opening the specific page.
When I run the WCF service project, it always shows the error page (HTTP Error 403.14).
This is because the browser tries to go to localhost instead of localhost/HelloWorldService.svc.
Do you know how can I solve it?
I want to open the localhost/HelloWorldService.svc when I start running the WCF service in VS 2012.
<?xml version="1.0"?>
<!--
For more information on how to configure your ASP.NET application, please visit
<a href="http://go.microsoft.com/fwlink/?LinkId=169433">http://go.microsoft.com/fwlink/?LinkId=169433
-->
<configuration>
<system.web>
<compilation debug="false" targetFramework="4.5"/>
<httpRuntime targetFramework="4.5"/>
</system.web>
<system.serviceModel>
<serviceHostingEnvironment >
<serviceActivations>
<add factory="System.ServiceModel.Activation.ServiceHostFactory"
relativeAddress="HelloWorldService.svc"
service="HelloWorldService.HelloWorldService"/>
</serviceActivations>
</serviceHostingEnvironment>
<behaviors>
<serviceBehaviors>
<behavior>
<serviceMetadata httpGetEnabled="true"/>
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
</configuration>
Go to Properties -> Web -> click Specific Page and enter /HelloWorldService.svc.

WCF REST communication with Windows Phone

I'm having some trouble regarding a rest request sent from my Windows Phone app via RestSharp to a self hosted WCF service I have made. The request works fine in the emulator and the request returns a result but when I try to perform the request on my app on an actual device it fails and the request does not return anything.
Having done a fair amount of research I have discovered that apparently webHttpBinding is not supported on Windows Phone and therefore I need to add a basicHttpBinding endpoint to my Web.config file. However, when I try to do this I get several errors associated with having more than one endpoint and I can't seem to get any solution to successfully work with both of these. Switching the endpoint to basicHttpBinding and commenting out webHttpBinding also causes an error when I navigate to localhost:81/mywebservice.svc
"System.InvalidOperationException: For request in operation analyseFace to be a stream the operation must have a single parameter whose type is Stream."
asking for the stream that should be attached with a request. It shows a normal help page using the original code and the same address.
My web.config file
<trace>
<listeners>
<add type="Microsoft.WindowsAzure.Diagnostics.DiagnosticMonitorTraceListener, Microsoft.WindowsAzure.Diagnostics, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
name="AzureDiagnostics">
<filter type="" />
</add>
</listeners>
</trace>
</system.diagnostics>
<system.web>
<compilation debug="true" targetFramework="4.0" />
</system.web>
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior>
<!-- 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>
<behavior name="servicebehavior">
<!--<serviceMetadata httpGetEnabled="true"/>-->
<serviceDebug includeExceptionDetailInFaults="true" httpHelpPageEnabled="true"/>
</behavior>
</serviceBehaviors>
<endpointBehaviors>
<behavior name="restbehavior">
<webHttp/>
</behavior>
</endpointBehaviors>
</behaviors>
<services>
<service behaviorConfiguration="servicebehavior" name="VizageAPIWebRole.vizage">
<endpoint address="" behaviorConfiguration="restbehavior" binding="webHttpBinding" name="RESTEndPoint" contract="VizageAPIWebRole.Ivizage" />
<!--<endpoint address="" binding="basicHttpBinding" contract="VizageAPIWebRole.Ivizage" />-->
</service>
</services>
<serviceHostingEnvironment multipleSiteBindingsEnabled="true" />
<bindings>
<webHttpBinding>
<binding name="RestBinding">
<readerQuotas maxStringContentLength="5242880" maxArrayLength="16384" maxBytesPerRead="4096" />
<security mode="None">
</security>
</binding>
</webHttpBinding>
</bindings>
</system.serviceModel>
<system.webServer>
<modules runAllManagedModulesForAllRequests="true"/>
</system.webServer>
My WCF SERVICE
[ServiceContract]
public interface Ivizage
{
[WebInvoke(UriTemplate = "/agerecog/{auth}", Method = "POST")]
VizageResult analyseFace(string auth, Stream dataStream);
}
I'm wondering if someone can help me work out what code I have to edit in my web.config to make this work for both endpoints as I haven't had any luck so far with things I've tried
Your code seems to be a REST service, but basicHttpBinding supports SOAP. So please continue to use webHttpBinding. However, on the Windows Phone side, the WCF cannot be used, it is needed to use WebClient or HttpWebRequest to access a REST service. WCF can just help us to access a SOAP service.
In addition, you mentioned the application works fine in the emulator, so it is unlikely to be a coding issue. It would be better if you can check whether your real phone device is able to access internet. Please also make sure you're using the cloud service's address instead of 127.0.0.1. Local address will not work on a real device.
Best Regards,
Ming Xu.
From the exception and your config - you are using simplified configuration which by default uses basicHttpBinding. You have to explicity define service end point
<services>
<service name="WcfRest.Ivizage">
<endpoint behaviorConfiguration="restbehavior" binding="webHttpBinding"
contract="WcfRest.Ivizage" />
</service>
</services>
Edit: looks like webHttp is not supported