بررسی روش آپلود فایل‌ها از طریق یک برنامه‌ی Angular به یک برنامه‌ی ASP.NET Core
اندازه‌ی قلم متن
تخمین مدت زمان مطالعه‌ی مطلب: یازده دقیقه

پیشنیازها
«بررسی روش آپلود فایل‌ها در ASP.NET Core»
«ارسال فایل و تصویر به همراه داده‌های دیگر از طریق jQuery Ajax»
- در مطلب اول، روش دریافت فایل‌ها از کلاینت، در سمت سرور و ذخیره سازی آن‌ها در یک برنامه‌ی ASP.NET Core بررسی شده‌است که کلیات آن در اینجا نیز صادق است.
- در مطلب دوم، روش کار با FormData استاندارد بررسی شده‌است. هرچند در مطلب جاری از jQuery استفاده نمی‌شود، اما نکات نحوه‌ی کار با شیء FormData استاندارد، در اینجا نیز یکی است.


تدارک مقدمات مثال این قسمت

این مثال در ادامه‌ی همین سری کار با فرم‌های مبتنی بر قالب‌ها است. به همین جهت ابتدا ماژول جدید UploadFile را به آن اضافه می‌کنیم:
 >ng g m UploadFile -m app.module --routing
همچنین به فایل app.module.ts مراجعه کرده و UploadFileModule را بجای UploadFileRoutingModule در قسمت imports معرفی می‌کنیم. سپس به این ماژول جدید، کامپوننت فرم ثبت یک درخواست پشتیبانی را اضافه خواهیم کرد:
 >ng g c UploadFile/UploadFileSimple
که اینکار سبب به روز رسانی فایل upload-file.module.ts و افزوده شدن UploadFileSimpleComponent به قسمت declarations آن می‌شود.
در ادامه کلاس مدل معادل فرم ثبت نام یک درخواست پشتیبانی را تعریف می‌کنیم:
 >ng g cl UploadFile/Ticket
با این محتوا:
export class Ticket {
  constructor(public description: string = "") {}
}
در اینجا Ticket تعریف شده دارای یک خاصیت توضیحات است و این فرم به همراه فیلد ارسال چندین فایل نیز می‌باشد که نیازی به درج آن‌ها در کلاس فوق نیست:



ایجاد مقدمات کامپوننت UploadFileSimple و قالب آن

پس از ایجاد ساختار کلاس Ticket، یک وهله از آن‌را به نام model ایجاد کرده و در اختیار قالب آن قرار می‌دهیم:
import { Ticket } from "./../ticket";

export class UploadFileSimpleComponent implements OnInit {
  model = new Ticket();
سپس قالب این کامپوننت و یا همان فایل upload-file-simple.component.html را به صورت ذیل تکمیل می‌کنیم:
<div class="container">
  <h3>Support Form</h3>
  <form #form="ngForm" (submit)="submitForm(form)" novalidate>
    <div class="form-group" [class.has-error]="description.invalid && description.touched">
      <label class="control-label">Description</label>
      <input #description="ngModel" required type="text" class="form-control"
        name="description" [(ngModel)]="model.description">
      <div *ngIf="description.invalid && description.touched">
        <div class="alert alert-danger"  *ngIf="description.errors.required">
          description is required.
        </div>
      </div>
    </div>

    <div class="form-group">
      <label class="control-label">Screenshot(s)</label>
      <input #screenshotInput required type="file" multiple (change)="fileChange($event)"
        class="form-control" name="screenshot">
    </div>

    <button class="btn btn-primary" [disabled]="form.invalid" type="submit">Ok</button>
  </form>
</div>
در اینجا ابتدا فیلد توضیحات درخواست جدید، ارائه و به خاصیت model.description متصل شده‌است. همچنین این فیلد با ویژگی required مزین، و اجباری بودن آن بررسی گردیده‌است.
سپس در انتها، فیلد آپلود را مشاهده می‌کنید؛ با این ویژگی‌ها:
الف) ngModel ایی به آن متصل نشده‌است؛ چون روش کار با آن متفاوت است.
ب) یک template reference variable به نام screenshotInput# در آن تعریف شده‌است. از این متغیر، در کامپوننت قالب استفاده خواهیم کرد.
ج) به رخ‌داد change این کنترل، متد fileChange متصل شده‌است که رخ‌داد جاری را نیز دریافت می‌کند.
د) ذکر ویژگی استاندارد multiple را نیز در اینجا مشاهده می‌کنید. وجود آن سبب خواهد شد تا کاربر بتواند چندین فایل را با هم انتخاب کند. اگر نیازی به ارسال چندین فایل نیست، این ویژگی را حذف کنید.


دسترسی به المان ارسال فایل در کامپوننت متناظر

تا اینجا یک المان ارسال فایل را به فرم، اضافه کرده‌ایم. اما چگونه باید به فایل‌های آن برای ارسال به سرور دسترسی پیدا کنیم؟
برای این منظور در ادامه دو روش را بررسی خواهیم کرد:

1) دسترسی به المان ارسال فایل از طریق رخ‌داد change
در تعریف فیلد ارسال فایل، اتصال به رخ‌داد change تعریف شده‌است:
 (change)="fileChange($event)"
معادل آن در سمت کامپوننت متناظر، به صورت ذیل است:
fileChange(event) {
    const filesList: FileList = event.target.files;
    console.log("fileChange() -> filesList", filesList);
}
همانطور که مشاهده می‌کنید، event.target، امکان دسترسی مستقیم به المان متناظری را در قالب کامپوننت میسر می‌کند. سپس می‌توان به خاصیت files آن دسترسی یافت.


در اینجا ساختار شیء استاندارد FileList و اجزای آن‌را مشاهده می‌کنید. برای مثال چون دو فایل انتخاب شده‌است، این لیست به همراه یک خاصیت طول و دو شیء File است.

تعاریف این اشیاء استاندارد، در فایل ذیل قرار دارند و به همین جهت است که VSCode، بدون نیاز به تنظیمات دیگری، آن‌ها را شناسایی و intellisense متناظری را مهیا می‌کند:
 C:\Program Files (x86)\Microsoft VS Code\resources\app\extensions\node_modules\typescript\lib\lib.dom.d.ts
همچنین اگر به فایل tsconfig.json پروژه نیز مراجعه کنید، یک چنین تعاریفی در آن قرار دارند:
{
    "lib": [
      "es2016",
      "dom"
    ]
  }
}
وجود و تعریف کتابخانه‌ی dom است که سبب کامپایل شدن کدهای فوق، بدون بروز هیچگونه خطایی می‌شود.


2) دسترسی به المان آپلود فایل از طریق یک template reference variable
در حین تعریف المان فایل در فرم برنامه، متغیر screenshotInput# نیز ذکر شده‌است. می‌توان به یک چنین متغیرهایی در کامپوننت متناظر به روش ذیل دسترسی یافت:
import { Component, OnInit, ViewChild, ElementRef } from "@angular/core";

export class UploadFileSimpleComponent implements OnInit {
  @ViewChild("screenshotInput") screenshotInput: ElementRef;

  submitForm(form: NgForm) {
    const fileInput: HTMLInputElement = this.screenshotInput.nativeElement;
    console.log("fileInput.files", fileInput.files);
  }
ابتدا یک خاصیت جدید را به نام screenshotInput از نوع ElementRef که در angular/core@ تعریف شده‌است، اضافه می‌کنیم. سپس برای اتصال آن به template reference variable ایی به نام screenshotInput، از ویژگی به نام ViewChild، با پارامتری مساوی نام همین متغیر، استفاده خواهیم کرد.
اکنون خاصیت screenshotInput کامپوننت، به متغیری به همین نام در قالب متناظر با آن متصل شده‌است. بنابراین با استفاده از خاصیت nativeElement آن همانند کدهایی که در متد submitForm فوق ملاحظه می‌کنید، می‌توان به خاصیت files این کنترل ارسال فایل‌ها دسترسی یافت.
نوع جدید و استاندارد HTMLInputElement نیز در فایل lib.dom.d.ts که پیشتر معرفی شد، ثبت شده‌است.


ارسال فرم درخواست پشتیبانی به سرور

تا اینجا فرمی را تشکیل داده و همچنین به فیلد file آن دسترسی پیدا کردیم. اکنون می‌خواهیم این اطلاعات را به سمت سرور ارسال کنیم. برای این منظور، سرویس جدیدی را ایجاد خواهیم کرد:
 >ng g s UploadFile/UploadFileSimple -m upload-file.module
که سبب به روز رسانی خودکار قسمت providers فایل upload-file.module.ts نیز می‌شود.
در ادامه کدهای کامل این سرویس را مشاهده می‌کنید:
import { Http, RequestOptions, Response, Headers } from "@angular/http";
import { Injectable } from "@angular/core";
import { Observable } from "rxjs/Observable";
import "rxjs/add/operator/do";
import "rxjs/add/operator/catch";
import "rxjs/add/observable/throw";
import "rxjs/add/operator/map";
import "rxjs/add/observable/of";

import { Ticket } from "./ticket";

@Injectable()
export class UploadFileSimpleService {
  private baseUrl = "api/SimpleUpload";

  constructor(private http: Http) {}

  private extractData(res: Response) {
    const body = res.json();
    return body || {};
  }

  private handleError(error: Response): Observable<any> {
    console.error("observable error: ", error);
    return Observable.throw(error.statusText);
  }

  postTicket(ticket: Ticket, filesList: FileList): Observable<any> {
    if (!filesList || filesList.length === 0) {
      return Observable.throw("Please select a file.");
    }

    const formData: FormData = new FormData();

    for (const key in ticket) {
      if (ticket.hasOwnProperty(key)) {
        formData.append(key, ticket[key]);
      }
    }

    for (let i = 0; i < filesList.length; i++) {
      formData.append(filesList[i].name, filesList[i]);
    }

    const headers = new Headers();
    headers.append("Accept", "application/json");
    const options = new RequestOptions({ headers: headers });

    return this.http
      .post(`${this.baseUrl}/SaveTicket`, formData, options)
      .map(this.extractData)
      .catch(this.handleError);
  }
}
توضیحات تکمیلی:
روش کار با فرم‌هایی که فیلدهای ارسال فایل را به همراه دارند، متفاوت است با روش کار با فرم‌های معمولی. در فرم‌های معمولی، اصل شیء Ticket را به متد this.http.post واگذار می‌کنیم. مابقی آن خودکار است. در اینجا باید شیء استاندارد FormData را تشکیل داده و سپس اطلاعات را از طریق آن ارسال کنیم:
الف) افزودن مقادیر خواص شیء Ticket به FormData
  postTicket(ticket: Ticket, filesList: FileList): Observable<any> {
    const formData: FormData = new FormData();

    for (const key in ticket) {
      if (ticket.hasOwnProperty(key)) {
        formData.append(key, ticket[key]);
      }
    }
با استفاده از حلقه‌ی for می‌توان بر روی خواص یک شیء جاوا اسکریپتی حرکت کرد. به این ترتیب می‌توان نام و مقدار آن‌ها را یافت و سپس به formData به صورت key/value افزود.

ب) افزودن فایل‌ها به شیء FormData
پس از افزودن اطلاعات ticket به FormData، اکنون نوبت به افزودن فایل‌های فرم است:
    for (let i = 0; i < filesList.length; i++) {
      formData.append(filesList[i].name, filesList[i]);
    }
این مورد نیز به سادگی تشکیل یک حلقه، بر روی خاصیت files المان آپلود فایل است. به همین جهت بود که به دو روش سعی کردیم، به این خاصیت دسترسی پیدا کنیم.

یک نکته: چون در اینجا کلید اضافه شده، نام فایل است، دیگر نمی‌توان در سمت سرور از روش model binding استفاده کرد. چون این نام دیگر ثابت نیست و هربار می‌تواند متغیر باشد (در حالت model binding دقیقا مشخص است که کلید مشخصی قرار است به سرور ارسال شود و بر همین اساس، نام خاصیت یا پارامتر سمت سرور تعیین می‌گردد). به همین جهت در سمت سرور برای دسترسی به این مجموعه، از روش Request.Form.Files استفاده می‌کنیم.

ج) ارسال اطلاعات نهایی به سرور
اکنون که formData را بر اساس اطلاعات اضافی ticket و فایل‌های متصل به آن تشکیل دادیم، روش ارسال آن به سرور همانند قبل است:
    const headers = new Headers();
    headers.append("Accept", "application/json");
    const options = new RequestOptions({ headers: headers });

    return this.http
      .post(`${this.baseUrl}/SaveTicket`, formData, options)
      .map(this.extractData)
      .catch(this.handleError);

یک نکته: در اینجا در روش استفاده از formData نباید Content-Type را به multipart/form-data  تنظیم کرد. در غیراینصورت خطای Missing content-type boundary error را دریافت می‌کنید.


تکمیل کامپوننت ارسال درخواست پشتیبانی

پس از تکمیل سرویس ارسال اطلاعات به سمت سرور، اکنون نوبت به استفاده‌ی از آن در کامپوننت ارسال فرم درخواست پشتیبانی است. بنابراین ابتدا این سرویس جدید را به سازنده‌ی UploadFileSimpleComponent تزریق می‌کنیم:
import { UploadFileSimpleService } from "./../upload-file-simple.service";

export class UploadFileSimpleComponent implements OnInit {
  constructor(private uploadService: UploadFileSimpleService  ) {}
و سپس متد submitForm چنین شکلی را پیدا می‌کند:
  submitForm(form: NgForm) {
    const fileInput: HTMLInputElement = this.screenshotInput.nativeElement;
    console.log("fileInput.files", fileInput.files);

    this.uploadService
      .postTicket(this.model, fileInput.files)
      .subscribe(data => {
        console.log("success: ", data);
      });
  }
در اینجا this.model حاوی اطلاعات شیء ticket است (برای مثال اطلاعات توضیحات آن) و fileInput.files امکان دسترسی به اطلاعات فایل‌های انتخابی توسط کاربر را می‌دهد. پس از آن فراخوانی متدهای this.uploadService.postTicket و subscribe، سبب ارسال این اطلاعات به سمت سرور می‌شوند.


دریافت فرم درخواست پشتیبانی در سمت سرور و ذخیره‌ی فایل‌های آن‌

کدهای کامل SimpleUpload که در سرویس فوق مشخص شده‌است، به صورت ذیل هستند. ابتدا مدل Ticket مشخص شده‌است:
namespace AngularTemplateDrivenFormsLab.Models
{
    public class Ticket
    {
        public int Id { set; get; }
        public string Description { set; get; }
    }
}
و سپس کنترلر ذخیره سازی اطلاعات Ticket را مشاهده می‌کنید:
using System.IO;
using System.Threading.Tasks;
using AngularTemplateDrivenFormsLab.Models;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;

namespace AngularTemplateDrivenFormsLab.Controllers
{
    [Route("api/[controller]")]
    public class SimpleUploadController : Controller
    {
        private readonly IHostingEnvironment _environment;
        public SimpleUploadController(IHostingEnvironment environment)
        {
            _environment = environment;
        }

        [HttpPost("[action]")]
        public async Task<IActionResult> SaveTicket(Ticket ticket)
        {
            //TODO: save the ticket ... get id
            ticket.Id = 1001;

            var uploadsRootFolder = Path.Combine(_environment.WebRootPath, "uploads");
            if (!Directory.Exists(uploadsRootFolder))
            {
                Directory.CreateDirectory(uploadsRootFolder);
            }

            var files = Request.Form.Files;
            foreach (var file in files)
            {
                //TODO: do security checks ...!

                if (file == null || file.Length == 0)
                {
                    continue;
                }

                var filePath = Path.Combine(uploadsRootFolder, file.FileName);
                using (var fileStream = new FileStream(filePath, FileMode.Create))
                {
                    await file.CopyToAsync(fileStream).ConfigureAwait(false);
                }
            }

            return Created("", ticket);
        }
    }
}
توضیحات تکمیلی
- تزریق IHostingEnvironment در سازنده‌ی کلاس کنترلر، سبب می‌شود تا از طریق خاصیت WebRootPath آن، به مسیر wwwroot سایت دسترسی پیدا کنیم و فایل‌های نهایی را در آنجا ذخیره سازی کنیم.
- همانطور که ملاحظه می‌کنید، هنوز هم model binding کار کرده و می‌توان شیء Ticket را به نحو متداولی دریافت کرد:
 SaveTicket(Ticket ticket)
اما همانطور که عنوان شد، چون در حلقه‌ی افزودن فایل‌ها در سمت کلاینت، کلید نام این فایل‌ها هربار متفاوت است:
 formData.append(filesList[i].name, filesList[i]);
مجبور هستیم در سمت سرور بر روی Request.Form.Files یک حلقه را تشکیل داده و تمام فایل‌های رسیده را پردازش کنیم:
var files = Request.Form.Files;
foreach (var file in files)



کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید.
  • #
    ‫۷ سال و ۲ ماه قبل، جمعه ۲۳ تیر ۱۳۹۶، ساعت ۱۹:۳۷
    یک نکته‌ی تکمیلی
    به همراه نگارش Angular 4.3، روش جدیدی برای کار با HTTP، توسط ماژول جدید HTTP Client آن ارائه شده‌است که ساختار آن بسیار شبیه به ماژول فعلی HTTP آن است و کدهای فعلی را به سادگی می‌توان به آن انتقال داد. یکی از تغییرات آن داشتن رخ‌دادهای درصد آپلود و دانلود یک درخواست است:
    import {  HttpEventType,  HttpClient,  HttpRequest } from '@angular/common/http';
    
    http.request(new HttpRequest(
      'POST',
      URL,
      body, 
      {
        reportProgress: true
      })).subscribe(event => {
      if (event.type === HttpEventType.DownloadProgress) {
      }
    
      if (event.type === HttpEventType.UploadProgress) {
      }
    
      if (event.type === HttpEventType.Response) {
        console.log(event.body);
      }
    })
    به این ترتیب بدون نیاز به کامپوننت اضافه‌تری می‌توان درصد پیشرفت آپلود را نمایش داد.

    برای مطالعه‌ی بیشتر:
    The Angular HTTP Client - Quickstart Guide 
    A Taste From The New Angular HTTP Client  
  • #
    ‫۷ سال و ۱ ماه قبل، سه‌شنبه ۳۱ مرداد ۱۳۹۶، ساعت ۱۸:۳۴
    یک نکته‌ی تکمیلی: به روز رسانی مثال مطلب جاری جهت گزارش درصد پیشرفت آپلود فایل‌ها توسط HTTP Client جدید Angular

    در اینجا می‌خواهیم بدون استفاده از هیچگونه کامپوننت ثالثی، صرفا بر اساس قابلیت‌های جدید ماژول HttpClient ارائه شده‌ی در Angular 4.3، درصد پیشرفت آپلود را نیز نمایش دهیم. تغییرات مورد نیاز برای این منظور به شرح زیر هستند:

    1) تغییر سرویس برنامه جهت استفاده از HTTP Client و گزارش درصد پیشرفت

      postTicket(ticket: Ticket, filesList: FileList): Observable<HttpEvent<any>> {
    
        //… the same as before
    
        const headers = new HttpHeaders().set("Accept", "application/json");
        return this.http
          .post(`${this.baseUrl}/SaveTicket`, formData, {
            headers: headers,
            reportProgress: true,
            observe: "events"
          })
          .map(response => response || {})
          .catch((error: HttpErrorResponse) => {
            console.error("observable error: ", error);
            return Observable.throw(error.statusText);
          });
      }
    در اینجا متد postTicket، به این صورت تغییر کرده‌است:
    الف) خروجی متد آن یک Observable از نوع HttpEvent تعیین شده‌است. به این ترتیب مشترکین به آن قادر خواهند شد به رخ‌دادهای HTTP Client گوش فرا دهند:
     postTicket(ticket: Ticket, filesList: FileList): Observable<HttpEvent<any>> {

    ب) به قسمت options متد post، گزینه‌ها‌ی "observe: "events و reportProgress: true اضافه شده‌اند:
    {
      headers: headers,
      reportProgress: true,
      observe: "events"
    }
    به این ترتیب، رخ‌دادها به همراه گزارش درصد پیشرفت آپلود و دانلود، به مشترکین این متد ارسال خواهند شد.

    2) تغییر متد submitForm کامپوننت جهت گزارش و اعمال تغییرات به قالب برنامه

      queueProgress: number;
      isUploading: boolean;
      uploadTimeRemaining: number;
      uploadTimeElapsed: number;
      uploadSpeed: number;
    
      submitForm(form: NgForm) {
        const fileInput: HTMLInputElement = this.screenshotInput.nativeElement;
        this.queueProgress = 0;
        this.isUploading = true;
        let startTime = Date.now();
    
        this.uploadService.postTicket(this.model, fileInput.files).subscribe(
          (event: HttpEvent<any>) => {
            switch (event.type) {
              case HttpEventType.Sent:
                startTime = Date.now();
                console.log("Request sent!");
                break;
              case HttpEventType.DownloadProgress:
              case HttpEventType.UploadProgress:
                if (event.total) {
                  this.queueProgress = Math.round(event.loaded / event.total * 100);
    
                  const timeElapsed = Date.now() - startTime;
                  const uploadSpeed = event.loaded / (timeElapsed / 1000);
                  this.uploadTimeRemaining = Math.ceil(
                    (event.total - event.loaded) / uploadSpeed
                  );
                  this.uploadTimeElapsed = Math.ceil(timeElapsed / 1000);
                  this.uploadSpeed = uploadSpeed / 1024 / 1024;
                }
                break;
              case HttpEventType.Response:
                this.queueProgress = 100;
                this.isUploading = false;
                console.log("Done! ResponseBody:", event.body);
                break;
            }
          },
          (error: HttpErrorResponse) => {
            this.isUploading = false;
            console.log(error);
          }
        );
      }
    پس از اینکه خروجی متد سرویس ارسال فایل‌ها را از نوع <<Observable<HttpEvent<any تعیین کردیم، اکنون پس از subscribe به آن، اینبار event: HttpEvent را بجای data متداول دریافت می‌کنیم. سپس بر اساس event.type می‌توان در مورد رخ‌داد گزارش داده شده، تصمیم‌گیری کرد:
    - HttpEventType.Sent شروع عملیات است. برای مثال از آن می‌توان جهت تعیین زمان شروع به آپلود استفاده کرد. سپس این زمان را با زمان جاری، در رخ‌داد آپلود به سرور مقایسه و گزارش سرعت آپلود، زمان‌های صرف شده و باقیمانده را محاسبه کرد.
    - HttpEventType.DownloadProgress و HttpEventType.UploadProgress گزارش درصد آپلود را مشخص می‌کنند. در اینجا event.total حجم کلی است و event.loaded حجم ارسالی کلی به سمت سرور می‌باشد. به همین جهت از این اطلاعات می‌توان برای نمایش درصد کل آپلود استفاده کرد.
    - HttpEventType.Response در پایان عملیات رخ‌خواهد داد. در اینجا event.body همان بدنه‌ی response دریافتی از سمت سرور است.

    3) تغییر رابط کاربری برنامه (قالب کامپوننت) جهت گزارش درصد پیشرفت کلی آپلود
        <div *ngIf="queueProgress > 0">
          <table class="table">
            <thead>
              <tr>
                <th width="15%">Event</th>
                <th>Status</th>
              </tr>
            </thead>
            <tbody>
              <tr>
                <td><strong>Elapsed time</strong></td>
                <td nowrap>{{uploadTimeElapsed | number:'.1'}} second(s)</td>
              </tr>
              <tr>
                <td><strong>Remaining time</strong></td>
                <td nowrap>{{uploadTimeRemaining | number:'.1'}} second(s)</td>
              </tr>
              <tr>
                <td><strong>Upload speed</strong></td>
                <td nowrap>{{uploadSpeed | number:'.3'}} MB/s</td>
              </tr>
              <tr>
                <td><strong>Queue progress</strong></td>
                <td>
                  <div class="progress-bar progress-bar-info progress-bar-striped" role="progressbar"
                    aria-valuemin="0" aria-valuemax="100" [attr.aria-valuenow]="queueProgress"
                    [ngStyle]="{ 'width': queueProgress + '%' }">
                    {{queueProgress}}%
                  </div>
                </td>
              </tr>
            </tbody>
          </table>
        </div>
    
        <button class="btn btn-primary" [disabled]="form.invalid || isUploading" type="submit">Submit</button>
    در اینجا کار نمایش زمان صرف شده، زمان باقیمانده، سرعت آپلود و همچنین درصد میزان پیشرفت آپلود انجام شده‌اند. درصد محاسبه شده به ngStyle متصل شده‌است تا سبب حرکت progressbar بوت استرپ شود.
    همچنین در طی مدت آپلود، خاصیت isUploading به true تنظیم می‌شود تا دکمه‌ی ارسال را غیرفعال کند. این خاصیت در صورت بروز خطایی و یا تکمیل شدن عملیات، false می‌شود.



    یک نکته: اگر می‌خواهید درصد پیشرفت آپلود را در حالت آزمایش local بهتر مشاهده کنید، دربرگه‌ی network، سرعت را بر روی 3G تنظیم کنید:



    کدهای کامل این تغییرات را از اینجا می‌توانید دریافت کنید.
    • #
      ‫۴ سال و ۱ ماه قبل، چهارشنبه ۱۵ مرداد ۱۳۹۹، ساعت ۲۲:۵۸
      با سلام
      وقتی درخواست را به سرور ارسال میکنم این خطا را دریافت میکنم راه حل چیست ؟

      با تشکر از شما

      • #
        ‫۴ سال و ۱ ماه قبل، پنجشنبه ۱۶ مرداد ۱۳۹۹، ساعت ۰۰:۳۸
        مشخص نکردید امضای اکشن متد مرتبط به چه صورتی هست. خطای 415 unsupported media type یعنی امضای اکشن متد شما با اطلاعاتی که به سمت سرور ارسال می‌کنید تطابقی ندارد. برای مثال اگر از IFormFile استفاده می‌کنید باید از [FromForm] هم استفاده کنید.
        public async Task<IActionResult> UploadFile(string id, [FromForm] IFormFile file)
  • #
    ‫۶ سال و ۳ ماه قبل، دوشنبه ۱۴ خرداد ۱۳۹۷، ساعت ۲۱:۳۵
    معادل همین مطلب در Vue.js (+)
  • #
    ‫۵ سال و ۵ ماه قبل، دوشنبه ۱۹ فروردین ۱۳۹۸، ساعت ۱۷:۴۹
    چطور میشود در بخش بک اند متد saveTicket، بجای ذخیره فایل روی فایل سیستم این فایل را روی دیتابیس ذخیره کرد؟ منظورم این است که آیا میشود این فایل را بدون اینکه روی سیستم ذخیره کنیم به آرایه ای از بایت‌ها تبدیلش کرد؟
    • #
      ‫۵ سال و ۵ ماه قبل، دوشنبه ۱۹ فروردین ۱۳۹۸، ساعت ۲۲:۳۷
              public async Task<IActionResult> FileUpload(IFormFile file)
              {
                  if (file == null || file.Length == 0)
                  {
                      return BadRequest();
                  }
      
                  using (var memoryStream = new MemoryStream())
                  {
                      await file.CopyToAsync(memoryStream);
                      var fileBytes = memoryStream.ToArray();
                      // ... save it
                  }
              }
  • #
    ‫۵ سال و ۴ ماه قبل، شنبه ۷ اردیبهشت ۱۳۹۸، ساعت ۱۲:۴۵
    سلام.
    من برای دانلود فایل در بخش api ام این کد رو نوشتم:
    public async Task<ServiceResult<FileContentResult>> GetByFileFolderId(int id)
            {
                var file = await FileRepository.GetByFileFolderId( id);
                var locatedFile = (byte[])Convert.ChangeType(file.FileData64, typeof(byte[]));
                var result= new FileContentResult(locatedFile, new
                MediaTypeHeaderValue("application/pdf"))
                {
                    FileDownloadName = "SomeFileDownloadName.pdf"
                };
                return Ok(result);
            }
    و در بخش کلاینت از این کد استفاده کردم:
    getPDF(fileId): Observable<Blob> {
        const uri = 'MyApiUri/GetByFileFolderId?id=' + fileId;
        return this.http.get(uri, { responseType: 'blob' });
      }
    
    downloadFile(){
    this.getPDF()
    .subscribe(x => {
      var newBlob = new Blob([x], { type: "application/pdf" });
    
      const data = window.URL.createObjectURL(newBlob);
      var link = document.createElement('a');
      link.href = data;
      link.download = "SomeFileDownloadName.pdf";
      link.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true, view: window }));
      setTimeout(function () {
          window.URL.revokeObjectURL(data);
          link.remove();
      }, 100);
    });
    }
    ولی زمانی که فایل رو دانلود میکنه و فایل رو میخوام باز کنم ارور This PDF is corrupted میده. و برای انواع دیگر فایل هم همین مشکل رو داره و نمیتونه محتوای فایل رو تشخیص بده.