Why isn't my C# endpoint understanding my Angular Reactive form data?

Viewed 83

Apologies - Angular and typescript are still relatively Greek to me. Long story short, when I post my form to a C#/.NET 3.1 endpoint, all of the key value pairs from my form are smooshed together into one big key with no corresponding value.

I've put together a form in an Angular component, like

export class DialogFileUpload {
    uploadForm = this.fb.group({
       isFoo:[''],
       hasHeaders:['', [Validators.required]],
       distributor:[''],
       file:['']
    });

    date = new FormControl(moment());
    
    distributors: IDistributor[] = [];


    get aliases() {
        return this.uploadForm.get('aliases') as FormArray;
    }
    constructor(
        public dialogRef: MatDialogRef<DialogFileUpload>,
        private fb: FormBuilder,
        private distributorService: DistributorService,
        private uploadService: UploadService) {}

    ngOnInit() {
        this.distributorService.getDistributors()
            .subscribe(distributors => {
                this.distributors = distributors;
            })
...

My corresponding HTML looks like

<form [formGroup]="uploadForm" (ngSubmit)="onSubmit()">
  <mat-form-field>
    <input matInput formControlName="effectiveMonth" [matDatepicker]="picker" [min]="sixMonthsAgo" [max]="today" placeholder="Effective Month">
    <mat-datepicker-toggle  matSuffix [for]="picker"></mat-datepicker-toggle>
    <mat-datepicker #picker 
                    startView="multi-year"
                    (yearSelected) = "chosenYearHandler($event)"
                    (monthSelected)= "closeDatePicker($event, picker)">
    </mat-datepicker>
  </mat-form-field>

  <mat-form-field appearance="fill" class="distributor-select">
    <mat-label>Distributor</mat-label>
    <mat-select formControlName="distributor">
      <mat-option value="-1">--</mat-option>
      <mat-option *ngFor="let distributor of distributors" [value]="distributor.id">
        {{distributor.name}}
      </mat-option>
    </mat-select>
  </mat-form-field>
  <input type="file" formControlName="file" (change)="appendFile($event.target.files)">

  <mat-radio-group 
    formControlName="isFoo">
    <mat-radio-button value="true">foo</mat-radio-button>
    <mat-radio-button value="false">Non-foo</mat-radio-button>
  </mat-radio-group>
  <mat-radio-group *ngIf="uploadForm.value.isFoo == 'false'"
    formControlName="hasHeaders">
    <mat-radio-button value="true">Has Headers</mat-radio-button>
    <mat-radio-button value="false">Headerless</mat-radio-button>
  </mat-radio-group>

  <div mat-dialog-actions>
    <button mat-button (click)="onNoClick()">Cancel</button>
    <button mat-raised-button color="primary" cdkFocusInitial>Ok</button>
  </div>
</form>

<br>{{uploadForm.value | json}}

As an aside, the uploadForm.value appears to be formatted correctly Once the form is completed, there's this call:

   onSubmit(): void {
        console.log(this.uploadForm.value);
        this.uploadService.postFileToServer(this.uploadForm.value)
            .subscribe({
                next: () =>{},
                error: () => {},
                complete: () =>{
                    this.dialogRef.close();
                    window.location.reload();

                }
            })
    }

Which calls this:

postFileToServer(data: FormData) {
    let url = this.baseUrl + this.fileUploadUrl;
    return this.http.post(url, data, {headers : { "Content-Type" : "Application/x-www-form-urlencoded"}});

}

Should this be application/x-www-form-urlencoded? Multipart/form-data? I get errors in my backend if I use the latter. Anyway, the corresponding endpoint looks like this

    [HttpPost]
    [DisableRequestSizeLimit]

    public IActionResult UploadEdiFile() {

        var foo = Request.Form;
        
        Console.WriteLine(foo["distributor"]);
        bool isFoo = Request.Form["isFoo"] == "true" ? true : false;

And it turns up that 'isFoo' returns null. In looking at the request object, I see only one key and no value, it's like it's combined all my key values into one lump.

{[{"isFoo":"true","hasHeaders":"true","distributor":"5","file":"","effectiveMonth":"2021-06-21T22:54:18.038Z"}, {}]}

The uploadForm.value || json displayed in browser, however, looks like:

{ "isFoo": "true", "hasHeaders": "true", "distributor": "5", "file": "", "effectiveMonth": "2021-06-21T22:51:39.688Z" }

Where'd I mess up?

1 Answers

Per the comments on the question, I think this is good to go - though it feels more like a workaround than a happy path.

When adding a file to the form, the following is called:

appendFile(uploadFile: File) : void {
    this.uploadForm.controls['file'].setValue(uploadFile, uploadFile ? uploadFile.name : '');
}

The new version of my onSubmit method, however, looks like this:

onSubmit(): void {
    const formData = new FormData();
    formData.append('file', this.uploadForm.get('file').value[0]);
    formData.append('isFoo', this.uploadForm.get('isFoo').value);
    formData.append('hasHeasders', this.uploadForm.get('hasHeaders').value);
    formData.append('effectiveMonth', this.uploadForm.get('effectiveMonth').value);
    formData.append('distributor', this.uploadForm.get('distributor').value);
    this.uploadService.postFileToServer(formData)
        .subscribe({
            next: () =>{},
            error: () => {},
            complete: () =>{
                this.dialogRef.close();
                window.location.reload();

            }
        })
}

It now creates a new instance of FormData, and appends each element from the FormGroup, which is then posted. The post method no longer needs some sort of special content-type callout, and indeed the urlencodedform nor multipart/form-data did me any favors.

Related