3

I am using a WCF (.svc) WebService and it's working perfectly - I can call its methods without problems from Postman, PHP, etc. But, when I try to call it from JavaScript/jQuery using AJAX, there is an obvious problem - I do it from other domain than WS, so it won't let me do it.

It is all about POST methods. But there is a problem even when my page is sending firstly an OPTIONS method:

OPTIONS 'WS ADDRESS' 405 (Method Not Allowed)

XMLHttpRequest cannot load 'WS ADDRESS' Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'MY ADDRESS' is therefore not allowed access. The response had HTTP status code 405.

There are detailed responses:

Responses

Okay, I read about cross-domain and WS's Web.config contains all of it that is necessary (?)

Web.config

Please let me know what am I doing wrong that I cannot reach my WS from JavaScript, even though it seems to me configured well. But still, it seems not to send these special headers in response... Thanks in advance.

Community
  • 1
  • 1
  • That 405 code means your server’s configured to disallow OPTIONS requests. That’s what the *Method Not Allowed* message means. You must configure it to instead respond to OPTIONS requests with a 200 or 204 success message & the right Access-Control-Allow-* headers. As far as how to do that, sounds like to you need to read more docs and study it further—start at https://stackoverflow.com/questions/16024347/cross-origin-resource-sharing-for-c-sharp-wcf-restful-web-service-hosted-as-wind/16039951#16039951 & https://stackoverflow.com/questions/43911702/405-method-not-allowed-wcf-service-and-ionic – sideshowbarker Aug 01 '17 at 09:37

2 Answers2

2

XML HttpRequest allows data to be transferred from a web server to the browser. The browsers, however, prevents CORS by default. GET request is not going to change anything on the server side (nothing is created/updated/deleted) - it's only going to return some data.

POST/PUT/DELETE requests, however, will process the request and change something on the server side and issue a response to the browser. If the response doesn't have a proper Access-Control-Allow-Origin header, the browser will block the response. But that doesn't matter because by the time the response is issued, the server has already processed the request and made changes, perhaps to a database.

In order to prevent POST/PUT/DELETE request to be processed on server side, browsers will send a preflight request.

A prefligth request is an http request with the method OPTIONS. So before sending the POST request, the browser will send an OPTIONS request with an additional header called Access-Control-Request-Method with the value of POST.

The 405 (Method Not Allowed) error indicates the the server is not configured to accept OPTIONS request.

You can solve this issue either by using a wildcard for your web invoke method similar to this:

[OperationContract]
[WebInvoke(Method = "*", UriTemplate = "/Path", ResponseFormat = WebMessageFormat.Json)]

Or by adding an additional [OperationContract] to your [ServiceContract] that handles OPTIONS request similar to this:

 [OperationContract(Name = "OptionsMyFunction")]
 [WebInvoke(Method = "OPTIONS", UriTemplate = "/Path", ResponseFormat = WebMessageFormat.Json)]
S.Dav
  • 2,436
  • 16
  • 22
1

You can implement .NET IDispatchMessageInspector for this work.

  • Create a class implementing IDispatchMessageInspector
  • Create a class implementing Attribute,IEndpointBehavior,IOperationBehavior

Allow only OPTIONS in your class implementing IDispatchMessageInspector

The code will look like this

using System;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Dispatcher;
using System.ServiceModel.Description;
using System.Collections.Generic;
using System.Net;

namespace WapstyPrintService
{
    public class MessageInspector : IDispatchMessageInspector
    {
        private ServiceEndpoint _serviceEndpoint;
        Dictionary<string, string> requiredHeaders;

        public MessageInspector(ServiceEndpoint serviceEndpoint)
        {
            _serviceEndpoint = serviceEndpoint;
            requiredHeaders = new Dictionary<string, string>();

            requiredHeaders.Add("Access-Control-Allow-Origin", "*");
            requiredHeaders.Add("Access-Control-Request-Method", "POST,GET,PUT,DELETE,OPTIONS");
            requiredHeaders.Add("Access-Control-Allow-Headers", "X-Requested-With,Content-Type");
        }

        /// <summary>
        /// Called when an inbound message been received
        /// </summary>
        /// <param name="request">The request message.</param>
        /// <param name="channel">The incoming channel.</param>
        /// <param name="instanceContext">The current service instance.</param>
        /// <returns>
        /// The object used to correlate stateMsg. 
        /// This object is passed back in the method.
        /// </returns>
        public object AfterReceiveRequest(ref Message request,
                                              IClientChannel channel,
                                              InstanceContext instanceContext)
        {
            var httpRequest = (HttpRequestMessageProperty)request
            .Properties[HttpRequestMessageProperty.Name];
            return new
            {
                origin = httpRequest.Headers["Origin"],
                handlePreflight = httpRequest.Method.Equals("OPTIONS",
                StringComparison.InvariantCultureIgnoreCase)
            };
        }

        /// <summary>
        /// Called after the operation has returned but before the reply message
        /// is sent.
        /// </summary>
        /// <param name="reply">The reply message. This value is null if the 
        /// operation is one way.</param>
        /// <param name="correlationState">The correlation object returned from
        ///  the method.</param>
        public void BeforeSendReply(ref Message reply, object correlationState)
        {
            var state = (dynamic)correlationState;
            if (state.handlePreflight)
            {
                reply = Message.CreateMessage(MessageVersion.None, "PreflightReturn");

                var httpResponse = new HttpResponseMessageProperty();
                reply.Properties.Add(HttpResponseMessageProperty.Name, httpResponse);

                httpResponse.SuppressEntityBody = true;
                httpResponse.StatusCode = HttpStatusCode.OK;
            }

            var httpHeader = reply.Properties["httpResponse"] as HttpResponseMessageProperty;
            foreach (var item in requiredHeaders)
            {
                httpHeader.Headers.Add(item.Key, item.Value);
            }
        }
    }
}

and

using System;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;

namespace WapstyPrintService
{
    public class BehaviorAttribute : Attribute, IEndpointBehavior,
                                   IOperationBehavior
    {
        public void Validate(ServiceEndpoint endpoint) { }

        public void AddBindingParameters(ServiceEndpoint endpoint,
                                 BindingParameterCollection bindingParameters)
        { }

        /// <summary>
        /// This service modify or extend the service across an endpoint.
        /// </summary>
        /// <param name="endpoint">The endpoint that exposes the contract.</param>
        /// <param name="endpointDispatcher">The endpoint dispatcher to be
        /// modified or extended.</param>
        public void ApplyDispatchBehavior(ServiceEndpoint endpoint,
                                          EndpointDispatcher endpointDispatcher)
        {
            // add inspector which detects cross origin requests
            endpointDispatcher.DispatchRuntime.MessageInspectors.Add(
                                                   new MessageInspector(endpoint));
        }

        public void ApplyClientBehavior(ServiceEndpoint endpoint,
                                        ClientRuntime clientRuntime)
        { }

        public void Validate(OperationDescription operationDescription) { }

        public void ApplyDispatchBehavior(OperationDescription operationDescription,
                                          DispatchOperation dispatchOperation)
        { }

        public void ApplyClientBehavior(OperationDescription operationDescription,
                                        ClientOperation clientOperation)
        { }

        public void AddBindingParameters(OperationDescription operationDescription,
                                  BindingParameterCollection bindingParameters)
        { }

    }
}

Then add your message inspector to service endpoint behavior

ServiceHost host = new ServiceHost(typeof(myService), _baseAddress);
foreach (ServiceEndpoint EP in host.Description.Endpoints)
            EP.Behaviors.Add(new BehaviorAttribute());