0

Having some issues trying to pass FormData from an Angular 12 app to a .Net Core API. Every attempt to send the data gets denied in preflight and never actually calls the backend endpoint (confirmed with Console.WriteLine there).

Followed the same patterns in many videos and websites and even created a side projected where it was able to work but for some reason in the main app it never sends. Only difference is that our main app uses PrimeNG as a framework so that is the main driver of collecting the files from the users. I've read their documentation but still not having success with it. Hopefully someone out there can help.

Attachment-Form Component

  File Upload <span class="example-text">(Max File Size: 10MB)</span>
  <p-fileUpload
    #form
    name="fileUpload[]"
    multiple="multiple"
    accept="{{ acceptedFiles }}"
    maxFileSize="10000000"
    customUpload="true"
    (uploadHandler)="uploadFiles($event)"
  >
    <ng-template pTemplate="content">
      <div
        *ngIf="form.files.length === 0 && uploadedFiles.length === 0"
        class="drag-drop"
      >
        <i class="pi pi-clone me-2"></i>DRAG & DROP FILES OR CLICK CHOOSE | THEN
        PRESS UPLOAD
      </div>

      <div *ngIf="uploadedFiles.length !== 0">
        <div class="p-fileupload-row" *ngFor="let file of uploadedFiles; let i">
          <div class="p-fileupload-filename">
            {{ file.name }} - {{ file.size }} bytes
          </div>
          <div>
            <button
              type="button"
              icon="pi pi-trash"
              pbutton
              (click)="showFile(file)"
              class="p-element p-button p-component p-button-icon-only p-button-danger"
            >
              <span class="p-button-icon pi pi-trash" aria-hidden="true"></span
              ><span aria-hidden="true" class="p-button-label">&nbsp;</span>
            </button>
          </div>
        </div>
      </div>
    </ng-template>
  </p-fileUpload>
</div>
<div>
  File Upload <span class="example-text">(Max File Size: 10MB)</span>
  <p-fileUpload
    #form
    name="fileUpload[]"
    multiple="multiple"
    accept="{{ acceptedFiles }}"
    maxFileSize="10000000"
    customUpload="true"
    (uploadHandler)="uploadFiles($event)"
  >
    <ng-template pTemplate="content">
      <div
        *ngIf="form.files.length === 0 && uploadedFiles.length === 0"
        class="drag-drop"
      >
        <i class="pi pi-clone me-2"></i>DRAG & DROP FILES OR CLICK CHOOSE | THEN
        PRESS UPLOAD
      </div>

      <div *ngIf="uploadedFiles.length !== 0">
        <div class="p-fileupload-row" *ngFor="let file of uploadedFiles; let i">
          <div class="p-fileupload-filename">
            {{ file.name }} - {{ file.size }} bytes
          </div>
          <div>
            <button
              type="button"
              icon="pi pi-trash"
              pbutton
              (click)="showFile(file)"
              class="p-element p-button p-component p-button-icon-only p-button-danger"
            >
              <span class="p-button-icon pi pi-trash" aria-hidden="true"></span
              ><span aria-hidden="true" class="p-button-label">&nbsp;</span>
            </button>
          </div>
        </div>
      </div>
    </ng-template>
  </p-fileUpload>
</div>

Form Component (for custom upload event)

uploadFiles(event) {
    let files: any = <File>event.files;
    // console.log(files);

    const fileData = new FormData();
    for (let i = 0; i < files.length; i++) {
      fileData.append('file', files[i], files[i].name);
      // Console Log for my sake
      fileData.forEach((item) => console.log('fileData', item));
    }
 this.attachmentsService.uploadFiles(fileData).subscribe(
      (res) => console.log(res),
      (err) => console.log(err)
    );
  }

Attachment Service

  uploadFiles(upload: any) {
    //Confirmation they are still there
    upload.forEach((item) => console.log('in api call', item));
    
    return this.http.post(
      'apiendpoint',
      upload
    );
  }

//I've used these headers and additional content-type headers as well and nothing worked
  headers = new HttpHeaders()
    .set('content-type', 'application/json')
    .set('Access-Control-Allow-Origin', '*')
    .set('Access-Control-Allow-Methods', '*');

Failed Call Image

Any ideas are apricated. Thanks,

2/7 Update for API Info

Startup.cs

        public void ConfigureServices(IServiceCollection services)
        {

            services.AddControllers();
            services.AddSwaggerGen(c =>
            {
                c.SwaggerDoc("v1", new OpenApiInfo { Title = "AttachmentService", Version = "v1" });
            });

            services.AddCors(c => {
                c.AddPolicy("AllowOrigin", options => options.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader());
            });

            services.AddDbContext<AttachmentContext>(options =>
                options.UseNpgsql(Configuration["PGConnection"]));

            services.AddScoped<IAttachmentData, AttachmentData>();
            services.AddControllers();
            services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies());

        }

        // 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();
                app.UseSwagger();
                app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "AttachmentService v1"));
            }

            app.UseHttpsRedirection();

            app.UseRouting();

            app.UseCors("AllowOrigin");

            app.UseAuthorization();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });
        }
    }

Controller

     // [HttpOptions] <- Currently commented just as trial but doesn't work while on
        [HttpPost, Route("files")]
        public ActionResult<string> PostFiles(IFormCollection formData)
        {
            Console.WriteLine("post fired");
            // rest of logic
        }
Shinoblee
  • 3
  • 3

3 Answers3

0

The problem is not related to FormData nor PrimeNG. You are having trouble with CORS. It is likely that it worked before because you had the Angular App and the Web API on the same domain and port and it is equally likely that now it is not working because they are no longer on the same domain or port.

The better solution, in the long term, is to enable CORS on the server side. Since you mentioned that you are using ASP.NET Core, you should follow their documentation as it is quite good.

Also, if you don't know what CORS (Cross-Origin Resource Sharing) is, there is a great article on the MDN website explaining everything.

Essential, your browser is denying your request because it does not comply with a security feature that browsers have which is called SOP (Same-Origin Policy). CORS is a mechanism to loosen up constraints applied by SOP.

If you need further help, add the version of the .NET Core that you are using and the content of your Startup.cs file to your question.

Edit Update based on the question update.

Instead of:

app.UseEndpoints(endpoints => {
  endpoints.MapControllers();
});

Do:

app.MapControllers();

If you really need to use endpoint routing, you can do:

app.UseEndpoints(endpoints => {
  endpoints.MapControllers().RequireCors("AllowOrigin");
});

Resources:

David Fontes
  • 1,427
  • 2
  • 11
  • 16
  • The strange thing is I do have CORS enabled in the API. Looked at that as an issue and created a basic get function and was able to connect to it and receive an OK 200 status back from the Angular app. Maybe I need to do something different for the form data? ConfigurationServices services.AddCors(c => { c.AddPolicy("AllowOrigin", options => options.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader()); }); Configuration app.UseCors("AllowOrigin"); – Shinoblee Feb 05 '22 at 20:52
  • Some requests don't trigger preflight request, those are called _simple requests_, so I'm guessing that your simple function, is one of those. Another peculiarity about the ASP .NET Core, is that they use a middleware and if you use CORS, you need to apply it in some specific position. Overall, the call to `UseCors` must be placed after `UseRouting`, but before `UseAuthorization`. For more information, check out their [documentation](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/middleware/?view=aspnetcore-6.0#middleware-order) or update your question and I will update my answer. – David Fontes Feb 05 '22 at 21:08
  • I did have the usecors in the wrong place but even after making the correction I still get the preflight error. I've tried some options found on this section [Cors Preflight](https://learn.microsoft.com/en-us/aspnet/core/security/cors?view=aspnetcore-6.0#preflight-requests) but nothing seems to work. It is strange cause all our other apis with post methods ran into this issue and it has only come by trying to post a file to a server. – Shinoblee Feb 06 '22 at 16:33
  • Which options did you try? The best way for us to help you, is if you update your question with the relevant code, in this case, with the `Startup.cs` file content. – David Fontes Feb 06 '22 at 17:45
  • Added the code from the Startup.cs file and Controller. Hopefully it is enought to help figure out the issue. – Shinoblee Feb 07 '22 at 13:09
  • I have updated my answer. Let me know if it worked. If not, we can go to the chat and continue there. – David Fontes Feb 07 '22 at 15:23
  • Sorry for the delay on this. Added the two options above and neither worked for me. But ultimately I figured out that it was denying the request at the http:5000 port but would accept it at the https:5001 port. Never ran into that issue before but maybe it seems that posts have to go through https? Thanks for all the ideas though, learned more than I should know about CORS now. – Shinoblee Feb 09 '22 at 20:25
  • I'm glad that you worked it out :) But that is weird indeed. POSTs methods (or any other method for that matter) do not have to be used over HTTPS, it is recommended obviously, but not a restriction. – David Fontes Feb 09 '22 at 22:11
0

This is not a problem with your Angular code. The problem is that your server is not configured to allow cross origin requests (CORS). You dev server is on port 5000 and your Angular dev server is on port 4200, making the request from a different origin.

Here's the documentation on how to enable CORs in dotnet.

This stackoverflow post also has some good information.

The way I've configured CORs in dotnet is in Startup.cs:

public class Startup {
    public void ConfigureServices(IServiceCollection services){
        //some configuration here

        services.AddCors();

        //likely some more configuration
    }
    
    public void Configure(IApplicationBuilder app, IWebHostEnvironment env, MyDbContext dataContext){
        //some configuration

        app.UseCors(
            options => options
                .AllowAnyHeader()
                .WithMethods("OPTIONS", "GET", "POST", "PUT", "DELETE")
                .AllowAnyOrigin()
        );

        //perhaps some more configuration
    }
}

I'd recommend giving the dotnet core angular template a try. The project template will set up your app correctly and you do not have to mess with CORs.

spots
  • 2,483
  • 5
  • 23
  • 38
  • The strange thing is I do have CORS enabled in the API. Looked at that as an issue and created a basic get function and was able to connect to it and receive an OK 200 status back from the Angular app. Maybe I need to do something different for the form data? ConfigurationServices services.AddCors(c => { c.AddPolicy("AllowOrigin", options => options.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader()); }); Configuration app.UseCors("AllowOrigin"); – Shinoblee Feb 05 '22 at 20:54
0

Ultimately I figured out that it was denying the request at the http:5000 port but would accept it at the https:5001 port. It was taking my requests and trying to redirect them to the https port which was causing the deny.

Maybe consider just using https:5001 for post requests?

Shinoblee
  • 3
  • 3