1

I have two projects, one is WCF Service, which is to speak a text/sentence in a text box.

public class Service1 : IService1
{
    public string RunTts(string text)
    {
        using (SpeechSynthesizer synth = new SpeechSynthesizer())
        {
            // Configure the audio output. 
            synth.SetOutputToDefaultAudioDevice();
            synth.Speak(text);
            return "";
        }
    }
}

Then I call it with ajax in the _Layout.cshtml page in the second project, which is asp.net mvc.

<script type="text/javascript">
    function ttsFunction() {
        serviceUrl = "Service1.svc/RunTts";

        $.ajax({
            type: "POST",
            url: serviceUrl,
            data: '{"text": "' + $('#speak').val() + '"}',
            contentType: "text/xml; charset=utf-8",
            dataType: "text/xml",
            error: function (xhr,status,error) {
                console.log("Status: " + status); // got "error"
                console.log("Error: " + error);   // got "Not Found"
                console.log("xhr: " + xhr.readyState); // got "4"
            },
            statusCode: {
                404: function() {
                    console.log("page not found"); // got 
                }
            }
        });
    }
</script>

Because I got 404 error, so I think the url is wrong. Please see the structure of files, the web reference is called 'ServiceReference1' I guess. picture

John Saunders
  • 160,644
  • 26
  • 247
  • 397
  • Do you want to build a talking server? As soon as you deploy this service in IIS, it won't do what I think you expect it to do. – CodeCaster Jul 05 '13 at 13:27
  • No, I don't want to build a talking server. Why I failed in localhost? –  Jul 05 '13 at 13:30
  • I'm not responding to the WCF part, I don't get what's going wrong there (though I guess you made up the URL `Service1.svc/RunTts`), is it a SOAP binding? But I mean that you try to synthesize speech from a WCF service. That might work on your development machine, but as soon as you deploy it to IIS, you nor your website's visitors will hear any sound. – CodeCaster Jul 05 '13 at 15:37
  • FYI, that's a service reference, not a web reference. – John Saunders Jul 09 '13 at 23:45

1 Answers1

5

As shown in your screenshot, the service is not hosted in your web application. You cannot access such a service (hosted outside of your web application) directly from the client side, because you're violating the same origin policy restriction. It's one of the underlying concepts of trust, on which web security is based on (e.g. protection aganist XSS) - you cannot send cross domain AJAX requests. This essentially states that if content from one site (e.g. https://bank.ny.com) is granted permission to access resources on the system, then any content from that site will share these permissions, while content from another site (https://nsa.ny.com) will have to be granted permissions separately (in general, the term origin is defined using the domain name, application layer protocol, and port number).

Nevertheless, you have at least 4 solutions to solve your problem:

First - talk to your service through the middle-controller layer. Going this way implies to have proxy class generated (by svcutil.exe, what you have done by adding service reference using Visual Studio). Communication with this client looks like below:

public class TtsController
{
    public JsonResult RunTts(string text)
    {                        
        using(var client = new ServiceReference1.Service1Client())
        {
            var response = client.RunTts(text);
            return Json(response);
...

The JavaScript side should then use such an URL: var serviceUrl = "/Tts/RunTts" (along with proper JSON data passing to the AJAX request, which I'll go through a bit further).

Second - talk directly to the service. If you want to communicate directly with the service, you have to host this service in your web application. The correct WCF configuration should be followed to support RESTful services:

<system.serviceModel>
  <behaviors>
    <endpointBehaviors>
      <behavior name="webby">
        <webHttp />
      </behavior>
    </endpointBehaviors>
    <serviceBehaviors>
      <behavior>
        <serviceMetadata httpGetEnabled="true" />
      </behavior>
    </serviceBehaviors>
  </behaviors>
  <services>
    <service name="Namespace.Service1">
      <endpoint address="" 
                behaviorConfiguration="webby"
                binding="webHttpBinding" 
                contract="Namespace.IService1" />
    </service>
  </services>  
</system.serviceModel>

For a RESTful endpoint, the binding you should use is WebHttpBinding along with appropriate behavior. Alternatively there is configuration-free experience for many RESTful services - WebServiceHostFactory. Your .svc file should look like below (MSDN):

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

WebServiceHostFactory creates an instance of the WebServiceHost, and since the WebServiceHost will auto-configure the endpoint using WebHttpBinding and related behavior, there doesn't need to be any configuration for this endpoint in the web.config at all (of course, if you need to customize the binding, you have to use the configuration) (MSDN).

Then to access the service use appropriate full URL: http://localhost:[port]/Service1.svc/RunTts or relative one: /Service1.svc/RunTts.

Since you're using ASP.NET MVC, based on your routes definitions, the request will dispatched to some controller, where such an action doesn't exist. You have to tell MVC to ignore route to your service:

routes.IgnoreRoute("{resource}.svc/{*pathInfo}");

(BTW: If you put your .svc file under different directory within your application, modify respectively URL and route to ignore.)

Your code needs some additional fixes:

  1. If you want to send message in JSON format, specify dataType and contentType parameters correctly:

    $.ajax({
        url: serviceUrl,
        type: "POST",
        dataType: "json",
        contentType: "application/json; charset=utf-8",
        ...
    
  2. Do not construct your JSON strings manually, as it can lead to further parsing errors - use converters e.g.:

    var data = new Object();
    data.text = $('#speak').val();
    var jsonString = JSON.stringify(data);
    
    $.ajax({
        ...
        data: jsonString,
        ...
    
  3. Provide additional declarative information to your service:

    [ServiceContract]
    public interface IService1
    {
        [OperationContract]
        [WebInvoke(Method = "POST", RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json, BodyStyle = WebMessageBodyStyle.Wrapped)]        
        string RunTts(string text);
    ...
    
  4. Remove service reference from the project. You don't need it as there is no usage of middle-controller here.

Third - JSONP (look here and here) can be used to overcome origin policy restriction. But you can't POST using JSONP because it just doesn't work that way - it creates a <script> element to fetch data, which has to be done via GET request. JSONP solution doesn't use XmlHttpRequest object, so it is not an AJAX request in the standard way of understanding, but the content is still accessed dynamically - no difference for the end user.

$.ajax({
    url: serviceUrl,
    dataType: "jsonp",
    contentType: "application/json; charset=utf-8",
    data: data,
    ...

[OperationContract]         
[WebGet(RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json, BodyStyle = WebMessageBodyStyle.Wrapped, UriTemplate="RunTts?text={text}")]
public string RunTts(string text);

RESTful WCF configuration with cross domain requests allowed:

<system.serviceModel>
  <bindings>
    <webHttpBinding>
      <binding name="jsonp" crossDomainScriptAccessEnabled="true" />
    </webHttpBinding>
  </bindings>
  <behaviors>
    <endpointBehaviors>
      <behavior name="webby">
        <webHttp />
      </behavior>
    </endpointBehaviors>
    <serviceBehaviors>
      <behavior>
        <serviceMetadata httpGetEnabled="true" />
      </behavior>
    </serviceBehaviors>
  </behaviors>
  <services>
    <service name="Namespace.Service1">
      <endpoint address="" 
                behaviorConfiguration="webby"
                binding="webHttpBinding" 
                bindingConfiguration="jsonp"
                contract="Namespace.IService1" />
    </service>
  </services>
</system.serviceModel>

Fourth - CORS. Implemented in modern browsers alternative to JSON with Padding.

Community
  • 1
  • 1
jwaliszko
  • 16,942
  • 22
  • 92
  • 158
  • Still wrong, all error. Should I change basicHttpsBinding to something else. I think it should be change automatically since I added REST attribute to the service. –  Jul 05 '13 at 16:09
  • What error do you get now? (By the way I've edited the answer to add some more information.) – jwaliszko Jul 05 '13 at 17:55
  • Let me add some points in my question. Please see it. Okay, I used the second method. I removed the service reference and start the service project. Then debug my mvc project instance, the error: NetworkError: 400 Bad Request - http://localhost:55433/Service1.svc/RunTts". I haven't found endpoint in both web.config. –  Jul 05 '13 at 18:16
  • I think that this issue can be connected with violating the same origin policy restriction (http://en.wikipedia.org/wiki/Same_origin_policy), because you cannot send cross domain AJAX requests. JSONP (SO: http://stackoverflow.com/questions/2067472/what-is-jsonp-all-about, MSDN: http://msdn.microsoft.com/en-us/library/ee834511.aspx) can be used to overcome this. If I were you I'd try to go through it now, as I have no other ideas. – jwaliszko Jul 05 '13 at 18:59
  • The service was created by myself, just a few lines. I don't have any restrictions, actually I don't know how to add them at all. –  Jul 05 '13 at 20:14
  • @Love: I've done some bigger edit of the answer and tried to explain a couple of concepts important in this thread. I think it should give more comprehensive view of what should be taken under consideration here. – jwaliszko Jul 09 '13 at 23:24