Reputation: 1893
I read How to Create Restful in WCF tutorial and download the sample code from WCFTutorial. There are 1 Host (name: MYFirstRestfulServiceHost) and 1 client (name: WebClient). The WebClient and MYFirstRestfulServiceHost are in different domain. Hence when I make a GET / POST request, I encounter an issue: status 405 "Method Not Allowed".
After doing 2 days of research , I found out I have to add some configuration in Host app.config to perform GET/POST request on cross domain wcf service.
After adding the configuration in app.config, I succeed to perform the GET request in the WebClient , but fail to perform the remaining POST , DELETE and PUT by pressing the button from WebClient.
Please advice what else should I configure in order to get it success.
Below is the source code and configuration:
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
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>
It success to retrieve the list of employee info by pressing the Refresh button (GET) . However it fail for Update, Delete and Add.
The image shows the status 405 error after pressing the Add button from chrome.
I would very much appreciate it for you advice and help!
Upvotes: 3
Views: 1222
Reputation: 754
You need to enable CORS if you are getting a 405 (a new Web standard in 2011; see original RFC here).
CORS is not particularly easy to enable with WCF, as you have to do more than just adding the custom headers as you've tried in web.config. Just editing web.config is sufficient for an ASP.NET Web API, but if that's not what you're working on, you'll need to add a fair amount of custom code to allow the OPTIONS header, within your web service. Luckily, people have done this in the past. (Basically, you need to create a message inspector and then some endpoint behavior that uses the message inspector class to add the required headers.)
The easiest way for you to add the necessary message inspector is by using a service host factory, like the one linked to at the end of this article. After you add the service host factory reference to your .svc file, and the two necessary files that implement the message inspectors, you will have successfully enabled CORS. As long as you get the 405 error, you haven't successfully enabled CORS.
For more reference about the implementation, see the example at http://enable-cors.org/server_wcf.html.
Upvotes: 1
Reputation: 2062
I don't believe the issue here is caused by CORS, but the fact it is actually a GET request, rather than the required 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 is a mechanism to avoid making cross origin ajax requests, and JSONP requests will always be sent using GET.
You should set the data type to the one you are expecting from the API.
When making a cross origin ajax request, the browser will first make an OPTIONS request. This is called preflighting, which you can read more about here: MDN - CORS - Preflighted requests
To enable this, you need to create a route for the OPTIONS method inside IEmployeeService.cs, and return an empty response with 200. Your configuration file seems to be setting the correct headers.
Upvotes: 2