4

I've set up the following interface.

[ServiceContract]
public interface IService1
{
  [OperationContract]
  String Ping();
}

Its implementation is as follows.

public class Service1 : IService1
{
  public string Ping(){ return "Pong"; }
}

According to the testing application in VS it's working properly when invoked. My problem is that I'd like the text to appear on the screen when I type http://localhost:12345/Service1.svc (or maybe Service1.svc?Ping or Service.svc/Ping). Is it totally off or am I barking up the right tree?

Of course, "Pong" will eventually be an XML strucure.

EDIT

The set-up presented in the reply by @carlosfigueira below gives a good structure to a suggestion for a solution but unfortunately leads on my machine to an error message when run using F5. It seems that metadata is required and that the same goes for endpoints.

  • possible duplicate of [Invoking WCF service method through a browser](http://stackoverflow.com/questions/802518/invoking-wcf-service-method-through-a-browser) – Adriano Repetti Nov 12 '12 at 14:41
  • @Adriano It's not even **close** to that post. Either I'm misreading it or you've misread mine. :) –  Nov 12 '12 at 14:53
  • you need to invoke a function through the browser (probably with AJAX?!), right? Well it says how and it gives you an example of the URI you have to use (please read all answers). If your result is XML/JSON or plain string it really doesn't matter. Correct me if you need something else too! – Adriano Repetti Nov 12 '12 at 14:59
  • Ah, I don't have a WCF service (yet!) and I haven't set up any HTTP bindings (or have I unknowingly?) so I gave up after the OP. My bad, I should have read more. Nevertheless, I'm still not sure what I'm doing and the stuff that @carlosfigueira posted make me wish to cry, at the moment. The downvoted post I understand **but** apparently it's not a fully satisfactory solution. –  Nov 12 '12 at 15:25
  • carlos' method is right and you can't escape to configure the service somehow (it needs an endpoint!). The only case you do not need to do it (somehow) is if your method isn't a standalone web service but a web site and Ping() is just a method invoked from JavaScript. – Adriano Repetti Nov 12 '12 at 15:40
  • When I run the service with the change suggested by @carlosfigueira, I get error "*Cannot obtain Metadata from...*". There's also something about meta-data registration. What more do I miss? –  Nov 12 '12 at 15:43
  • A service which produces XML (or JSON) to be consumed by JavaScript is not a SOAP service, it's what is called a POX (Plain-Old XML), REST or WebHTTP service. When you're hitting F5 to run the project, VS is likely launching the WCFTestClient, which is a tool used to test **SOAP** services. That tool [doesn't work for non-SOAP services](http://blogs.msdn.com/b/carlosfigueira/archive/2012/03/26/mixing-add-service-reference-and-wcf-web-http-a-k-a-rest-endpoint-does-not-work.aspx). Try setting the startup file to a HTML page and you won't have that problem. – carlosfigueira Nov 13 '12 at 22:35
  • @carlosfigueira According to [MSDN](http://msdn.microsoft.com/en-us/library/ms731758.aspx), I'm supposed to use WCFTestClient.exe for **all** services (since they don't mention SOAP in the article). I've been trying and trying to get it right but all I get is an empty reply in `alert(xhr + " says: " + xhr.responseText);`. Also, I downloaded your project - would you mind taking a look if it works on your machine? On mine, I get no effect when clicking the buttons. –  Nov 15 '12 at 11:09
  • @carlosfigueira Also, I think it needs to be added that when I execute the following in my browser: `http://localhost:30198/MyService.svc/Ping?renewer=cacheBuster`, I get `Bad request 400` according to FireBug. –  Nov 15 '12 at 11:24
  • @AndreasJohansson believe me, WCFTestClient.exe does not work for non-SOAP services. The blog post at http://blogs.msdn.com/b/carlosfigueira/archive/2012/03/26/mixing-add-service-reference-and-wcf-web-http-a-k-a-rest-endpoint-does-not-work.aspx explains the details why. Also, I added a self-contained project at https://skydrive.live.com/redir?resid=99984BBBEC66D789!6355 with the code in my answer. And as far as the 400 goes, you can enable tracing at the server to see what happened. – carlosfigueira Nov 15 '12 at 18:13

2 Answers2

9

I finally got totally PO and went off to business full contact. This is what I've produced - it works on my machine and I hope it's not a local phenomenon. :)

IRestService.cs - the declaration, what your code promises to a contacting client

[ServiceContract]
public interface IRestService
{
  [OperationContract]
  [WebInvoke(Method = "GET", 
    ResponseFormat = WebMessageFormat.Xml, 
    BodyStyle = WebMessageBodyStyle.Wrapped, 
    UriTemplate = "xml/{id}")]
  String XmlData(String id);

  [OperationContract]
  [WebInvoke(Method = "GET", 
    ResponseFormat = WebMessageFormat.Json, 
    BodyStyle = WebMessageBodyStyle.Wrapped, 
    UriTemplate = "json/{id}")]
  String JsonData(String id);
}

RestService.svc.cs - the implementation, what your code actually does to the client

public class RestService : IRestService
{
  public String XmlData(String id)
  {
    return "Requested XML of id " + id;
  }

  public String JsonData(String id)
  {
    return "Requested JSON of id " + id;
  }
}

Web.config - the configuration, what your code is handled as on the way to the client

<configuration>

  <system.web>
    <compilation debug="true" targetFramework="4.0" />
  </system.web>

  <system.serviceModel>
    <services>
      ...
    </services>
    <behaviors>
    </behaviors>
  </system.serviceModel>

</configuration>

services - contents of the tag describing the service's nature

<service name="DemoRest.RestService" 
         behaviorConfiguration="ServiceBehavior">
  <endpoint address="" binding="webHttpBinding" 
            contract="DemoRest.IRestService" 
            behaviorConfiguration="web"></endpoint>
</service>

behaviors - contents of the tag describing the behavior of the service and the end-point

<serviceBehaviors>
  <behavior name="ServiceBehavior">
    <serviceMetadata httpGetEnabled="true"/>
    <serviceDebug includeExceptionDetailInFaults="true"/>
  </behavior>
</serviceBehaviors>

<endpointBehaviors>
  <behavior name="web">
    <webHttp/>
  </behavior>
</endpointBehaviors>

Index.html - the executor, what your code can be called as

<html>
  <head>
    <script>
      ...
    </script>
    <style>
      ...
    </style>
  </head>
  <body>
    ...
  </body>
</html>

script - contents of the tag describing the executable in JavaScript

window.onload = function () {
  document.getElementById("xhr").onclick = function () {
    var xhr = new XMLHttpRequest();
    xhr.onload = function () { alert(xhr.responseText); }
    xhr.open("GET", "RestService.svc/xml/Viltersten");
    xhr.send();
  }
}

style - contents of the tag describing the appearance

.clickable
{
  text-decoration: underline;
  color: #0000ff;
}

body - contents of the tag describing the markup structure

<ul>
  <li>XML output <a href="RestService.svc/xml/123">
    <span class="clickable">here</span></a></li>
  <li>JSON output <a href="RestService.svc/json/123">
    <span class="clickable">here</span></a></li>
  <li>XHR output <span id="xhr" class="clickable">here</span></li>

Everything is stored in a project called DemoRest. I created my own files for declaration and implementation of the service, removing the default ones. The directives of using as well as the XML version declaration are omitted for spacial reasons.

Now the response can be retrieved using the following URL.

localhost:12345/RestService.svc/xml/Konrad
localhost:12345/RestService.svc/json/Viltersten
  1. Does anybody else get it to work too?
  2. Any suggestions on improvement or clarification?
Konrad Viltersten
  • 36,151
  • 76
  • 250
  • 438
  • The question is about WCF; using `HttpContext` doesn't work in all cases in WCF (won't work if AspNetCompatibilityMode is disabled; won't work if self-hosted) – carlosfigueira Nov 12 '12 at 14:57
  • Hmm... Never had those issues (lucky me, I guess). What can be done about it, then? – Konrad Viltersten Nov 12 '12 at 14:58
  • See my answer. Using a Web endpoint will return XML (or JSON, which seems to be more appropriate given the OP's comment that he wants to use `XMLHttpRequest`). – carlosfigueira Nov 12 '12 at 15:00
  • @carlosfigueira I've tried your approach. It doesn't work on my configuration. I get some nag about meta-data. Not saying that your suggestion is wrong - I'm always willing to adapt improving new ways but at this point (on my machine, that is) my solution works, yours doesn't. Anything obvious that I'm missing? I'll google it for a while, of course, but if you have additional pointers, please don't be shy. :) – Konrad Viltersten Nov 13 '12 at 08:16
  • What is the error you get about metadata? [For web endpoints there is no metadata](http://blogs.msdn.com/b/carlosfigueira/archive/2012/03/26/mixing-add-service-reference-and-wcf-web-http-a-k-a-rest-endpoint-does-not-work.aspx), so if you're trying to enable it, you should just not do that. Also, I'll update my answer with a complete example. – carlosfigueira Nov 13 '12 at 14:37
  • @carlosfigueira First of all - thanks for your time. I appreciate your efforts and I believe that other participants do too. I can't reproduce the error described by the OP (however, being a bit lazy I didn't type in your code exactly as she did). I can start up the service and run it. I also get the results in the testing application that comes up. However, I only get a blank page when I type stuff into the browser's URL field. Would you be kind to upload a working project so we can trace the error? – Konrad Viltersten Nov 13 '12 at 20:15
  • You can get a working project from my GitHub repository at https://github.com/carlosfigueira/WCFQuickSamples/tree/master/WCFForums/QuickWebCode1. – carlosfigueira Nov 13 '12 at 22:37
  • @carlosfigueira I can get **a** project from there but it's not working, exactly. Maybe it's supposed to be executed in another way. Do tell. I get a run-time exception opening the host, in your test method to begin with. I'm looking for a minimal, **working** example of your suggestion - *a service interface, its implementation and a HTML page that accesses it*. If it's doable with your solution, please advise. I'd be thrilled to see if we can make it work. :) When I only execute the `QuickWebCode1`, I see a bunch of buttons but nothing happens when i click them. – Konrad Viltersten Nov 15 '12 at 07:11
  • You can find a minimal project at https://skydrive.live.com/redir?resid=99984BBBEC66D789!6355 I copied the code into an empty web application. If you run the app by browsing to the Default.htm file, you should see it working. – carlosfigueira Nov 15 '12 at 18:10
  • @carlosfigueira **I think I finally made it work.** For some reason I couldn't get your stuff to play along all the way, which was quite frustrating because I could see what you did and where, most of the time. I guess it was a little bit too much, too new and too overwhelming. One can easily lose touch when not everything works out perfectly. Nevertheless, it was very educational. Let's hope that others feel the same way (OP - I'm looking your way). I'd be delighted if you could throw an eye on my updated reply to see if you can see anything improvable, Carlos. – Konrad Viltersten Nov 15 '12 at 21:06
1

If you define your service endpoint as a WebHttp endpoint (a.k.a. REST endpoint), you'll get what you want. The easiest way to do that is to use the WebServiceHostFactory in your svc file:

Service1.svc.

<%@ ServiceHost Language="C#" Debug="true" Service="YourNamespace.Service1"
                Factory="System.ServiceModel.Activation.WebServiceHostFactory" %>

Or you can define the endpoint without the factory, by defining that it will use the webHttpBinding and have a <webHttp/> behavior:

<system.serviceModel>
  <behaviors>
    <endpointBehaviors>
      <behavior name="MyBehavior">
        <webHttp/>
      </behavior>
    </endpointBehaviors>
  </behaviors>
  <services>
    <service name="YourNamespace.Service1">
      <endpoint address=""
                behaviorConfiguration="MyBehavior"
                binding="webHttpBinding"
                contract="YourNamespace.IService1" />
    </service>
  </services>
</system.serviceModel>

Update: Since some people were having issues, I wrote a full example of using XMLHttpRequest to talk to the service listed above. The code can be found at https://github.com/carlosfigueira/WCFQuickSamples/tree/master/WCFForums/QuickWebCode1 (look for StackOverflow_13345557), and it's mostly listed here.

Service code (notice that I'm using JSON as the response, but XML works just as well):

namespace StackOverflow_13345557
{
    [ServiceContract]
    public interface IService1
    {
        [WebGet(ResponseFormat = WebMessageFormat.Json)]
        string Ping();
        [WebGet(ResponseFormat = WebMessageFormat.Json)]
        string PingWithParameters(int a, string b);
    }

    public class Service1 : IService1
    {
        public string Ping()
        {
            return "Hello";
        }

        public string PingWithParameters(int a, string b)
        {
            return string.Format("Hello {0} - {1}", a, b);
        }
    }
}

.SVC file - notice no usage of the Factory attribute, since I'm defining the endpoint via configuration:

<%@ ServiceHost Language="C#" Debug="true" Service="StackOverflow_13345557.Service1"
                CodeBehind="StackOverflow_13345557.svc.cs" %>

web.config:

<system.serviceModel>
  <services>
    <service name="StackOverflow_13345557.Service1">
      <endpoint address=""
                behaviorConfiguration="WithWebHttp"
                binding="webHttpBinding"
                bindingConfiguration="WithJSONP"
                contract="StackOverflow_13345557.IService1" />
    </service>
  </services>
  <behaviors>
    <endpointBehaviors>
      <behavior name="WithWebHttp">
        <webHttp/>
      </behavior>
    </endpointBehaviors>
  </behaviors>
  <bindings>
    <webHttpBinding>
      <binding name="WithJSONP" crossDomainScriptAccessEnabled="true" />
    </webHttpBinding>
  </bindings>
</system.serviceModel>

HTML page accessing service (body only):

<body>
    <script type="text/javascript">
        function StackOverflow_13345557_Test(passParameters) {
            var baseUrl = "/StackOverflow_13345557.svc";
            var cacheBuster = new Date().getTime(); // to prevent cached response; development only
            var url;
            if (passParameters) {
                url = baseUrl + "/PingWithParameters?a=123&b=john+doe&_=" + cacheBuster;
            } else {
                url = baseUrl + "/Ping?_=" + cacheBuster;
            }
            var xhr = new XMLHttpRequest();
            xhr.onreadystatechange = function () {
                if (xhr.readyState == 4) {
                    document.getElementById("result").innerText = xhr.responseText;
                }
            }

            xhr.open('GET', url, true);
            xhr.send();
        }
    </script>
    <input type="button" value="StackOverflow 13345557 (no params)" onclick="StackOverflow_13345557_Test(false);" /><br />
    <input type="button" value="StackOverflow 13345557 (with params)" onclick="StackOverflow_13345557_Test(true);" /><br />
    <div id='result'></div>
</body>

One more update: added a self-contained, minimal project at https://skydrive.live.com/redir?resid=99984BBBEC66D789!6355 with the code listed above.

carlosfigueira
  • 85,035
  • 14
  • 131
  • 171
  • Oh, this was confusing to a rookie... I'll be exposing the XML to a JS-driven `XmlHttpRequest` run on an other server. Due to CORS problems I'll probably need to set the headers too. Easiest way to achieve that? –  Nov 12 '12 at 14:50
  • If you're only making GET requests (which seems to be the case, since you wanted to call the service with a browser), you should consider using JSONP instead of raw XMLHttpRequest. CORS in WCF isn't the easiest thing to be done, while JSONP support is built-in. For an example of it, you can look at http://www.codeproject.com/Articles/259832/Consuming-Cross-Domain-WCF-REST-Services-with-jQue. – carlosfigueira Nov 12 '12 at 14:56
  • I believe my confusion is due to the fact that I don't see where in your suggestion to put in (1) the method in the service that I'm calling and (2) the parameters I'd like to pass in. It's probably something very straight-forward but for somebody who's never done that before, it looks like magic, dark like November nights. –  Nov 12 '12 at 15:18
  • He's got typos in there. I've corrected it. That's **part** of your problem but there's probably more, haha. – Konrad Viltersten Nov 12 '12 at 15:49
  • I've done **exactly** what you said (except that I changed `StackOverflow_13345557` for `WcfService2`). The rest just as you've shown. I can see what stuff does which part, it's pretty clear. Only problem is it doesn't work. When I hit F5, I get a progress bar and when it hits about 50%, I get the following error message. "*Error: Cannot obtain Metadata from http://localhost:52850/Service1.svc If this is a Windows (R) Communication Foundation service to which you have access, please check that you have enabled metadata publishing...*". What to do? I've pasted the full message in your reply. –  Nov 13 '12 at 20:00
  • @AndreasJohansson, see my comment in the question. – carlosfigueira Nov 13 '12 at 22:35