曾问过类似的问题,但在浏览了所有有关该主题的博客文章之后,我仍然无法弄清这一点,请原谅。
我正在创建一个简单的博客,该博客包含(针对此问题)两部分,Angular 8中的前端SPA和ASP.NET Core 3中的后端API。在我的前端的一部分中,我尝试上传图片用作新创建的博客的图片。当我尝试上传图像时,后端中生成的IFormFile总是输出到null
。下面是代码,非常感谢您的帮助!
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
from 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();
}
我的BlogController的签名如下:
[HttpPost]
public async Task<ActionResult<Blog>> PostBlog([FromForm]PostBlogModel blogModel)
使用PostBlogModel如下:
public class PostBlogModel
{
public string Name { get; set; }
public IFormFile TileImage { get; set; }
}
我已经实现了日志记录中间件以进行调试。输出如下(我看到前端由于某种原因发送了application / json而不是multipart / form-data,但是我不确定为什么或如何解决...)
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"}
我的BlogController看起来像这样:
[HttpPost] public async Task<ActionResult<Blog>> PostBlog([FromForm]PostBlogModel blogModel)
似乎您想使用form-data传递数据,要实现它,您可以参考以下代码示例。
。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();
}
测试结果
第一
<input type="file">
与ngModel
或formControlName
绑定,仅捕获value property
但是实际上,当我们提交表单时,我们需要files property
,以便我们可以创建将应用于所有项目<input type="file">
元素的自定义指令,因此,当我们提交表单,获得文件属性
之前
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 rating 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]);
}
}
之后
要使用Http上传文件,应使用multipart/form-data
对数据进行编码,允许通过http帖子发送文件,以便使用FormData
,
FormData对象将自动生成带有MIME的请求数据键入现有服务器可以处理的multipart / form-data。要添加一个您使用扩展名可以使用的File对象的数据的file字段从文件路径构造。然后可以简单地将FormData对象设为传递给XMLHttpRequest:
所以您的提交方法应该像
onSubmit() {
let formData: FormData = new FormData();
Object.keys(this.newBlogForm).forEach(key => {
formData.append(key, this.newBlogForm[key])
});
//pass formData to your service
}
Thred
在postBlog
方法中创建Subject
没有任何好处,您可以只返回http.post,然后在调用方方法中使用指定是subscribe
还是使用async/await
触发http调用
onSubmit() {
.....
this.postBlog(formData).subscribe(
result => { }
);
}
async onSubmit() {
.....
let res = await this.postBlog(formData).toPromise();
}