用VSCode开发一个asp.net core2.0+angular5项目(5): Angular5+asp.net core 2.0 web api文件上传

简介: 第一部分: http://www.cnblogs.com/cgzl/p/8478993.html 第二部分: http://www.cnblogs.com/cgzl/p/8481825.html 第三部分: https://www.

第一部分: http://www.cnblogs.com/cgzl/p/8478993.html

第二部分: http://www.cnblogs.com/cgzl/p/8481825.html

第三部分: https://www.cnblogs.com/cgzl/p/8525541.html

第四部分: https://www.cnblogs.com/cgzl/p/8536350.html

这部分就讲从angular5的客户端上传图片到asp.net core 2.0的 web api.

这是需要的源码: https://pan.baidu.com/s/1Eqc4MRiQDwOHmu0OHyttqA

当前的效果如下:

点击这个超链接后:

好的, 下面开始编写上传相关的代码.

Asp.net core 2.0 文件上传

按照顺序, 先建立Photo的domain model:

建立Models/Photo.cs:

using System.ComponentModel.DataAnnotations;

namespace Tv.Models
{
    public class Photo
    {
        public int Id { get; set; }
        [Required]
        [StringLength(255)]
        public string FileName { get; set; }
    }
}

然后编辑TvShow.cs:

using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;

namespace Tv.Models
{
    public class TvShow
    {
        public TvShow()
        {
            Photoes = new List<Photo>();
        }

        public int Id { get; set; }
        [Required]
        [StringLength(50)]
        public string Name { get; set; }
        public int TvNetworkId { get; set; }
        public TvNetwork TvNetwork { get; set; }

        public ICollection<Photo> Photoes { get; set; }
    }
}

TvContext.cs:

using Microsoft.EntityFrameworkCore;
using Tv.Models;

namespace Tv.Database
{
    public class TvContext : DbContext
    {
        public TvContext(DbContextOptions<TvContext> options)
            : base(options)
        {

        }

        public DbSet<TvNetwork> TvNetworks { get; set; }
        public DbSet<TvShow> TvShows { get; set; }
        public DbSet<Photo> Photoes { get; set; }
    }
}

然后添加迁移和更新数据库, 您应该知道怎么做了, 这部分就略了.

添加PhotoViewModel.cs:

namespace Tv.ViewModels
{
    public class PhotoViewModel
    {
        public int Id { get; set; }
        public string FileName { get; set; }
    }
}

不要忘了做一下Mapping映射, 这里我就不写了.

然后建立PhotoesController.cs:

using System;
using System.IO;
using System.Threading.Tasks;
using AutoMapper;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Tv.Database;
using Tv.Models;
using Tv.ViewModels;

namespace Tv.Controllers
{
    [Route("api/tvshows/{tvShowId}/photoes")]
    public class PhotoesController : Controller
    {
        private readonly IHostingEnvironment host;
        private readonly ITvRepository tvRepository;
        private readonly IUnitOfWork unitOfWork;
        private readonly IMapper mapper;

        public PhotoesController(IHostingEnvironment host, ITvRepository tvRepository, IUnitOfWork unitOfWork, IMapper mapper)
        {
            this.host = host;
            this.tvRepository = tvRepository;
            this.unitOfWork = unitOfWork;
            this.mapper = mapper;
        }

        [HttpPost]
        public async Task<IActionResult> Upload(int tvShowId, IFormFile file)
        {
            var tvShow = await tvRepository.GetTvShowByIdAsync(tvShowId, includeRelated: false);
            if (tvShow == null)
            {
                return NotFound();
            }
            var uploadsFolderPath = Path.Combine(host.WebRootPath, "Uploads");
            if (!Directory.Exists(uploadsFolderPath))
            {
                Directory.CreateDirectory(uploadsFolderPath);
            }
            var fileName = Guid.NewGuid().ToString() + Path.GetExtension(file.FileName);
            var filePath = Path.Combine(uploadsFolderPath, fileName);

            using (var stream = new FileStream(filePath, FileMode.Create))
            {
                await file.CopyToAsync(stream);
            }

            var photo = new Photo
            {
                FileName = fileName
            };
            tvShow.Photoes.Add(photo);
            await unitOfWork.SaveAsync();
            var result = mapper.Map<Photo, PhotoViewModel>(photo);
            return Ok(result);
        }
    }
}

这里要简单讲一下. asp.net core 上传文件的文档在这: https://docs.microsoft.com/en-us/aspnet/core/mvc/models/file-uploads

首先该controller的路由应该遵循web api的规范, 注意controller 的路由地址.

上传单个文件需要使用IFormFile作为Action的参数. 如果上传的是多个文件, 那么应该使用IFormCollection.

这里我做的是单文件上传, 所以使用IFormFile.

随后使用注入的IHostingEnvironment获得wwwroot目录, 我想要把文件上传到wwwroot/uploads下, 判断该目录是否存在, 如果不存在则创建该目录.

为了防黑, 把文件名改成Guid, 后缀名不变.

然后使用FileStream创建该文件.

后边的内容就是把文件名保存到数据库了.

接下来, 使用Postman来测试这个api.

打开postman, 按照图示输入:

注意这里的参数的key为file, 这个名字要与action的参数名一致:

send:

很好, 测试通过.

下面为Action添加一些验证:

这就是一些常规的验证, 没有什么特别的, 就不累述了.

针对这些东西, 您可以使用配置类, 并把相关的值放在appSettings.json里面. 这部分您自己学一下吧 https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/?tabs=basicconfiguration.

下面是客户端

Angular 5 文件上传

先做ui, tv-show-detail.component.html:

<form>
  <h2>基本信息</h2>
  <div class="form-group row">
    <label for="name" class="col-sm-2 col-form-label">名称</label>
    <div class="col-sm-10">
      <input type="text" readonly class="form-control-plaintext" id="name" value="{{model.name}}">
    </div>
  </div>
  <h2>电视剧照片</h2>
  <div class="form-group row">
    <label for="file" class="col-sm-2 col-form-label">照片</label>
    <input type="file" name="file" id="file" class="form-control" #fileInput (change)="upload()">
  </div>
</form>

注意这里使用了template reference.

然后创建一个photo.service:

import { Injectable } from '@angular/core';
import { HttpHeaders, HttpClient } from '@angular/common/http';

@Injectable()
export class PhotoService {

  constructor(
    private http: HttpClient
  ) { }

  upload(tvShowId: number, photo) {
    const formData = new FormData();
    formData.append('file', photo);
    return this.http.post(`/api/tvshows/${tvShowId}/photoes`, formData);
  }
}

其中post的参数类型是FormData, 它是js原生对象. formData里面文件的key要和后台Action方法的参数名一样.

最后改一下tv-show-detail.component.ts:

import { Component, OnInit, ElementRef, ViewChild } from '@angular/core';
import { TvShowService } from '../../services/tv-show.service';
import { Router, ActivatedRoute, ParamMap } from '@angular/router';
import { TvShow } from '../../models/tv-show';
import { Subscription } from 'rxjs/Subscription';
import { ToastrService } from 'ngx-toastr';
import { PhotoService } from '../../services/photo.service';

@Component({
  selector: 'app-tv-show-detail',
  templateUrl: './tv-show-detail.component.html',
  styleUrls: ['./tv-show-detail.component.css']
})
export class TvShowDetailComponent implements OnInit {

  tvShowId: number;
  @ViewChild('fileInput') fileInput: ElementRef;

  model: TvShow = new TvShow();
  busy: Subscription;

  constructor(
    private tvShowService: TvShowService,
    private router: Router,
    private route: ActivatedRoute,
    private toastr: ToastrService,
    private photoService: PhotoService
  ) { }

  ngOnInit() {
    this.route.paramMap.switchMap((params: ParamMap) => {
      this.tvShowId = +params.get('id');
      return this.tvShowService.getById(this.tvShowId);
    }).subscribe(item => {
      this.model = item;
    });
  }

  upload() {
    const ele = this.fileInput.nativeElement;
    this.photoService.upload(this.tvShowId, ele.files[0]).subscribe(x => {
      console.log(x);
    });
  }
}

 

如果上传成功, 那么回来先只做打印到log. 试一下:

上传成功. 文件即出现在wwwroot下, 文件名也保存到了数据库.

回显照片:

首先修改Photo.cs:

using System.ComponentModel.DataAnnotations;

namespace Tv.Models
{
    public class Photo
    {
        public int Id { get; set; }
        [Required]
        [StringLength(255)]
        public string FileName { get; set; }
        public int TvShowId { get; set; }
        public TvShow TvShow { get; set; }
    }
}

不要忘记迁移数据库.

然后创建Repository, 并注册:

using System.Collections.Generic;
using System.Threading.Tasks;
using Tv.Models;

namespace Tv.Database
{
    public interface IPhotoRepository
    {
        Task<List<Photo>> GetPhotoesByTvShowIdAsync(int tvShowId);
    }
}

 

using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Tv.Models;

namespace Tv.Database
{
    public class PhotoRepository : IPhotoRepository
    {
        private readonly TvContext context;

        public PhotoRepository(TvContext context)
        {
            this.context = context;
        }

        public async Task<List<Photo>> GetPhotoesByTvShowIdAsync(int tvShowId)
        {
            var photoes = await context.Photoes.Where(x => x.TvShowId == tvShowId).ToListAsync();
            return photoes;
        }
    }
}

 

最后修改PhotoesController:

using System;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using AutoMapper;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Tv.Database;
using Tv.Models;
using Tv.ViewModels;

namespace Tv.Controllers
{
    [Route("api/tvshows/{tvShowId}/photoes")]
    public class PhotoesController : Controller
    {
        private readonly IHostingEnvironment host;
        private readonly ITvRepository tvRepository;
        private readonly IUnitOfWork unitOfWork;
        private readonly IMapper mapper;
        private readonly IPhotoRepository photoRepository;

        public PhotoesController(IHostingEnvironment host, ITvRepository tvRepository, IUnitOfWork unitOfWork, IMapper mapper, IPhotoRepository photoRepository)
        {
            this.host = host;
            this.tvRepository = tvRepository;
            this.unitOfWork = unitOfWork;
            this.mapper = mapper;
            this.photoRepository = photoRepository;
        }

        [HttpPost]
        public async Task<IActionResult> Upload(int tvShowId, IFormFile file)
        {
            var tvShow = await tvRepository.GetTvShowByIdAsync(tvShowId, includeRelated: false);
            if (tvShow == null)
            {
                return NotFound();
            }

            if (file == null)
            {
                return BadRequest("File is null");
            }
            if (file.Length == 0)
            {
                return BadRequest("File is Empty");
            }
            if (file.Length > 10 * 1024 * 1024)
            {
                return BadRequest("文件大小不能超过10M");
            }
            var acceptedTypes = new[] { ".jpg", ".png", ".jpeg" };
            if (acceptedTypes.All(t => t != Path.GetExtension(file.FileName).ToLower()))
            {
                return BadRequest("文件类型不对");
            }

            var uploadsFolderPath = Path.Combine(host.WebRootPath, "Uploads");
            if (!Directory.Exists(uploadsFolderPath))
            {
                Directory.CreateDirectory(uploadsFolderPath);
            }
            var fileName = Guid.NewGuid().ToString() + Path.GetExtension(file.FileName);
            var filePath = Path.Combine(uploadsFolderPath, fileName);

            using (var stream = new FileStream(filePath, FileMode.Create))
            {
                await file.CopyToAsync(stream);

            }

            var photo = new Photo
            {
                FileName = fileName
            };
            tvShow.Photoes.Add(photo);
            await unitOfWork.SaveAsync();
            var result = mapper.Map<Photo, PhotoViewModel>(photo);
            return Ok(result);
        }

        [HttpGet]
        public async Task<IActionResult> GetPhotoesByTvShowId(int tvShowId)
        {
            var photoes = await photoRepository.GetPhotoesByTvShowIdAsync(tvShowId);
            return Ok(photoes);
        }
    }
}

 

然后修改angular部分:

添加Photo到model:

export class Photo {
    id: number;
    tvShowId: number;
    fileName: string;
}

 

修改photo service:

import { Injectable } from '@angular/core';
import { HttpHeaders, HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs/Observable';
import { Photo } from '../models/photo';

@Injectable()
export class PhotoService {

  constructor(
    private http: HttpClient
  ) { }

  upload(tvShowId: number, photo): Observable<Photo> {
    const formData = new FormData();
    formData.append('file', photo);
    return this.http.post<Photo>(`/api/tvshows/${tvShowId}/photoes`, formData);
  }

  getPhotoes(tvShowId: number): Observable<Photo[]> {
    return this.http.get<Photo[]>(`/api/tvshows/${tvShowId}/photoes`);
  }
}

 

tv-show-detail.component.html:

<form>
  <h2>基本信息</h2>
  <div class="form-group row">
    <label for="name" class="col-sm-2 col-form-label">名称</label>
    <div class="col-sm-10">
      <input type="text" readonly class="form-control-plaintext" id="name" value="{{model.name}}">
    </div>
  </div>
  <h2>电视剧照片</h2>
  <div class="form-group row">
    <label for="file" class="col-sm-2 col-form-label">照片</label>
    <input type="file" name="file" id="file" class="form-control" #fileInput (change)="upload()">
  </div>
  <div>
    <img [src]="'http://localhost:5000/Uploads/' + p.fileName" [alt]="p.fileName" *ngFor="let p of photoes" class="m-1" width="200"
      height="200" />
  </div>
</form>

 

tv-show-detail.component.ts:

import { Component, OnInit, ElementRef, ViewChild } from '@angular/core';
import { TvShowService } from '../../services/tv-show.service';
import { Router, ActivatedRoute, ParamMap } from '@angular/router';
import { TvShow } from '../../models/tv-show';
import { Subscription } from 'rxjs/Subscription';
import { ToastrService } from 'ngx-toastr';
import { PhotoService } from '../../services/photo.service';
import { Photo } from '../../models/photo';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/forkJoin';

@Component({
  selector: 'app-tv-show-detail',
  templateUrl: './tv-show-detail.component.html',
  styleUrls: ['./tv-show-detail.component.css']
})
export class TvShowDetailComponent implements OnInit {

  tvShowId: number;
  @ViewChild('fileInput') fileInput: ElementRef;

  model: TvShow = new TvShow();
  busy: Subscription;
  photoes: Photo[] = [];

  constructor(
    private tvShowService: TvShowService,
    private router: Router,
    private route: ActivatedRoute,
    private toastr: ToastrService,
    private photoService: PhotoService
  ) { }

  ngOnInit() {
    this.route.paramMap.switchMap((params: ParamMap) => {
      this.tvShowId = +params.get('id');
      return Observable.forkJoin<TvShow, Photo[]>(
        this.tvShowService.getById(this.tvShowId),
        this.photoService.getPhotoes(this.tvShowId)
      );
    }).subscribe(([tvShow, photoes]) => {
      this.model = tvShow;
      this.photoes = photoes;
    });
  }

  upload() {
    const ele = this.fileInput.nativeElement;
    this.photoService.upload(this.tvShowId, ele.files[0]).subscribe(photo => {
      this.photoes.push(photo);
    });
  }
}

 

这部分比较简单, 注意同时发送多个请求可以使用forkJoin.

看看效果:

如果照片没有显示出来, 可能是asp.net core没有启用静态文件到支持, 在Startup.cs添加这句话即可:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using AutoMapper;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Tv.Database;

namespace Tv
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddAutoMapper();
            // services.AddDbContext<TvContext>(opt => opt.UseSqlServer(Configuration["ConnectionStrings:Default"]));
            services.AddDbContext<TvContext>(opt => opt.UseSqlServer(Configuration.GetConnectionString("Default")));
            services.AddScoped<ITvRepository, TvRepository>();
            services.AddScoped<IPhotoRepository, PhotoRepository>();
            services.AddScoped<IUnitOfWork, UnitOfWork>();
            services.AddMvc();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            app.UseStaticFiles();
            app.UseMvc();
        }
    }
}

 

很好. 即使是刚添加完到照片也会即时显示出来.

上传进度显示.

首先创建一个修改photo service:

根据官方文档, 如果想要上传文件时显示进度, 那么应该使用HttpRequest, 并设置属性reportProgress为true:

import { Injectable } from '@angular/core';
import { HttpHeaders, HttpClient, HttpRequest, HttpEvent, HttpEventType, HttpErrorResponse } from '@angular/common/http';
import { Observable } from 'rxjs/Observable';
import { Photo } from '../models/photo';

@Injectable()
export class PhotoService {

  constructor(
    private http: HttpClient
  ) { }

  upload(tvShowId: number, photo: File) {
    const formData = new FormData();
    formData.append('file', photo);
    // return this.http.post<Photo>(`/api/tvshows/${tvShowId}/photoes`, formData);
    const req = new HttpRequest('POST', `/api/tvshows/${tvShowId}/photoes`, formData, {
      reportProgress: true
    });
    return this.http.request<Photo>(req);
  }

  getPhotoes(tvShowId: number): Observable<Photo[]> {
    return this.http.get<Photo[]>(`/api/tvshows/${tvShowId}/photoes`);
  }
}

回到 tv-show-detail.component.ts:

import { Component, OnInit, ElementRef, ViewChild } from '@angular/core';
import { TvShowService } from '../../services/tv-show.service';
import { Router, ActivatedRoute, ParamMap } from '@angular/router';
import { TvShow } from '../../models/tv-show';
import { Subscription } from 'rxjs/Subscription';
import { ToastrService } from 'ngx-toastr';
import { PhotoService } from '../../services/photo.service';
import { Photo } from '../../models/photo';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/forkJoin';
import { HttpEvent, HttpEventType } from '@angular/common/http';
import { HttpResponse } from 'selenium-webdriver/http';

@Component({
  selector: 'app-tv-show-detail',
  templateUrl: './tv-show-detail.component.html',
  styleUrls: ['./tv-show-detail.component.css']
})
export class TvShowDetailComponent implements OnInit {

  tvShowId: number;
  @ViewChild('fileInput') fileInput: ElementRef;

  model: TvShow = new TvShow();
  busy: Subscription;
  photoes: Photo[] = [];

  constructor(
    private tvShowService: TvShowService,
    private router: Router,
    private route: ActivatedRoute,
    private toastr: ToastrService,
    private photoService: PhotoService
  ) { }

  ngOnInit() {
    this.route.paramMap.switchMap((params: ParamMap) => {
      this.tvShowId = +params.get('id');
      return Observable.forkJoin<TvShow, Photo[]>(
        this.tvShowService.getById(this.tvShowId),
        this.photoService.getPhotoes(this.tvShowId)
      );
    }).subscribe(([tvShow, photoes]) => {
      this.model = tvShow;
      this.photoes = photoes;
    });
  }

  upload() {
    const ele = this.fileInput.nativeElement;
    const file = ele.files[0];
    this.photoService.upload(this.tvShowId, file).subscribe((event: HttpEvent<any>) => {
      switch (event.type) {
        case HttpEventType.Sent:
          console.log(`开始上传 "${file.name}", 大小是: ${file.size}.`);
          break;
        case HttpEventType.UploadProgress:
          const percentDone = Math.round(100 * event.loaded / event.total);
          console.log(`文件 "${file.name}" 的上传进度是 ${percentDone}%.`);
          break;
        case HttpEventType.Response:
          console.log(`文件 "${file.name}" 上传成功!`);
          this.toastr.success(`文件 "${file.name}" 上传成功!`);
          this.photoes.push(<Photo>(event.body));
          break;
        default:
          console.log(`文件 "${file.name}" 的事件类型: ${event.type}.`);
          break;
      }
    });
  }
}

 

这样, 上传文件时, 每个进度都会返回一个event, 我暂时就先把它打印到控制台.

看一下效果:

好的, 文件太小, 本地到速度又太快, 进度直接100%了. 

那我改一下Chrome的设置, 打开Developer Tools的Network 选项, 然后点击这里:

然后添加:

添加一个非常慢的网速限制:

最后选取这个限制:

实际上, 选择Slow 3G就很慢了.

这时, 再上传一次试试效果:

 

很好, 没问题.

接下来就是UI显示进度条的问题了, 很简单:

打开html:

<form>
  <h2>基本信息</h2>
  <div class="form-group row">
    <label for="name" class="col-sm-2 col-form-label">名称</label>
    <div class="col-sm-10">
      <input type="text" readonly class="form-control-plaintext" id="name" value="{{model.name}}">
    </div>
  </div>
  <h2>电视剧照片</h2>
  <div class="form-group row">
    <label for="file" class="col-sm-2 col-form-label">照片</label>
    <input type="file" name="file" id="file" class="form-control" #fileInput (change)="upload()">
  </div>
  <div class="progress" *ngIf="progress">
    <div class="progress-bar progress-bar-striped progress-bar-animated" role="progressbar" aria-valuenow="0" aria-valuemin="0"
      aria-valuemax="100" [style.width]="progress"></div>
  </div>
  <div>
    <img [src]="'http://localhost:5000/Uploads/' + p.fileName" [alt]="p.fileName" *ngFor="let p of photoes" class="m-1" width="200"
      height="200" />
  </div>
</form>

 

打开tv-show-detail.component.ts:

import { Component, OnInit, ElementRef, ViewChild } from '@angular/core';
import { TvShowService } from '../../services/tv-show.service';
import { Router, ActivatedRoute, ParamMap } from '@angular/router';
import { TvShow } from '../../models/tv-show';
import { Subscription } from 'rxjs/Subscription';
import { ToastrService } from 'ngx-toastr';
import { PhotoService } from '../../services/photo.service';
import { Photo } from '../../models/photo';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/forkJoin';
import { HttpEvent, HttpEventType } from '@angular/common/http';
import { HttpResponse } from 'selenium-webdriver/http';

@Component({
  selector: 'app-tv-show-detail',
  templateUrl: './tv-show-detail.component.html',
  styleUrls: ['./tv-show-detail.component.css']
})
export class TvShowDetailComponent implements OnInit {

  tvShowId: number;
  @ViewChild('fileInput') fileInput: ElementRef;

  model: TvShow = new TvShow();
  busy: Subscription;
  photoes: Photo[] = [];
  progress: string;

  constructor(
    private tvShowService: TvShowService,
    private router: Router,
    private route: ActivatedRoute,
    private toastr: ToastrService,
    private photoService: PhotoService
  ) { }

  ngOnInit() {
    this.route.paramMap.switchMap((params: ParamMap) => {
      this.tvShowId = +params.get('id');
      return Observable.forkJoin<TvShow, Photo[]>(
        this.tvShowService.getById(this.tvShowId),
        this.photoService.getPhotoes(this.tvShowId)
      );
    }).subscribe(([tvShow, photoes]) => {
      this.model = tvShow;
      this.photoes = photoes;
    });
  }

  upload() {
    const ele = this.fileInput.nativeElement;
    const file = ele.files[0];
ele.value = ''; // 上传图片后,把input的值清空.
this.photoService.upload(this.tvShowId, file).subscribe((event: HttpEvent<any>) => { switch (event.type) { case HttpEventType.Sent: console.log(`开始上传 "${file.name}", 大小是: ${file.size}.`); break; case HttpEventType.UploadProgress: const percentDone = Math.round(100 * event.loaded / event.total); this.progress = `${percentDone}%`; console.log(`文件 "${file.name}" 的上传进度是 ${percentDone}%.`); break; case HttpEventType.Response: console.log(`文件 "${file.name}" 上传成功!`); this.toastr.success(`文件 "${file.name}" 上传成功!`); this.photoes.push(<Photo>(event.body)); this.progress = null; break; default: console.log(`文件 "${file.name}" 的事件类型: ${event.type}.`); break; } }); } }

 

试试效果:

OK, 没问题!

 

今天就写到这吧.

下面是我的关于ASP.NET Core Web API相关技术的公众号--草根专栏:

目录
相关文章
|
26天前
|
XML JSON API
ServiceStack:不仅仅是一个高性能Web API和微服务框架,更是一站式解决方案——深入解析其多协议支持及简便开发流程,带您体验前所未有的.NET开发效率革命
【10月更文挑战第9天】ServiceStack 是一个高性能的 Web API 和微服务框架,支持 JSON、XML、CSV 等多种数据格式。它简化了 .NET 应用的开发流程,提供了直观的 RESTful 服务构建方式。ServiceStack 支持高并发请求和复杂业务逻辑,安装简单,通过 NuGet 包管理器即可快速集成。示例代码展示了如何创建一个返回当前日期的简单服务,包括定义请求和响应 DTO、实现服务逻辑、配置路由和宿主。ServiceStack 还支持 WebSocket、SignalR 等实时通信协议,具备自动验证、自动过滤器等丰富功能,适合快速搭建高性能、可扩展的服务端应用。
86 3
|
20天前
|
JSON C# 开发者
C#语言新特性深度剖析:提升你的.NET开发效率
【10月更文挑战第15天】C#语言凭借其强大的功能和易用性深受开发者喜爱。随着.NET平台的演进,C#不断引入新特性,如C# 7.0的模式匹配和C# 8.0的异步流,显著提升了开发效率和代码可维护性。本文将深入探讨这些新特性,助力开发者在.NET开发中更高效地利用它们。
29 1
|
26天前
|
开发框架 NoSQL MongoDB
C#/.NET/.NET Core开发实战教程集合
C#/.NET/.NET Core开发实战教程集合
|
26天前
|
C# Windows
一款基于.NET开发的简易高效的文件转换器
一款基于.NET开发的简易高效的文件转换器
|
26天前
|
开发框架 缓存 前端开发
WaterCloud:一套基于.NET 8.0 + LayUI的快速开发框架,完全开源免费!
WaterCloud:一套基于.NET 8.0 + LayUI的快速开发框架,完全开源免费!
|
3月前
|
API 开发者 UED
PrimeFaces:JSF的魔法衣橱,解锁UI设计的无限可能!
【8月更文挑战第31天】本文介绍如何结合 JSF(JavaServer Faces)和 PrimeFaces 构建美观且功能强大的现代用户界面。PrimeFaces 提供丰富的 UI 组件库,包括按钮、输入框、数据网格等,支持现代 Web 标准,简化界面开发。文章通过具体示例展示如何使用 `&lt;p:inputText&gt;` 和 `&lt;p:calendar&gt;` 等组件创建用户表单,并用 `&lt;p:dataTable&gt;` 展示数据集合,提升 JSF 应用的易用性和开发效率。
56 0
|
3月前
|
开发者 安全 SQL
JSF安全卫士:打造铜墙铁壁,抵御Web攻击的钢铁防线!
【8月更文挑战第31天】在构建Web应用时,安全性至关重要。JavaServer Faces (JSF)作为流行的Java Web框架,需防范如XSS、CSRF及SQL注入等攻击。本文详细介绍了如何在JSF应用中实施安全措施,包括严格验证用户输入、使用安全编码实践、实施内容安全策略(CSP)及使用CSRF tokens等。通过示例代码和最佳实践,帮助开发者构建更安全的应用,保护用户数据和系统资源。
51 0
|
3月前
|
开发者 C# C++
揭秘:如何轻松驾驭Uno Platform,用C#和XAML打造跨平台神器——一步步打造你的高性能WebAssembly应用!
【8月更文挑战第31天】Uno Platform 是一个跨平台应用程序框架,支持使用 C# 和 XAML 创建多平台应用,包括 Web。通过编译为 WebAssembly,Uno Platform 可实现在 Web 上运行高性能、接近原生体验的应用。本文介绍如何构建高效的 WebAssembly 应用:首先确保安装最新版本的 Visual Studio 或 VS Code 并配置 Uno Platform 开发环境;接着创建新的 Uno Platform 项目;然后通过安装工具链并使用 Uno WebAssembly CLI 编译应用;最后添加示例代码并测试应用。
95 0
|
3月前
|
前端开发 开发者 安全
JSF支付功能大揭秘:探索如何在Java世界中实现安全无缝的在线支付体验
【8月更文挑战第31天】在电子商务和在线交易日益普及的今天,实现在线支付功能已成为许多Web应用的必备需求。JavaServer Faces (JSF) 作为一种流行的Java Web框架,提供了丰富的组件和工具来构建用户界面,包括与支付网关集成以实现在线支付。支付网关是处理信用卡和借记卡支付的系统,在商家和银行之间起到桥梁作用。本文将探讨如何使用JSF与支付网关集成,以及实现在线支付功能时需要考虑的关键点
44 0
|
3月前
|
开发者 前端开发 开发框架
JSF与移动应用,开启全新交互体验!让你的Web应用轻松征服移动设备,让用户爱不释手!
【8月更文挑战第31天】在现代Web应用开发中,移动设备的普及使得构建移动友好的应用变得至关重要。尽管JSF(JavaServer Faces)主要用于Web应用开发,但结合Bootstrap等前端框架,也能实现优秀的移动交互体验。本文探讨如何在JSF应用中实现移动友好性,并通过示例代码展示具体实现方法。使用Bootstrap的响应式布局和组件可以确保JSF页面在移动设备上自适应,并提供友好的表单输入和提交体验。尽管JSF存在组件库较小和学习成本较高等局限性,但合理利用其特性仍能显著提升用户体验。通过不断学习和实践,开发者可以更好地掌握JSF应用的移动友好性,为Web应用开发贡献力量。
48 0

热门文章

最新文章