.net - Varför förstår inte min C#-endpoint mina Angular Reactive-formulärdata?


Ursäkta - Angular och TypeScript är fortfarande relativt grekiska för mig. Kort sagt, när jag skickar mitt formulär till en C#/.NET 3.1-endpoint, blir alla nyckel-värde-par från mitt formulär hopslagna till en stor nyckel utan motsvarande värde.

Jag har satt ihop ett formulär i en Angular-komponent, som

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;
            })
...

Mitt motsvarande HTML ser ut som

<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}}

För övrigt verkar uploadForm.value vara korrekt formaterad. När formuläret är ifyllt finns detta anrop:

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

                }
            })
    }

Vilket kallar detta:

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

}

Borde detta vara application/x-www-form-urlencoded? Multipart/form-data? Jag får fel i min backend om jag använder det senare. Hur som helst, ser den motsvarande slutpunkten ut så här

    [HttpPost]
    [DisableRequestSizeLimit]

    public IActionResult UploadEdiFile() {

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

Och det visar sig att 'isFoo' returnerar null. När jag tittar på request-objektet ser jag bara en nyckel och inget värde, det är som om det har kombinerat alla mina nyckelvärden till en enda massa.

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

uploadForm.value || json som visas i webbläsaren ser dock ut så här:

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

Var gjorde jag fel?

Svar #1

Enligt kommentarerna på frågan tror jag att detta är redo att köras - även om det känns mer som en tillfällig lösning än en ideal väg.

När en fil läggs till i formuläret, anropas följande:

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

Den nya versionen av min onSubmit-metod ser dock ut så här:

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();

            }
        })
}

Det skapar nu en ny instans av FormData och lägger till varje element från FormGroup, som sedan skickas. Postmetoden behöver inte längre någon form av speciell content-type-angivelse, och faktiskt gjorde varken urlencodedform eller multipart/form-data mig några tjänster.