WCF Restful跨域资源共享POST请求失败

3
我阅读了《如何创建WCF Restful》教程,并从WCFTutorial下载了示例代码。 其中包括一个主机(名称:MYFirstRestfulServiceHost)和一个客户端(名称:WebClient)。 WebClient和MYFirstRestfulServiceHost位于不同的域中。 因此,当我进行GET / POST请求时,遇到了一个问题:状态码为405“方法不允许”。
经过两天的研究,我发现我必须在Host app.config中添加一些配置才能在跨域wcf服务上执行GET / POST请求。
在app.config中添加配置后,我成功在WebClient中执行了GET请求,但无法通过按下按钮在WebClient中执行剩余的POST,DELETE和PUT请求。
请指导我还需要配置什么以使其成功。
以下是源代码和配置:

IEmployeeService.cs

namespace MyFirstRESTfulService
{
    [ServiceContract()]
    public interface IEmployeeService
    {
        [WebGet(UriTemplate = "Employee", ResponseFormat = WebMessageFormat.Json)]
        [OperationContract]
        List<Employee> GetAllEmployeeDetails();

        [WebGet(UriTemplate = "Employee?id={id}", ResponseFormat = WebMessageFormat.Json)]
        [OperationContract]
        Employee GetEmployee(int Id);

        [WebInvoke(Method = "POST", UriTemplate = "EmployeePOST", ResponseFormat = WebMessageFormat.Json, RequestFormat = WebMessageFormat.Json)]
        [OperationContract]
        void AddEmployee(Employee newEmp);

        [WebInvoke(Method = "PUT", UriTemplate = "EmployeePUT", ResponseFormat = WebMessageFormat.Json, RequestFormat = WebMessageFormat.Json)]
        [OperationContract]
        void UpdateEmployee(Employee newEmp);

        [WebInvoke(Method = "DELETE", UriTemplate = "Employee/{empId}", ResponseFormat = WebMessageFormat.Json)]
        [OperationContract]
        void DeleteEmployee(string empId);
    }
}

EmployeeService.cs

namespace MyFirstRESTfulService
{
    [ServiceContract()]
    public interface IEmployeeService
    {
        [WebGet(UriTemplate = "Employee", ResponseFormat = WebMessageFormat.Json)]
        [OperationContract]
        List<Employee> GetAllEmployeeDetails();

        [WebGet(UriTemplate = "Employee?id={id}", ResponseFormat = WebMessageFormat.Json)]
        [OperationContract]
        Employee GetEmployee(int Id);

        [WebInvoke(Method = "POST", UriTemplate = "EmployeePOST", ResponseFormat = WebMessageFormat.Json, RequestFormat = WebMessageFormat.Json)]
        [OperationContract]
        void AddEmployee(Employee newEmp);

        [WebInvoke(Method = "PUT", UriTemplate = "EmployeePUT", ResponseFormat = WebMessageFormat.Json, RequestFormat = WebMessageFormat.Json)]
        [OperationContract]
        void UpdateEmployee(Employee newEmp);

        [WebInvoke(Method = "DELETE", UriTemplate = "Employee/{empId}", ResponseFormat = WebMessageFormat.Json)]
        [OperationContract]
        void DeleteEmployee(string empId);
    }
}

我的第一个Restful服务主机

app.config

<?xml version="1.0"?>
<configuration>
  <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/>
  </startup>
  <system.webServer>
    <modules runAllManagedModulesForAllRequests="true" />
    <httpProtocol>
      <customHeaders>
        <add name="Access-Control-Allow-Origin" value="*" />
        <add name="Access-Control-Allow-Headers" value="Content-Type" />
        <add name="Access-Control-Allow-Methods" value="POST,GET,OPTIONS" />
        <add name="Access-Control-Max-Age" value="1728000" />
      </customHeaders>
    </httpProtocol>
  </system.webServer>
  <system.serviceModel>
    <serviceHostingEnvironment aspNetCompatibilityEnabled="true"/>
    <standardEndpoints>
      <webHttpEndpoint>
        <standardEndpoint crossDomainScriptAccessEnabled="true"></standardEndpoint>
      </webHttpEndpoint>
      <webScriptEndpoint>
        <standardEndpoint crossDomainScriptAccessEnabled="true"></standardEndpoint>
      </webScriptEndpoint>
    </standardEndpoints>
    <bindings>
      <webHttpBinding>
        <binding name="webHttpBindingWithJsonP" crossDomainScriptAccessEnabled="true"/>
      </webHttpBinding>
    </bindings>
  </system.serviceModel>
  
</configuration>

我的第一个RESTful服务主机

Program.cs

static void Main(string[] args)
{
    try
    {

        Uri httpUrl = new Uri("http://localhost:8090/MyService/EmployeeService");
        WebServiceHost host = new WebServiceHost(typeof(MyFirstRESTfulService.EmployeeService), httpUrl);
        host.Open();
    
        foreach (ServiceEndpoint se in host.Description.Endpoints)
                Console.WriteLine("Service is host with endpoint " + se.Address);
        Console.WriteLine("Host is running... Press <Enter> key to stop");
        Console.ReadLine();
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.Message);
        Console.ReadLine();
    }
}

WebClient

Default.aspx

 <script type="text/javascript" >
      function RefreshPage() {
          var serviceUrl = "http://localhost:8090/MyService/EmployeeService/Employee";
          $.ajax({
              type: "GET",
              url: serviceUrl,
              dataType: 'jsonp',
              contentType: "application/json; charset=utf-8",
              success: function (data) {
                  var itemRow = "<table>";
                  $.each(data, function (index, item) {
                      itemRow += "<tr><td>" + item.EmpId + "</td><td>" + item.Fname + "</td></tr>";
                  });
                  itemRow += "</table>";

                  $("#divItems").html(itemRow);

              },
              error: ServiceFailed
          });
      }

      function POSTMethodCall() {
           var EmpUser = [{ "EmpId": "13", "Fname": "WebClientUser", "Lname": "Raju", "JoinDate": Date(1224043200000), "Age": "23", "Salary": "12000", "Designation": "Software Engineer"}];
           var st = JSON.stringify(EmpUser);
          $.ajax({
              type: "POST",
              url: "http://localhost:8090/MyService/EmployeeService/EmployeePOST",
              data: JSON.stringify(EmpUser),
               contentType: "application/json; charset=utf-8",
              dataType: "jsonp",
              success: function (data) {
                  // Play with response returned in JSON format
              },
              error:ServiceFailed
          });

      }
      function DELETEMethodCall() {
          $.ajax({
              type: "DELETE",
              url: "http://localhost:8090/MyService/EmployeeService/Employee/2",
              data: "{}",
              contentType: "application/json; charset=utf-8",
              dataType: "jsonp",
              success: function (data) {
                  // Play with response returned in JSON format
              },
              error: function (msg) {
                  alert(msg);
              }
          });

      }

      function PUTMethodCall() {
          var EmpUser = [{ "EmpId": "3", "Fname": "WebClientUser", "Lname": "Raju", "JoinDate": Date(1224043200000), "Age": "23", "Salary": "12000", "Designation": "Software Engineer"}];
          $.ajax({
              type: "PUT",
              url: "http://localhost:8090/MyService/EmployeeService/EmployeePUT",
              data: EmpUser,
              contentType: "application/json; charset=utf-8",
              dataType: "jsonp",
              success: function (data) {
                  alert('success');
                  // Play with response returned in JSON format
              },
              
               error: ServiceFailed
          });

      }
      function ServiceFailed(xhr) {
           alert("response:" + xhr.responseText);

          if (xhr.responseText) {
              var err = xhr.responseText;
              if (err)
                  error(err);
              else
                  error({ Message: "Unknown server error." })
          }

          return;
      }
  </script>
<input type="button" onclick="PUTMethodCall();" name="btnUpdate"  value ="Update" />
<input type="button" onclick="DELETEMethodCall();" name="btnDelete"  value ="Delete" />
<input type="button" onclick="POSTMethodCall();" name="btnAdd"  value ="Add" />
<input type="button" onclick="RefreshPage()" name="btnRefesh"  value ="Refresh" />
    <div id="divItems"></div>

enter image description here

通过点击“刷新”按钮(GET),成功检索到员工信息列表。然而,更新、删除和添加操作失败。 在Chrome中点击添加按钮后,图像显示状态405错误。 enter image description here 非常感谢您的建议和帮助!
2个回答

2

我认为这里的问题不是由CORS引起的,而是事实上它是一个GET请求,而不是所需的POST请求。

$.ajax({
  type: "POST",
  url: "http://localhost:8090/MyService/EmployeeService/EmployeePOST",
  data: JSON.stringify(EmpUser),
  contentType: "application/json; charset=utf-8",

  dataType: "jsonp",
  ^^^^^^^^^

  success: function (data) {
    // Play with response returned in JSON format
  },
  error:ServiceFailed
});

JSONP是一种避免跨域AJAX请求的机制,且JSONP请求始终使用GET方法发送。

应该将数据类型设置为从API中期望的数据类型。

在进行跨域AJAX请求时,浏览器将首先发出OPTIONS请求。这称为预检请求,您可以在此处阅读更多信息:MDN - CORS - Preflighted requests

要启用此功能,需要在IEmployeeService.cs中创建一个OPTIONS方法的路由,并返回一个空响应以200响应码。您的配置文件似乎已经正确地设置了头信息。


谢谢您的回复 @happyjack!那我应该将其设置为dataType:“json”吗? - DEN
我删除了 dataType: "jsonp" 这行代码,Chrome控制台显示错误信息:请求的资源上没有'Access-Control-Allow-Origin'头。因此,来自'http://localhost:25249'的源不被允许访问。响应的HTTP状态码为405。 - DEN
这是一个不同的问题,但我已经编辑了答案,包括可能的原因。 - jackfrankland
我应该如何在IEmployeeService.cs中为OPTIONS方法创建路由? 你介意帮我纠正一下我的源代码吗? - DEN
我不确定自己的答案是否正确。但我相信这是你原问题的解决方案,请将其标记为正确答案。如果您需要其他帮助,请发布另一个问题。话虽如此,下面G Brown的回答似乎能够帮助到你。 - jackfrankland

1
如果您遇到405错误(2011年的新Web标准;请参阅原始RFC 此处),则需要启用CORS。
在WCF中启用CORS并不容易,因为您需要做更多的工作,而不仅仅是像在web.config文件中添加自定义标头那样。只需编辑web.config文件即可为ASP.NET Web API提供足够的支持,但如果您正在处理其他内容,则需要添加大量自定义代码以允许OPTIONS标头。幸运的是,人们过去已经做过这些工作。(基本上,您需要创建一个消息检查器,然后创建一些使用该消息检查器类添加所需标头的端点行为。)
您最简单的方法是使用服务主机工厂,例如本文档末尾链接的工厂。在将服务主机工厂引用添加到.svc文件以及实现消息检查器所需的两个必要文件之后,您将成功启用CORS。只要您收到405错误,就说明您还没有成功启用CORS。

如需更多有关实现的参考,请参阅http://enable-cors.org/server_wcf.html上的示例。


1
只是挑刺一下,但CORS头在响应中对于任何ajax请求都是必需的,包括GET请求。确实,在标准的ajax GET请求中不会发生预检OPTIONS请求,但在标准的ajax POST请求中也不会发生。此外,您不能仅通过更改访问控制允许来源来阻止任何人访问发布数据。CORS是为了保护最终用户,而不是服务器。 - jackfrankland
这不是吹毛求疵,而是正确的。我有机会时会进行编辑。谢谢。 - G Brown

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接