c# - Hur man laddar upp fil från Angular till ASP.NET Core Web API
Liknande frågor har ställts, men efter att ha tittat igenom alla dessa och många blogginlägg om ämnet har jag inte kunnat lista ut detta, så var snäll och förlåt mig.
Jag skapar en enkel blogg med (för syftet med denna fråga) två delar, en front-end SPA i Angular 8 och en back-end API i ASP.NET Core 3. I en del av min front-end försöker jag ladda upp en bild som ska användas som bilden för ett nyss skapat blogginlägg. När jag försöker ladda upp en bild, blir den resulterande IFormFile i backend alltid null. Nedan är koden, all hjälp uppskattas stort!
new-blog.component.html:
<form [formGroup]="newBlogForm" (ngSubmit)="onSubmit(newBlogForm.value)">
<div>
<label for="Name">
Blog Name
</label>
<input type="text" formControlName="Name">
</div>
<div>
<label for="TileImage">
Tile Image
</label>
<input type="file" formControlName="TileImage">
</div>
<button type="submit">Create Blog</button>
</form>
new-blog.component.ts:
import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, FormControl } from '@angular/forms';
import { BlogService } from '../blog-services/blog.service';
@Component({
selector: 'app-new-blog',
templateUrl: './new-blog.component.html',
styleUrls: ['./new-blog.component.css']
})
export class NewBlogComponent implements OnInit {
private newBlogForm: FormGroup;
constructor(private formBuilder: FormBuilder, private blogService: BlogService) { }
ngOnInit() {
this.newBlogForm = this.formBuilder.group({
Name: new FormControl(null),
TileImage: new FormControl(null)
});
}
onSubmit(blogData: FormData) {
console.log('new blog has been submitted.', blogData);
this.blogService.postBlog(blogData);
this.newBlogForm.reset();
}
}
postBlog från blog.service.ts:
postBlog(blogData: FormData): Observable<any> {
const postBlogSubject = new Subject();
this.appOptions.subscribe(
(options) => {
const url = options.blogAPIUrl + '/Blogs';
this.http
.post(url, blogData)
.subscribe(
(blog) => {
postBlogSubject.next(blog);
}
);
}
);
return postBlogSubject.asObservable();
}
Signaturen för min BlogController ser ut så här:
[HttpPost]
public async Task<ActionResult<Blog>> PostBlog([FromForm]PostBlogModel blogModel)
med PostBlogModel på följande sätt:
public class PostBlogModel
{
public string Name { get; set; }
public IFormFile TileImage { get; set; }
}
Jag har implementerat loggningsmiddleware för att försöka felsöka. Utdata är som följer (jag ser att front-end av någon anledning skickar application/json istället för multipart/form-data men jag är inte säker på varför eller hur man åtgärdar...)
blogapi_1 | info: Microsoft.AspNetCore.Hosting.Diagnostics[2]
blogapi_1 | Request finished in 170.16740000000001ms 500
blogapi_1 | info: Microsoft.AspNetCore.Hosting.Diagnostics[1]
blogapi_1 | Request starting HTTP/1.1 OPTIONS http://localhost:5432/api/v1/Blogs
blogapi_1 | dbug: BlogAPI.Middleware.RequestResponseLoggingMiddleware[0]
blogapi_1 | HTTP Request: Headers:
blogapi_1 | key: Connection, values: keep-alive
blogapi_1 | key: Accept, values: */*
blogapi_1 | key: Accept-Encoding, values: gzip, deflate, br
blogapi_1 | key: Accept-Language, values: en-US,en-IN;q=0.9,en;q=0.8,en-GB;q=0.7
blogapi_1 | key: Host, values: localhost:5432
blogapi_1 | key: Referer, values: http://localhost:5431/blog/new-blog
blogapi_1 | key: User-Agent, values: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36
blogapi_1 | key: Origin, values: http://localhost:5431
blogapi_1 | key: Access-Control-Request-Method, values: POST
blogapi_1 | key: Access-Control-Request-Headers, values: content-type
blogapi_1 | key: Sec-Fetch-Site, values: same-site
blogapi_1 | key: Sec-Fetch-Mode, values: cors
blogapi_1 |
blogapi_1 | type:
blogapi_1 | scheme: http
blogapi_1 | host+path: localhost:5432/api/v1/Blogs
blogapi_1 | queryString:
blogapi_1 | body:
blogapi_1 | info: Microsoft.AspNetCore.Cors.Infrastructure.CorsService[4]
blogapi_1 | CORS policy execution successful.
blogapi_1 | dbug: BlogAPI.Middleware.RequestResponseLoggingMiddleware[0]
blogapi_1 | HTTP Response: Headers:
blogapi_1 | key: Access-Control-Allow-Headers, values: Content-Type
blogapi_1 | key: Access-Control-Allow-Origin, values: http://localhost:5431
blogapi_1 |
blogapi_1 | statusCode: 204
blogapi_1 | responseBody:
blogapi_1 | info: Microsoft.AspNetCore.Hosting.Diagnostics[2]
blogapi_1 | Request finished in 58.5088ms 204
blogapi_1 | info: Microsoft.AspNetCore.Hosting.Diagnostics[1]
blogapi_1 | Request starting HTTP/1.1 POST http://localhost:5432/api/v1/Blogs application/json 56
blogapi_1 | dbug: BlogAPI.Middleware.RequestResponseLoggingMiddleware[0]
blogapi_1 | HTTP Request: Headers:
blogapi_1 | key: Connection, values: keep-alive
blogapi_1 | key: Content-Type, values: application/json
blogapi_1 | key: Accept, values: application/json, text/plain, */*
blogapi_1 | key: Accept-Encoding, values: gzip, deflate, br
blogapi_1 | key: Accept-Language, values: en-US,en-IN;q=0.9,en;q=0.8,en-GB;q=0.7
blogapi_1 | key: Host, values: localhost:5432
blogapi_1 | key: Referer, values: http://localhost:5431/blog/new-blog
blogapi_1 | key: User-Agent, values: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36
blogapi_1 | key: Origin, values: http://localhost:5431
blogapi_1 | key: Content-Length, values: 56
blogapi_1 | key: Sec-Fetch-Site, values: same-site
blogapi_1 | key: Sec-Fetch-Mode, values: cors
blogapi_1 |
blogapi_1 | type: application/json
blogapi_1 | scheme: http
blogapi_1 | host+path: localhost:5432/api/v1/Blogs
blogapi_1 | queryString:
blogapi_1 | body: {"Name":"test","TileImage":"C:\\fakepath\\DSC_0327.jpg"}
Svar #1
min BlogController ser ut så här:
[HttpPost] public async Task<ActionResult<Blog>> PostBlog([FromForm]PostBlogModel blogModel)
Det verkar som att du vill skicka data med form-data, för att uppnå det kan du referera till följande kodexempel.
.component.html
<form [formGroup]="newBlogForm" (ngSubmit)="onSubmit(newBlogForm.value)">
<div>
<label for="Name">
Blog Name
</label>
<input type="text" formControlName="Name">
</div>
<div>
<label for="TileImage">
Tile Image
</label>
<input type="file" formControlName="TileImage" (change)="onSelectFile($event)" >
</div>
<button type="submit">Create Blog</button>
</form>
.component.ts
selectedFile: File = null;
private newBlogForm: FormGroup;
constructor(private http: HttpClient) { }
ngOnInit() {
this.newBlogForm = new FormGroup({
Name: new FormControl(null),
TileImage: new FormControl(null)
});
}
onSelectFile(fileInput: any) {
this.selectedFile = <File>fileInput.target.files[0];
}
onSubmit(data) {
const formData = new FormData();
formData.append('Name', data.Name);
formData.append('TileImage', this.selectedFile);
this.http.post('your_url_here', formData)
.subscribe(res => {
alert('Uploaded!!');
});
this.newBlogForm.reset();
}
Testresultat
Svar #2
Första
Binding av <input type="file"> med Angular med ngModel eller formControlName kommer bara att fånga value-egenskapen, men egentligen när vi skickar formuläret behöver vi files-egenskapen så vi kan skapa en anpassad direktiv som kommer att tillämpas på alla <input type="file"> element i projektet så att när vi skickar formuläret får vi file-egenskapen
Innan
import { Directive, forwardRef, HostListener, ElementRef, Renderer2 } from '@angular/core';
import { NG_VALUE_ACCESSOR, ControlValueAccessor } from '@angular/forms';
@Directive({
selector : `input[type=file][formControlName],
input[type=file][formControl],
input[type=file][ngModel]`,
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: FileValueAccessorDirective,
multi: true
}
]
})
export class FileValueAccessorDirective implements ControlValueAccessor {
constructor(private elementRef: ElementRef, private render: Renderer2) {
}
// Function to call when the file changes.
onChange = (file: any) => {}
//fire when the form value changed programmaticly
writeValue(value: any): void {
}
//fire only one time to register on change event
registerOnChange = (fn: any) => { this.onChange = fn; }
//fire only one time to register on touched event
registerOnTouched = (fn: any) => { }
//Disable the input
setDisabledState?(isDisabled: boolean): void {
}
//listen to change event
@HostListener('change', ['$event.target.files'])
handleChange(file) {
this.onChange(file[0]);
}
}
Efter
Andra
För att ladda upp filer med Http bör dina data kodas med multipart/form-data som tillåter filer att skickas över http-post, därför används FormData.
Ett FormData-objekt genererar automatiskt begäransdata med MIME-typen multipart/form-data som befintliga servrar kan bearbeta. För att lägga till ett filfält i datan använder du ett File-objekt som en tillägg kan skapa från filvägen. FormData-objektet kan sedan enkelt skickas till XMLHttpRequest:
Http Ladda upp filer
så din submit-metod bör vara som
onSubmit() {
let formData: FormData = new FormData();
Object.keys(this.newBlogForm.value).forEach(key => {
formData.append(key, this.newBlogForm.value[key])
});
//pass formData to your service
}
Tredje
I din postBlog-metod skapar du Subject utan någon nytta, du kan bara returnera http.post och sedan i den anropande metoden specificera om du subscribe eller använda async/await för att göra http-anropet.
onSubmit() {
.....
this.postBlog(formData).subscribe(
result => { }
);
}
async onSubmit() {
.....
let res = await this.postBlog(formData).toPromise();
}


