I’m trying to allow POST requests from my javascript app hosted at localhost:80 to a WCF REStful service hosted at a different port, but somehow it doesn’t work. I’ve tried adding custom properties to the header, as well as adding it programatically in my service’s JSONData method but I’m still getting ‘405 Method not allowed’ in my response. What is the proper approach here ?
This is my interface :
namespace RestService
{
public class RestServiceImpl : IRestServiceImpl
{
#region IRestServiceImpl Members
public string JSONData()
{
HttpContext.Current.Response.AddHeader("Access-Control-Allow-Origin", "*");
return "Your POST request";
}
#endregion
}
}
and the service code :
using System.ServiceModel;
using System.ServiceModel.Web;
using System.Web.Script.Services;
namespace RestService
{
[ServiceContract]
public interface IRestServiceImpl
{
[OperationContract]
[ScriptMethod]
[WebInvoke(Method = "POST",
ResponseFormat = WebMessageFormat.Json,
BodyStyle = WebMessageBodyStyle.Bare,
UriTemplate = "export")]
string JSONData();
}
}
And finally the config :
<?xml version="1.0"?>
<configuration>
<system.web>
<compilation debug="true" targetFramework="4.0" />
</system.web>
<system.serviceModel>
<services>
<service name="RestService.RestServiceImpl" behaviorConfiguration="ServiceBehaviour">
<endpoint address ="" binding="webHttpBinding" contract="RestService.IRestServiceImpl" behaviorConfiguration="web">
</endpoint>
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="ServiceBehaviour">
<serviceMetadata httpGetEnabled="true"/>
<serviceDebug includeExceptionDetailInFaults="false"/>
</behavior>
</serviceBehaviors>
<endpointBehaviors>
<behavior name="web">
<webHttp/>
</behavior>
</endpointBehaviors>
</behaviors>
<serviceHostingEnvironment multipleSiteBindingsEnabled="true" />
</system.serviceModel>
<system.webServer>
<modules runAllManagedModulesForAllRequests="true"/>
<httpProtocol>
<customHeaders>
<add name="Access-Control-Allow-Origin" value="*" />
</customHeaders>
</httpProtocol>
</system.webServer>
</configuration>
Answers:
Thank you for visiting the Q&A section on Magenaut. Please note that all the answers may not help you solve the issue immediately. So please treat them as advisements. If you found the post helpful (or not), leave a comment & I’ll get back to you as soon as possible.
Method 1
This worked better for me than the Web.config version:
Create a Global.asax
Add this method to the Global.asax.cs:
using System.Web;
namespace StackOverflow
{
public class Global : System.Web.HttpApplication
{
protected void Application_BeginRequest(object sender, EventArgs e)
{
HttpContext.Current.Response.AddHeader("Access-Control-Allow-Origin", "*");
if (HttpContext.Current.Request.HttpMethod == "OPTIONS")
{
HttpContext.Current.Response.AddHeader("Access-Control-Allow-Methods", "GET, POST");
HttpContext.Current.Response.AddHeader("Access-Control-Allow-Headers", "Content-Type, Accept");
HttpContext.Current.Response.AddHeader("Access-Control-Max-Age", "1728000");
HttpContext.Current.Response.End();
}
}
}
}
Method 2
Add these nodes to your Web.config:
<configuration>
<system.webServer>
<httpProtocol>
<customHeaders>
<add name="Access-Control-Allow-Origin" value="*"/>
<add name="Access-Control-Allow-Headers" value="Content-Type, Accept" />
<add name="Access-Control-Allow-Methods" value="POST,GET,OPTIONS" />
<add name="Access-Control-Max-Age" value="1728000" />
</customHeaders>
</httpProtocol>
</system.webServer>
</configuration>
Method 3
Enabling CORS for non-GET requests requires more than just setting the Access-Control-Allow-Origin header – it also needs to deal with preflight requests, which are OPTIONS requests which ask the server whether it’s safe to perform operations which can potentially change data (e.g., POST, PUT, DELETE) before the actual request is sent.
I’ve written a blog post about adding CORS support for WCF. It’s not the simplest of the implementations, but hopefully the code in the post can be simply copied / pasted into your project. The post can be found at http://blogs.msdn.com/b/carlosfigueira/archive/2012/05/15/implementing-cors-support-in-wcf.aspx.
Method 4
The following .NET code (global.asax) has an important difference that in stead of *, it can be better to echo back the Origin domain because this enables authentication over CORS (e.g. NTLM / Kerberos) as well as the Preflight.
void Application_BeginRequest(object sender, EventArgs e)
{
if (Request.HttpMethod == "OPTIONS")
{
Response.AddHeader("Access-Control-Allow-Methods", "GET, POST");
Response.AddHeader("Access-Control-Allow-Headers", "Content-Type, Accept");
Response.AddHeader("Access-Control-Max-Age", "1728000");
Response.End();
}
else
{
Response.AddHeader("Access-Control-Allow-Credentials", "true");
if (Request.Headers["Origin"] != null)
Response.AddHeader("Access-Control-Allow-Origin" , Request.Headers["Origin"]);
else
Response.AddHeader("Access-Control-Allow-Origin" , "*");
}
}
Method 5
All of the solutions above modify the CORS response to allow all sites to be CORS enabled by using the * attribute. This seemed like a security risk to me as I wanted to control what sites were given access to my REST services. I am hoping this may help others with the same type of issue.
I started by modifying the web.config file to include my allowed origin sites by using the SpecializedString collection which allows a string array to be stored in MySettings. The code is in VB.Net so should port easily to c#, if desired.
<applicationSettings>
<YourService.My.MySettings>
<setting name="AllowedOrigins" serializeAs="Xml">
<value>
<ArrayOfString xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<string>http://localhost:62087</string>
<string>https://yourallowedorign2:3344</string>
</ArrayOfString>
</value>
</setting>
</YourService.My.MySettings>
</applicationSettings>
And in the Global.asax.vb file, I made the following adjustments to the Application_BeginRequest code.
Note the support function to get the Allowed Origins
Private allowedOrigins As String()
Public Function GetAllowedOrigins() As String()
Dim mySetting As StringCollection = My.Settings.AllowedOrigins
If mySetting IsNot Nothing Then
' If you're using .NET 3.5 or greater:
Return mySetting.Cast(Of String)().ToArray()
' Otherwise:
Dim array(mySetting.Count - 1) As String
mySetting.CopyTo(array, 0)
Return array
Else
Dim strEmpty() As String = Enumerable.Empty(Of String).ToArray
Return strEmpty
End If
End Function
Sub Application_BeginRequest(ByVal sender As Object, ByVal e As EventArgs)
' Fires at the beginning of each request
Dim origin As String = sender.Request.Headers("Origin")
If origin IsNot Nothing Then
Dim originURI As Uri = New Uri(origin)
Dim requestHost As String = originURI.Scheme + Uri.SchemeDelimiter + originURI.Host
If originURI.Port <> 80 Then
requestHost += ":" + originURI.Port.ToString
End If
If allowedOrigins Is Nothing Then
allowedOrigins = GetAllowedOrigins()
End If
If allowedOrigins IsNot Nothing AndAlso allowedOrigins.Contains(requestHost) Then
HttpContext.Current.Response.AddHeader("Access-Control-Allow-Origin", requestHost)
If HttpContext.Current.Request.HttpMethod = "OPTIONS" Then
HttpContext.Current.Response.AddHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE")
HttpContext.Current.Response.AddHeader("Access-Control-Allow-Headers", "Content-Type, Accept, X-Requested-With")
HttpContext.Current.Response.AddHeader("Access-Control-Max-Age", "1728000")
HttpContext.Current.Response.End()
End If
End If
End If
End Sub
One other caveat that I found is that I needed to add the X-Requested-With value in the Access-Control-Allow-Headers header. If you get a CORS error, check that header first to see if you need additional options.
I hope this helps other that may be frustrated by this all too common issue.
All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0