问题
my environments:
- backend (localhost) written in .net web api 2
enabled package "Microsoft.AspNet.WebApi.Cors" - frontend (localhost) with vue-js-2 with webpack-simple
vue-resource for http requests
I am experiencing some CORS issues which I cannot solve:
Launching "simple requests" with GET verb works, but when using POST verb I get CORS error, saying:
Access to XMLHttpRequest at 'http://localhost:59837/api/Employees' from origin 'http://localhost:8080' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
I've read mozilla's CORS reference and noticed that indeed my POST request is sent as a preflight request first, with OPTIONS verb and Access-Control-Request-Method: POST
header.
on my WebApiConfig file I have:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
...
// Web API enable CORS
System.Web.Http.Cors.EnableCorsAttribute cors = new System.Web.Http.Cors.EnableCorsAttribute("*", "*", "*");
config.EnableCors(cors);
}
}
and my POST request code is as follows:
testPOST(){
const credentials = { username: '1', password: '11'};
this.$http.post('http://localhost:59837/api/Employees', {params: {id: '2'}, body: credentials, method: 'POST', headers: {'Content-Type': 'text/plain'}})
.then(function (response){
console.log(response);
})
.catch(function (error) {
console.log(error);
});
}
and the POST function in the controller:
public async Task<HttpResponseMessage> PostEmployees(Credentials creds)
{
Employees emp = await db.Employees.FindAsync(Int32.Parse(creds.username));
if (emp.password.Equals(creds.password))
{
emp.password = "";
return Request.CreateResponse(HttpStatusCode.OK, emp);
}
else
return Request.CreateResponse(HttpStatusCode.Unauthorized, "Username or password are incorrect");
}
My thinking was that perhaps I need to define the POST request headers to the authorized "text/plain". Using Fiddler I found the outgoing request but it didn't have the text/plain header.. Right now, I am not even sure if my error is related to the backend configuration or to the frontend request sending method. Any one encountered anything similar? (sorry for all the code, I wanted to be as encompassing yet minimal as possible)
回答1:
Web Api 2 doesn't respond to OPTIONS request. You can add Handler for it. Here it is how i solved it once. Create a IHttpModule somewhere. Here is mine:
namespace AAWebSmartHouse.WebApi.Infrastructure.Modules
{
using System.Web;
public class OPTIONSVerbHandlerModule : IHttpModule
{
public void Init(HttpApplication context)
{
context.BeginRequest += (sender, args) =>
{
var app = (HttpApplication)sender;
if (app.Request.HttpMethod == "OPTIONS")
{
app.Response.StatusCode = 200;
app.Response.AddHeader("Access-Control-Allow-Headers", "content-type,accept,authorization");
app.Response.AddHeader("Access-Control-Allow-Origin", "*");
app.Response.AddHeader("Access-Control-Allow-Credentials", "true");
app.Response.AddHeader("Access-Control-Allow-Methods", "POST,GET,OPTIONS,PUT,DELETE");
app.Response.AddHeader("Content-Type", "application/json");
app.Response.AddHeader("Accept", "application/json");
app.Response.End();
}
};
}
public void Dispose()
{
}
}
}
And add it in Web.config:
<modules>
<!-- ... -->
<add name="OPTIONSVerbHandlerModule" type="AAWebSmartHouse.WebApi.Infrastructure.Modules.OPTIONSVerbHandlerModule" />
</modules>
<handlers>
<remove name="ExtensionlessUrlHandler-Integrated-4.0" />
<remove name="OPTIONSVerbHandler" />
<!-- ... -->
</handlers>
If it still dosen't respond to OPTIONS request for some method in your controller i see that i have added AcceptVerbs Attribute like that:
// GET api/Account/UserInfo
[HostAuthentication(DefaultAuthenticationTypes.ExternalBearer)]
[Route("UserInfo")]
[AcceptVerbs("OPTIONS", "GET")]
public UserInfoViewModel GetUserInfo()
{
ExternalLoginData externalLogin = ExternalLoginData.FromIdentity(User.Identity as ClaimsIdentity);
return new UserInfoViewModel
{
Email = User.Identity.GetUserName(),
HasRegistered = externalLogin == null,
LoginProvider = externalLogin != null ? externalLogin.LoginProvider : null
};
}
回答2:
Solution
I think the problem was actually both front- and back-end related.
Microsoft's Web API 2's tutorial doesn't seem to mention its lack of support for the OPTIONS header that is generated by some clients as "pre-flight" request.
In addition, the parameters I was using in Vue-Resource's also caused some problems.
Back-end:
1. I changed the default behavior that caused the server to drop all requests with OPTIONS header in Global.asax.cs, thanks Obelixx:
if (Request.Headers.AllKeys.Contains("Origin") && Request.HttpMethod == "OPTIONS")
{
//Response.Flush();
Response.StatusCode = 200;
Response.AddHeader("Access-Control-Allow-Headers", "content-type,accept,authorization");
Response.AddHeader("Access-Control-Allow-Origin", "*");
Response.AddHeader("Access-Control-Allow-Credentials", "true");
Response.AddHeader("Access-Control-Allow-Methods", "POST,GET,PUT,DELETE");
Response.AddHeader("Content-Type", "application/json");
Response.AddHeader("Accept", "application/json");
Response.End();
}
I'm really not sure that's the best solution, I'm sure there's a perfectly good reason why .net programmers thought all OPTIONS requests should be dropped, this needs deeper digging into it, so use with caution.
2. as can be seen here, I also added an OPTIONS actions in my controller class:
// OPTIONS: allow pre-flight
public HttpResponseMessage Options()
{
return new HttpResponseMessage { StatusCode = HttpStatusCode.OK };
}
front-end:
1. vue-resource's API says has this signature for POST request:
post(url, [body], [config])
but also says you can include the body inside the config paramter.
well, that didn't work for met. so instead of this:
this.$http.post('http://localhost:59837/api/Employees',
{params: {id: '2'}, body: credentials, method: 'POST', headers: {'Content-Type': 'text/plain'}})
I did this:
this.$http.post('http://localhost:59837/api/Employees', credentials,
{headers: {'Content-Type': 'application/json'}})
notice how i took the body: credentials out and just used credentials as a separate argument.
2. I also changed the 'Content-Type' header from text/plain to application/json (microsoft says that text/plain types should prevent the pre-flight, but that only caused my json formatter to fail parsing credentials).
----
So that's the solution for now, like I said I had both front- and back-end related fixes, I'm not sure if there were really so many bugs or just an undiscovered one that I patched in many places.
I hope this helps other people.
来源:https://stackoverflow.com/questions/53665751/cors-issue-with-vue-and-dotnet