I'm trying to get my ReactJS app (on an AWS S3 machine) PUT request working with my Server API (on an AWS Windows EC2 machine). Seems I am being tripped up by the preflight message that is being sent out. I've been searching on how to handle this and came across these two stackoverflow posts:
Enable OPTIONS header for CORS on .NET Core Web API
How to handle OPTION header in dot net core web api
I've ensured IIS accepts the OPTIONS verb and have added the middleware described. I can see the OPTIONS preflight handling being called through the logging but for some reason I am still getting the CORS error. Listed the main sections of the code below, any help would be really appreciated.
ReactJS PUT request
var myHeaders = new Headers();
myHeaders.append('Accept', 'application/json');
myHeaders.append('Content-Type', 'application/json-patch+json');
var rawObject = {
Name: this.state.recipeEdit.name,
Type: this.state.recipeTypeEdit,
Description: this.state.recipeEdit.description,
Ingredients: this.state.recipeIngredients,
Steps: this.state.recipeSteps,
};
var requestOptions = {
method: 'PUT',
headers: myHeaders,
body: JSON.stringify(rawObject),
redirect: 'follow',
};
fetch(this.state.url, requestOptions)
.then((response) => response.json())
.then((data) => {
this.setState({ recipeDetail: data });
});
Middleware Class
public class OptionsMiddleware
{
private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
private readonly RequestDelegate _next;
public OptionsMiddleware(RequestDelegate next)
{
_next = next;
}
public Task Invoke(HttpContext context)
{
return BeginInvoke(context);
}
private Task BeginInvoke(HttpContext context)
{
if (context.Request.Method == "OPTIONS")
{
log.Error("Handling the OPTIONS preflight message");
context.Response.Headers.Add("Access-Control-Allow-Origin", new[] { (string)context.Request.Headers["Origin"] });
context.Response.Headers.Add("Access-Control-Allow-Headers", new[] { "Origin, X-Requested-With, Content-Type, Accept" });
context.Response.Headers.Add("Access-Control-Allow-Methods", new[] { "GET, POST, PUT, DELETE, OPTIONS" });
context.Response.Headers.Add("Access-Control-Allow-Credentials", new[] { "true" });
context.Response.StatusCode = 200;
return context.Response.WriteAsync("OK");
}
log.Error("Invoking message");
return _next.Invoke(context);
}
}
public static class OptionsMiddlewareExtentions
{
public static IApplicationBuilder UseOptions(this IApplicationBuilder builder)
{
return builder.UseMiddleware<OptionsMiddleware>();
}
}
CORS Configuration in Startup.cs
public void ConfigureServices(IServiceCollection services)
{
log.Error("Entered ConfigureServices");
try
{
#if DEBUG
services.AddCors();
#else
services.AddCors(o => o.AddPolicy("MyCorsPolicy", builder =>
{
builder.SetIsOriginAllowed((host) => true)
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials();
}));
#endif
services.AddControllersWithViews().AddNewtonsoftJson();
services.AddControllersWithViews(options =>
{
options.InputFormatters.Insert(0, GetJsonPatchInputFormatter());
});
services.AddMvc(options => options.EnableEndpointRouting = false).SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
services.AddMvc(options => options.Filters.Add(typeof(homebakeExceptionFilter)));
#if USE_SQLITE
log.Error("Using SQLITE");
services.AddDbContext<SqliteDbContext>(options =>
{
options.UseSqlite("Data Source=./homebake.db");
});
#else
services.AddDbContext<AppDbContext>(options =>
{
options.UseInMemoryDatabase("homebakeapp-api-in-memory");
});
#endif
log.Error("Adding services");
services.AddScoped<IIngredientRepository, IngredientRepository>();
services.AddScoped<IRecipeStepRepository, RecipeStepRepository>();
services.AddScoped<IRecipeRepository, RecipeRepository>();
services.AddScoped<IIngredientService, IngredientService>();
services.AddScoped<IRecipeStepService, RecipeStepService>();
services.AddScoped<IRecipeService, RecipeService>();
services.AddScoped<IUnitOfWork, UnitOfWork>();
log.Error("Adding auto mapper");
services.AddAutoMapper(typeof(Startup));
}
catch (System.Exception ex)
{
log.Error(ex.Message);
if (ex.InnerException != null )
log.Error(ex.InnerException);
}
}
private static NewtonsoftJsonPatchInputFormatter GetJsonPatchInputFormatter()
{
var builder = new ServiceCollection()
.AddLogging()
.AddMvc()
.AddNewtonsoftJson()
.Services.BuildServiceProvider();
return builder
.GetRequiredService<IOptions<MvcOptions>>()
.Value
.InputFormatters
.OfType<NewtonsoftJsonPatchInputFormatter>()
.First();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddLog4Net();
log.Error("Entered Configure");
app.UseOptions();
#if DEBUG
app.UseCors(options => options.WithOrigins("http://localhost:3000").AllowAnyMethod().AllowAnyHeader());
#else
log.Error("Using cors policy");
app.UseCors("MyCorsPolicy");
#endif
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
//app.use
app.UseHttpsRedirection();
log.Error("Using MVC");
app.UseMvc();
}