简体   繁体   中英

400 Bad Request error when sending POST request to WCF service using JavaScript

I have a very simple self-hosted WCF application initialized this way:

Host = new WebServiceHost(this, new Uri(serviceAddress));
var binding = new WebHttpBinding();
binding.ReceiveTimeout = TimeSpan.MaxValue;
binding.MaxReceivedMessageSize = int.MaxValue;
binding.CrossDomainScriptAccessEnabled = true; // TODO: Remove this?
var endpoint = Host.AddServiceEndpoint(typeof(HttpService), binding, "");
Host.Open();

My HttpService class has this method:

[OperationContract, WebInvoke(Method = "*")]
public string Evaluate(string query)
{
    WebOperationContext.Current.OutgoingResponse.Headers.Add("Access-Control-Allow-Origin", "*");

    if (WebOperationContext.Current.IncomingRequest.Method == "OPTIONS")
    {
        WebOperationContext.Current.OutgoingResponse.Headers.Add("Access-Control-Allow-Methods", "POST, PUT, DELETE");
        WebOperationContext.Current.OutgoingResponse.Headers.Add("Access-Control-Allow-Headers", "Content-Type, Accept, X-Requested-With");
        WebOperationContext.Current.OutgoingResponse.Headers.Add("Access-Control-Max-Age", "1728000");
        return "";
    }
    else
    {
        try
        {
            return "12345";
        }
        catch (ProcessNotFoundException ex)
        {
            throw new WebFaultException<string>(ex.Message, System.Net.HttpStatusCode.NotFound);
        }
        catch (Exception ex)
        {
            throw new WebFaultException<string>(ex.Message, System.Net.HttpStatusCode.InternalServerError);
        }
    }
}

I added the CORS stuff above so that anyone can use this API from any website using JavaScript. And I'm trying to run some POST requests using fetch because once it works, I'll pass large amounts of data so a GET method won't work. Doing the following from JavaScript works OK:

fetch('http://localhost:8001/HeliumScraperService/Evaluate', { method: 'POST', headers: { "Content-Type": 'application/json' }, body: '1232'});

When I do that, my Evaluate method is called first with OPTIONS and then with POST, and an XML string is returned with no errors. The following also works (note the quotes inside the body):

fetch('http://localhost:8001/HeliumScraperService/Evaluate', { method: 'POST', headers: { "Content-Type": 'application/json' }, body: '"hello world"'});

Since both are valid JSON, I though any JSON string would work, so I did this but I got a 400 Bad Request error:

fetch('http://localhost:8001/HeliumScraperService/Evaluate', { method: 'POST', headers: { "Content-Type": 'application/json' }, body: '[10]'});

I thought maybe it wants a JSON that it can convert to my method arguments, but this also doesn't work (gives me a 400 error):

fetch('http://localhost:8001/HeliumScraperService/Evaluate', { method: 'POST', headers: { "Content-Type": 'application/json' }, body: JSON.stringify({ query: "hey" })})

So it can take numbers and strings but nothing else, and I have no idea why. I tried using all these other attribute combinations and got the exact same results:

[OperationContract, WebInvoke(Method = "*", BodyStyle = WebMessageBodyStyle.Bare, RequestFormat = WebMessageFormat.Json)]
[OperationContract, WebInvoke(Method = "*", BodyStyle = WebMessageBodyStyle.Wrapped, RequestFormat = WebMessageFormat.Json)]
[OperationContract, WebInvoke(Method = "*", BodyStyle = WebMessageBodyStyle.Bare)]
[OperationContract, WebInvoke(Method = "*", BodyStyle = WebMessageBodyStyle.Wrapped)]

I just want to be able to send any string, without something looking into it and deciding whether it's good or bad, while also being able to do the CORS thing above. Is there a way to do this?

I found a workaround but I'm still not sure why it was letting me pass numbers and strings but not arrays, so if someone comes up with an actual explanation I'll be happy to accept that as an answer. My solution was to take a Stream instead of a string in my Evaluate method, and then read the stream like:

using (var memoryStream = new MemoryStream())
{
    query.CopyTo(memoryStream);
    var array = memoryStream.ToArray();
    var lole = Encoding.UTF8.GetString(array);
}

I think the idea is that, since I'm doing the CORS thing and the browser sends an empty body when sending an OPTIONS request, WCF can't handle that as JSON so I have to handle everything myself and the way to do that is by taking a war Stream. Still no idea why it was letting me send numbers and strings (and booleans too BTW) but no arrays (or objects).

At first, the following definition only supports to accept the parameters by using request body , instead of the URL Params.

[OperationContract, WebInvoke(Method = "*")]
public string Evaluate(string query)

Moreover, the body style of the parameters is bare by default. BodyStyle =WebMessageBodyStyle.Bare It means that the server accepts the string without wrapping the name of the parameter.

body: '1232'
body: '"hello world"'

When we manually switch the value to BodyStyle=WebMessageBodyStyle.Wrapped , the server will accept the below form. Basically, your attempts are correct. There is just a small problem here.

{"value":"abcd"}

Here is my example, wish it is helpful to you.
Server contract.

[OperationContract]
        [WebInvoke(Method ="*",BodyStyle =WebMessageBodyStyle.Wrapped,ResponseFormat =WebMessageFormat.Json)]
        string GetData(string value);


JS

 <script> var para1 = { "value": "abcdefg" }; $.ajax({ type: "POST", url: "http://localhost:8864/Service1.svc/getdata", data: JSON.stringify(para1), contentType: "application/json; charset=utf-8", dataType: "json", success: OnSuccessCall, error: OnErrorCall }); function OnSuccessCall(response) { console.log(response); } function OnErrorCall(response) { console.log(response.status + " " + response.statusText); } </script>

About handing the CORS issue, I add an extra global.asax file.

 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("Cache-Control", "no-cache"); HttpContext.Current.Response.AddHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS"); HttpContext.Current.Response.AddHeader("Access-Control-Allow-Headers", "Cache-Control, Pragma, Origin, Authorization, Content-Type, X-Requested-With,Accept"); HttpContext.Current.Response.AddHeader("Access-Control-Max-Age", "1728000"); HttpContext.Current.Response.End(); } }

Here is a related document.
https://docs.microsoft.com/en-us/dotnet/api/system.servicemodel.web.webmessagebodystyle?view=netframework-4.8
Feel free to let me know if there is anything I can help with.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM