WebGet with No Parameters or UriTemplate Fails
Asked Answered
S

2

14

I have a RESTful WCF web service with the following API:

[WebGet(ResponseFormat = WebMessageFormat.Json)]
MyResponseContract GetFileInfo();

When attempting to hit endpoint (using SOAPUI) I see the following error message:

The server encountered an error processing the request. Please see the service help page for constructing valid requests to the service.

I have SOAPUI set to hit it with a GET method call. When I switch it to a POST with no body, it fails with the following message:

Method not allowed.

This makes perfect sense: can't hit a GET with a POST. So I updated my code as follows:

[WebInvoke(ResponseFormat = WebMessageFormat.Json)]
MyResponseContract GetFileInfo();

And now I call it from SOAPUI with a POST method and it works. Curious. So I now change my code as follows:

[WebInvoke(ResponseFormat = WebMessageFormat.Json, Method = "GET")]
MyResponseContract GetFileInfo();

I've seen in a few posts that this is essentially equivalent to a WebGet attribute. This also does not work.

So my question is: why doesn't this work as a WebGet even though I am not accepting parameters or using a custom UriTemplate?

The Url I'm trying to hit it with (it's hosted locally in IIS) is:

http://localhost/Utilities/API/GetFileInfo

Update Given the comments below and the given answers, I am still faced with this problem. Some additional details.

My web-layer web.config

<?xml version="1.0"?>
<configuration>
  <system.web>
    <compilation debug="true" targetFramework="4.0" />
    <customErrors mode="Off" />
  </system.web>
  <system.serviceModel>
    <serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" />
    <standardEndpoints>
      <webHttpEndpoint>
        <standardEndpoint name="" helpEnabled="true" automaticFormatSelectionEnabled="true" maxReceivedMessageSize="10000000" />
      </webHttpEndpoint>
    </standardEndpoints>
    <behaviors>
      <endpointBehaviors>
        <behavior name="exampleBehavior">
          <callbackDebug includeExceptionDetailInFaults="true" />
          <enableWebScript />
          <webHttp helpEnabled="true" />
        </behavior>
      </endpointBehaviors>
    </behaviors>
    <bindings>
      <webHttpBinding>
        <binding name="WebHttpBinding" maxReceivedMessageSize="10000000" />
      </webHttpBinding>
    </bindings>
    <client>    
      <endpoint address="http://LOCALHOST/Utilities.AppService/API"
                binding="webHttpBinding" bindingConfiguration="WebHttpBinding"
                contract="Utilities.Common.API.IMyApi"
                behaviorConfiguration="exampleBehavior" />
    </client>
  </system.serviceModel>
</configuration>

My app-layer web.config:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <system.web>
    <compilation debug="true" targetFramework="4.0" />
    <customErrors mode="Off" />
  </system.web>
  <system.serviceModel>
    <serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" />
    <standardEndpoints>
      <webHttpEndpoint>
        <standardEndpoint name="" helpEnabled="true" automaticFormatSelectionEnabled="true" maxReceivedMessageSize="10000000" />
      </webHttpEndpoint>
    </standardEndpoints>
  </system.serviceModel>
  <system.webServer>
    <modules runAllManagedModulesForAllRequests="true" />
    <handlers>
      <remove name="ExtensionlessUrlHandler-Integrated-4.0" />
      <add name="ExtensionlessUrlHandler-Integrated-4.0" path="*." verb="GET,HEAD,POST,DEBUG,PUT,DELETE" type="System.Web.Handlers.TransferRequestHandler" resourceType="Unspecified" requireAccess="Script" preCondition="integratedMode,runtimeVersionv4.0" />
    </handlers>
  </system.webServer>
</configuration>

My service interface

[ServiceContract(Namespace = "API")]
public interface IMyApi
{    
    [WebGet]
    MyResponseContract GetFileInfo();
}

My web-layer implementation

[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Required)]
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, ConcurrencyMode = ConcurrencyMode.Multiple)]
public class MyApiWebService : ClientBase<IMyApi>, IMyApi
{
    public MyResponseContract GetFileInfo()
    {
        return Channel.GetFileInfo();
    }
}

My app-layer implementation

[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Required)]
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, ConcurrencyMode = ConcurrencyMode.Multiple)]
public class MyApiAppService : IMyApi
{
    public MyResponseContract GetFileInfo()
    {
        return new MyResponseContract();
    }
}

My web-layer Global.asax:

    protected void Application_Start(object sender, EventArgs e)
    {
        RouteTable.Routes.Add(new ServiceRoute("API", new WebServiceHostFactory(), typeof(MyApiWebService)));
    }

My app-layer Global.asax:

    protected void Application_Start(object sender, EventArgs e)
    {
        RouteTable.Routes.Add(new ServiceRoute("API", new WebServiceHostFactory(), typeof(MyApiAppService)));
    }

I'm not sure how much more detail I can provide. As you can see, given the solutions provided, I have implemented everything suggested to no avail. Whether I am trying to hit this WebGet method by placing the web layer service url in the browser, or using SOAPUI, or trying to hit it with a C# unit test using a service client, I am unable to use WebGet. Thanks again for all of your help.

And interesting note is that the App-layer URL works. But the web layer does not. So:

localhost/Utilities.AppService/API/GetFileInfo

works, whereas

localhost/Utilities.WebService/API/GetFileInfo

does not.

Starveling answered 19/5, 2015 at 16:30 Comment(6)
How are you accessing it - like a URI sample? And to be clear the last one didn't work either?Fortuity
Updated question with requested details.Starveling
Are you using WebHttpBinding?Fortuity
Yes, I'm using a standard webHttpBinding.Starveling
try to browse to localhost/Utilities/API/GetFileInfo (add '/' to the end of the link)Demigod
Adding a trailing slash makes no differenceStarveling
S
5

So this may not have been obvious until I updated recently, but I have two RESTful services that communicate with each other but live in separate domains. The Web-Layer service is the first point of contact and the App-Layer service is the actual work-doer. This being the case, I was able to debug a bit further and found that the actual exception was a 405 (Method Not Allowed) on the call from the Web to App layers. I found this link after much digging that solved my issue.

When using ClientBase<> as you method of communication between services you essentially need to reestablish the operation context between calls. Otherwise everything becomes a POST and, as such, only POSTs work.

I hope this helps others, and I greatly appreciate everyone's help in debugging this.

To demonstrate what this looks like, here is what my updated, working web service implementation looks like:

[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Required)]
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, ConcurrencyMode = ConcurrencyMode.Multiple)]
public class MyApiWebService : ClientBase<IMyApi>, IMyApi
{
    public MyResponseContract GetFileInfo()
    {
        MyResponseContract output = null;

        using(var context = new OperationContext(Channel as IContextChannel))
        {
            output = Channel.GetFileInfo();
        }

        return output;
    }
}
Starveling answered 26/5, 2015 at 21:7 Comment(0)
E
4

The .NET WCF web service by default is set up to send text encoded SOAP messages. This means that the HTTP method is POST and there are required headers to tell the service what method to call.

I created a quick example using your service endpoint and here is the request generated from fiddler to talk to that endpoint.

POST http://localhost/Utilities/API/GetFileInfo/Service1.svc HTTP/1.1
Content-Type: text/xml; charset=utf-8
SOAPAction: "http://tempuri.org/IService1/GetFileInfo"
Host: localhost:8888
Content-Length: 136
Expect: 100-continue
Connection: Keep-Alive

<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"><s:Body><GetFileInfo xmlns="http://tempuri.org/"/></s:Body></s:Envelope>

Getting back a response of

HTTP/1.1 200 OK
Cache-Control: private
Content-Length: 398
Content-Type: text/xml; charset=utf-8
Server: Microsoft-IIS/8.0
X-AspNet-Version: 4.0.30319
X-SourceFiles: =?UTF-8?B?QzpcV29ya1xFSVAgV29ya1xRdWVzdGlvbnNcV0NGR2V0VGVzdFxTZXJ2aWNlMS5zdmM=?=
X-Powered-By: ASP.NET
Date: Thu, 21 May 2015 19:47:49 GMT

<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"><s:Body><GetFileInfoResponse xmlns="http://tempuri.org/"><GetFileInfoResult xmlns:a="http://schemas.datacontract.org/2004/07/WCFGetTest" xmlns:i="http://www.w3.org/2001/XMLSchema-instance"><a:BoolValue>true</a:BoolValue><a:StringValue>MyResponseContract </a:StringValue></GetFileInfoResult></GetFileInfoResponse></s:Body></s:Envelope>

For you I think something in your SoapUI is not set correctly. Either the post data or the headers.

Ecstatics answered 21/5, 2015 at 19:49 Comment(2)
But when I simply place the URL in the browser it returns the same response.Starveling
If you want to expose a get method returning JSON you have to change your binding to do so. Take a look at #187131Ecstatics

© 2022 - 2024 — McMap. All rights reserved.