'ASP.NET Core form ignoring asp-controller

I'm working on an ASP.NET Core application. Part of it is an image gallery which allows the user to upload images. On localhost this works as expected, but when I deploy (AWS Lambda with API Gateway) the upload button is missing the asp-controller attribute.

I have a GalleryController.cs with two methods:

 public async Task<IActionResult> Index()
 {
      // Pulls images from aws S3, adds them to a list and passes them to then returns View();
 }

 [HttpPost("Upload")]
 public async Task<IActionResult> Upload(List<IFormFile> files)
 {
      // For each iformFile in Files, create a thumbnail in memory then upload both the original and thumbnail to aws s3 bucket
      return RedirectToAction("Index");
 }

The routing configuration in my startup.cs has been left default from the boilerplate:

app.UseEndpoints(endpoints =>
{
    endpoints.MapControllerRoute(
        name: "default",
        pattern: "{controller=Home}/{action=Index}/{id?}");
});

The folder structure for my Views is as follows:

├── Views
│   ├── Gallery
│   │   ├── _GalleryPartial.cshtml
│   │   ├── Index.cshtml
│   │   └── _UploadPartial.cshtml
│   ├── Home
│   │   └── Index.cshtml

_UploadPartial.cshtml looks like this:

<div class="container">
    <div class="col-md-12">
        <form method="post" enctype="multipart/form-data" asp-controller="Gallery" asp-action="Upload">
            <div class="form-group">
                <div class="col-md-12">
                    <div class="col-md-6">
                        <input class="form-control-file" type="file" name="files" multiple />
                    </div>
                    <div class="col-md-6">
                        <input class="form-control-file" type="submit" value="Upload" />
                    </div>
                </div>
            </div>
        </form>
    </div>
</div>
<hr>

As you can see I have asp-controller="Gallery" and asp-action="Upload", but when I inspect the form after deploying it to lambda it seems to be ignoring the controller tag.

Looks like I don't have enough points to embed an image therefore SO created a link

edit As per request, here is the contents of Gallery/index.cshtml

@model Dictionary<string, string>
    
@{
    ViewData["Title"] = "News page";
}
<center>
    @await Html.PartialAsync("_uploadPartial")


    @await Html.PartialAsync("_galleryPartial")
</center>

And the contents of my startup.cs:

using Amazon.S3;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Redacted.Helpers;

namespace Redacted;

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    // This method gets called by the runtime. Use this method to add services to the container.
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddControllersWithViews().AddRazorRuntimeCompilation();

        services.AddAWSService<IAmazonS3>();
        services.AddTransient<ImageHelper>();


        services.AddRazorPages();
        services.AddMvcCore();
        services.AddAuthentication(options =>
        {
            options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
            options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
            options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
        })
        .AddCookie()
        .AddOpenIdConnect(options =>
        {
            // Signin:
            options.ResponseType = Environment.GetEnvironmentVariable("ResponseType");
            options.MetadataAddress = Environment.GetEnvironmentVariable("MetadataAddress");
            options.ClientId = Environment.GetEnvironmentVariable("ClientId");
            options.ClientSecret = Environment.GetEnvironmentVariable("ClientSecret");
            // Signout
            options.Events = new OpenIdConnectEvents()
            {
                OnRedirectToIdentityProviderForSignOut = OnRedirectToIdentityProviderForSignOut
            };
        });

        services.AddAuthorization(options =>
        {
            options.AddPolicy("Admins", policy => policy.RequireAssertion(context => context.User.HasClaim(c => c.Type == "cognito:groups" && c.Value == "admins")));
            options.AddPolicy("Users", policy => policy.RequireAssertion(context => context.User.HasClaim(c => c.Type == "cognito:groups" && c.Value == "users" || c.Value == "admins")));
        });
    }


    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
        else
        {
            app.UseExceptionHandler("/Error");
            // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
            app.UseHsts();
        }

        app.UseHttpsRedirection();
        app.UseStaticFiles();

        app.UseRouting();

        app.UseAuthentication();
        app.UseAuthorization();

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllerRoute(
                name: "default",
                pattern: "{controller=Home}/{action=Index}/{id?}");
        });



    }

    private Task OnRedirectToIdentityProviderForSignOut(RedirectContext context)
    {


        context.ProtocolMessage.Scope = "openid";
        context.ProtocolMessage.ResponseType = "code";

        var cognitoDomain = Environment.GetEnvironmentVariable("CognitoDomain");
        var clientId = Environment.GetEnvironmentVariable("ClientId");
        var logoutUrl = Environment.GetEnvironmentVariable("LogoutUri");

        context.ProtocolMessage.IssuerAddress = $"{cognitoDomain}/logout?client_id={clientId}&logout_uri={logoutUrl}&redirect_uri={logoutUrl}";

        // delete cookies
        context.Properties.Items.Remove(CookieAuthenticationDefaults.AuthenticationScheme);
        // close openid session
        context.Properties.Items.Remove(OpenIdConnectDefaults.AuthenticationScheme);

        return Task.CompletedTask;
    }
}


Solution 1:[1]

After some time debugging I discovered the problem was with the infrastructure itsself. As i'm using API Gateway to route traffic to my lambda I was missing routes for POST. Simply adding a POST route method to /{proxy+} under api gateway/routes in the aws console OR using a terraform resource block similar to this:

resource "aws_apigatewayv2_route" "get_route" {
  api_id    = aws_apigatewayv2_api.lambda.id

  route_key = "GET /{proxy+}"
  target    = "integrations/${aws_apigatewayv2_integration.proxy_integration.id}"
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Mark Pendlebury