Search code examples
corsasp.net-core-webapihttpclientpatchflurl

ASP.NET Core 8 Method PATCH is not allowed by Access-Control-Allow-Methods in preflight response. Blocked By CORS policy


I created a PATCH method in an ASP.NET Core Web API like this:

#region Patch

/// <summary>
///     Patch TableName by id.
/// </summary>
// PATCH BY ID: /TableName/patch/{id}
[HttpPatch("[action]/{id}", Name = "PatchTableNameById")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[SwaggerOperation(
     Summary = "Patch Table Name by id",
     Description = "This can be used to modify any table name record",
     OperationId = "PatchTableNameById",
     Tags = new[] { "Patch" }
 )]
public async Task<IActionResult> Patch(string id, [FromBody] JsonPatchDocument<TableName> patchDoc)
{
     if (patchDoc != null)
     {
         var tablename =
             await context.TableName.SingleOrDefaultAsync(x => x.Id.ToString() == id);
         if (tablename == null)
             return NotFound();

         patchDoc.ApplyTo(tablename);
         context.Entry(tablename).State = EntityState.Modified;

         if (!ModelState.IsValid)
         {
             return BadRequest(ModelState);
         }

         await context.SaveChangesAsync();

         return new ObjectResult(tablename);
     }
     else
     {
         return BadRequest(ModelState);
     }
}

This works perfectly fine when I call it using Postman or directly using Swagger. However in my Blazor client I get an error when it is called. I have tried just using httpclient, RestSharp and now Flurl.

The error returned when using the Developer Tools console to review the log is:

Access to fetch at 'http://localhost:5259/SharedServices/Commands/TableName/patch/2bd4e0f3-974a-4794-bb63-b0ce00ba5147' from origin 'http://localhost:5265' has been blocked by CORS policy: Method PATCH is not allowed by Access-Control-Allow-Methods in preflight response.

Here is my CORS policy in the Web API. It is called before anything else adding services.

builder.Services.AddCors(options =>
{
    options.AddPolicy("AllowSpecificOrigins",
        builder =>
        {
            builder.WithOrigins("http://localhost:56075", "http://localhost:5265",
                    "https://localhost:7235").AllowAnyHeader().WithMethods("PATCH");
        });
});

I have tried various combinations here including of allowing all methods, headers, and origins. In other words opening it wide which isn't a great security tactic anyways.

Here is my client side code for the call.

public async Task Patch(string id, JsonPatchDocument<TableName> patchDoc)
{
    var url = "http://localhost:5259/SharedServices/Commands/TableName/patch/" +
              id;
    var resultStr = await url.WithHeader("Content-Type", "application/json-patch+json").PatchJsonAsync(patchDoc)
        .ReceiveString();
}

I have tried about every online suggestion to fix this. One problem is there are a lack of issue reports for later versions of asp.net -- post addition of addCORS. I have tried various browsers thinking this maybe a browser issue but no luck there either. Is this possibly a bug in asp.net core cors? I am also using Steeltoe. Is it possible it is interfering with this?

Is there any way to turn off a preflight request or modify it? Thanks for any help you can give!

I tried to use various clients such as httpclient, restsharp, flurl. Also tried using a PUT and got the same issue. I have also tried various browsers. The expect result is a successful unblocked call to my web api method.


Solution

  • Now that you've identified that Steeltoe is involved, the problem becomes a lot clearer. Steeltoe's hostbuilder extensions are intended to make things like Actuators easier to use most of the time, but they don't always expose all configurability available at lower levels. In this case, there should be a parameter for customizing the CORS policy, but that appears to have been missed in some cases.

    You can use this code to add all actuators and customize the CORS policy (and have greater control over which policy gets defined first if using more than one):

    var host = WebHost.CreateDefaultBuilder()
        // .AddAllActuators()
        .ConfigureServices(serviceCollection =>
        {
            static void myPolicy(CorsPolicyBuilder policy) => policy.WithMethods("patch");
            serviceCollection.AddAllActuators(buildCorsPolicy: myPolicy);
            serviceCollection.ActivateActuatorEndpoints();
        })