cors issue with vue and dotnet

2019-07-29 10:28发布


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)

标签: .net vue.js cors
2条回答
神经病院院长
2楼-- · 2019-07-29 10:51

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
    };
}
查看更多
Melony?
3楼-- · 2019-07-29 11:05

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 sats you can include the body inside the config paramter.
well, 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.

查看更多
登录 后发表回答