WCF Restful CORS 无法执行 POST

Posted

技术标签:

【中文标题】WCF Restful CORS 无法执行 POST【英文标题】:WCF Restful CORS failed to execute POST 【发布时间】:2016-01-09 03:09:09 【问题描述】:

我阅读了如何在 WCF 中创建 Restful 教程并从WCFTutorial 下载示例代码。 有 1 个主机(名称:MYFirstRestfulServiceHost)和 1 个客户端(名称:WebClient)。 WebClient 和 MYFirstRestfulServiceHost 位于不同的域中。 因此,当我发出 GET / POST 请求时,我遇到了一个问题:状态 405“方法不允许”。

经过 2 天的研究,我发现我必须在 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);
    

MyFirstRestfulServiceHost

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>

MyFirstRESTfulServiceHost

程序.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();
    

网络客户端

默认.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>

按刷新按钮(GET)成功检索员工信息列表。但是,更新、删除和添加失败。 该图显示了在 chrome 中按下“添加”按钮后的状态 405 错误。

非常感谢您的建议和帮助!

【问题讨论】:

【参考方案1】:

我不认为这里的问题是由 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" ? 我删除了 dataType: "jsonp" 这一行,chrome 控制台显示错误 No 'Access-Control-Allow-Origin' header is present on the requested resource。因此,Origin 'localhost:25249' 不允许访问。响应的 HTTP 状态代码为 405。 这是一个不同的问题,但我已经编辑了答案以包含可能的原因。 我应该如何为 IEmployeeService.cs 中的 OPTIONS 方法创建路由?您介意更正我的源代码吗? 我自己都不认识。我相信这是您原始问题的解决方案,因此请将其标记为正确答案,如果您需要其他帮助,请发布另一个问题。话虽如此,下面 G Brown 的回答似乎会对您有所帮助。【参考方案2】:

如果您获得 405(2011 年的新 Web 标准;请参阅原始 RFC here),则需要启用 CORS

使用 WCF 启用 CORS 并不是特别容易,因为您需要做的不仅仅是像在 web.config 中尝试的那样添加自定义标头。对于 ASP.NET Web API,只需编辑 web.config 就足够了,但如果这不是您正在处理的工作,则需要在 Web 服务中添加大量自定义代码以允许 OPTIONS 标头。幸运的是,人们过去曾这样做过。 (基本上,您需要创建一个消息检查器,然后创建一些使用消息检查器类添加所需标头的端点行为。)

添加必要的消息检查器的最简单方法是使用服务主机工厂,例如在this article 末尾链接的那个。将服务主机工厂引用添加到 .svc 文件以及实现消息检查器的两个必要文件后,您将成功启用 CORS。只要你得到405错误,就说明你没有成功启用CORS。

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

【讨论】:

只是为了挑剔,但任何 ajax 请求(包括 GET)的响应中都需要 CORS 标头。确实,预检 OPTIONS 请求不会发生在标准 ajax GET 请求上,但也不会发生在标准 ajax POST 请求上。此外,您不能仅通过更改 access-control-allow-origin 来阻止任何人访问发布数据。 CORS 是为了保护最终用户,而不是服务器。 这不是挑剔,它是正确的。有机会会编辑。谢谢

以上是关于WCF Restful CORS 无法执行 POST的主要内容,如果未能解决你的问题,请参考以下文章

Restful WCF 无法达到 operationcontract

将 transferMode 更改为“Streamed”后无法加载 wcf restful 帮助页面

C# Restful WCF 服务。无法在帖子正文中反序列化 XML

Fileless Restful WCF 在本地 IIS7 中托管时无法正常工作,但可以在本地运行

尽管有 web.config 条目,但带有 ASP.NET WCF 服务的 jQuery AJAX CORS 无法正常工作

WCF、RESTful Web 服务和自定义身份验证