服务参考的正确URL是什么?

时间:2013-07-05 13:09:23

标签: wcf jquery asp.net-mvc-4

我有两个项目,一个是WCF服务,即在文本框中说出文本/句子。

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

然后我在第二个项目的_Layout.cshtml页面中使用ajax调用它,这是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>

因为我收到404错误,所以我认为网址错了。请参阅文件的结构,我猜这个Web引用叫做'ServiceReference1'。 picture

1 个答案:

答案 0 :(得分:5)

如屏幕截图所示,该服务未托管在您的网络应用程序中。您无法直接从客户端访问此类服务(在Web应用程序之外托管),因为您违反了same origin policy限制。它是信任的基本概念之一,Web安全基于该概念(例如,保护aganist XSS) - 您无法发送跨域AJAX请求。这基本上表明,如果来自一个站点(例如https://bank.ny.com)的内容被授予访问系统资源的权限,那么来自该站点的任何内容将共享这些权限,而来自另一个站点的内容(https://nsa.ny.com)必须单独授予权限(通常,术语 origin 是使用域名,应用程序层协议和端口号定义的。)

尽管如此,您至少有4种解决方案可以解决您的问题:

首先 - 通过中间控制器层与您的服务对话。这样做意味着生成代理类(通过 svcutil.exe ,通过使用Visual Studio添加服务引用所做的工作)。与此客户的通信如下所示:

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

然后JavaScript方应该使用这样的URL:var serviceUrl = "/Tts/RunTts"(以及传递给AJAX请求的正确JSON数据,我将进一步讨论)。

第二次 - 直接与服务对话。如果要直接与服务通信,则必须在Web应用程序中托管此服务。应遵循正确的WCF配置以支持RESTful服务:

<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>

对于RESTful端点,您应该使用的绑定是WebHttpBinding以及适当的行为。或者,许多RESTful服务都具有免配置体验 - WebServiceHostFactory。您的.svc文件应如下所示(MSDN):

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

WebServiceHostFactory创建WebServiceHost的实例,由于WebServiceHost将使用WebHttpBinding和相关行为自动配置端点,因此不需要web.config中对此端点的任何配置(当然,如果您需要自定义绑定,则必须使用配置)(MSDN)。

然后,要访问该服务,请使用相应的完整网址:http://localhost:[port]/Service1.svc/RunTts或相对的网址:/Service1.svc/RunTts

由于您正在使用ASP.NET MVC,根据您的路由定义,请求将被分派到某个控制器,此类操作不存在。你必须告诉MVC忽略你的服务路线:

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

(顺便说一句:如果你将.svc文件放在应用程序的不同目录下,分别修改URL和路由以忽略。)

您的代码需要一些其他修补程序:

  1. 如果您想以JSON格式发送消息,请正确指定dataTypecontentType参数:

    $.ajax({
        url: serviceUrl,
        type: "POST",
        dataType: "json",
        contentType: "application/json; charset=utf-8",
        ...
    
  2. 不要手动构造JSON字符串,因为它可能导致进一步的解析错误 - 使用转换器,例如:

    var data = new Object();
    data.text = $('#speak').val();
    var jsonString = JSON.stringify(data);
    
    $.ajax({
        ...
        data: jsonString,
        ...
    
  3. 为您的服务提供其他声明性信息:

    [ServiceContract]
    public interface IService1
    {
        [OperationContract]
        [WebInvoke(Method = "POST", RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json, BodyStyle = WebMessageBodyStyle.Wrapped)]        
        string RunTts(string text);
    ...
    
  4. 从项目中删除服务引用。你不需要它,因为这里没有使用中间控制器。

  5. 第三次 - JSONP (外观herehere)可用于克服原始政策限制。但是你不能使用JSONP进行POST,因为它只是doesn't work that way - 它创建了一个<script>元素来获取数据,这必须通过GET请求来完成。 JSONP解决方案不使用XmlHttpRequest对象,因此它不是标准理解方式的AJAX请求,但内容仍然是动态访问的 - 对最终用户没有区别。

    $.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配置:

    <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>
    

    第四 - CORS 。在现代浏览器alternative中使用Padding在JSON中实现。