مطالب
بررسی روش آپلود فایل‌ها از طریق یک برنامه‌ی 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)



کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید.
نظرات مطالب
بررسی روش آپلود فایل‌ها در ASP.NET Core
یک نکته‌ی تکمیلی: روش تعیین حداکثر اندازه‌ی فایل قابل آپلود در برنامه‌های ASP.NET Core
الف) اگر برنامه توسط IIS هاست شده‌است
<?xml version="1.0" encoding="utf-8"?>
<configuration>
 <!-- To customize the asp.net core module uncomment and edit the following section. 
 For more info see https://go.microsoft.com/fwlink/?linkid=838655 -->
 <system.webServer>
  <handlers>
  <remove name="aspNetCore"/>
  <add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModule" resourceType="Unspecified"/>
  </handlers>
  <aspNetCore processPath="%LAUNCHER_PATH%" arguments="%LAUNCHER_ARGS%" stdoutLogEnabled="false" stdoutLogFile=".\logs\stdout" />
  <security>
   <requestFiltering>
    <!-- This will handle requests up to 50MB -->
    <requestLimits maxAllowedContentLength="52428800" />
   </requestFiltering>
  </security>
  </system.webServer>
</configuration>
در اینجا (در فایل web.config برنامه) مقدار خاصیت maxAllowedContentLength است که تعیین کننده‌ی حداکثر اندازه‌ی فایل قابل آپلود است. به علاوه مطمئن شوید که فایل web.config شما حتما publish شده.
همچنین requestTimeout را نیز به صورت زیر می‌توان مقدار دهی کرد:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <system.webServer>
    <aspNetCore requestTimeout="00:20:00"  .... />
  </system.webServer>
</configuration>
به همراه این تنظیمات:
        public void ConfigureServices(IServiceCollection services)
        {
            services.Configure<IISServerOptions>(options =>
            {
                options.MaxRequestBodySize = int.MaxValue;
            });
            services.Configure<FormOptions>(options =>
            {
                options.ValueLengthLimit = int.MaxValue;
                options.MultipartBodyLengthLimit = long.MaxValue; // <-- ! long.MaxValue
                options.MultipartBoundaryLengthLimit = int.MaxValue;
                options.MultipartHeadersCountLimit = int.MaxValue;
                options.MultipartHeadersLengthLimit = int.MaxValue;
            });
 
ب) اگر برنامه در لینوکس و بر اساس وب سرور Kestrel هاست شده‌است
روش اول: استفاده از ویژگی RequestSizeLimit که از نگارش ASP.NET Core 2.0 به بعد قابل استفاده‌است و صرفا به قسمتی از برنامه اعمال می‌شود:
[HttpPost]
[RequestSizeLimit(40000000)]
public async Task<IActionResult> UploadFiles(IFormFile file)
روش دوم: تنظیم سراسری آن برای کل برنامه:
        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder
                        .UseStartup<Startup>()
                        .ConfigureKestrel(kestrelServerOptions =>
                        {
                            kestrelServerOptions.Limits.KeepAliveTimeout = TimeSpan.FromMinutes(10);
                            kestrelServerOptions.Limits.MaxRequestBodySize = 52428800; //50MB
                        });
                });
بدیهی است تنظیم این دو روش در حالت استفاده‌ی از IIS تاثیری نخواهند داشت.
مطالب
شروع کار با Apache Cordova در ویژوال استودیو #2
در قسمت اول، با Apache Cordova  آشنا شدیم. در این قست قصد دارم در مورد Phonegap, معایب و مزایای Cordova و روش نصب و راه اندازی آن را بر روی Visual Studio، خدمت شما ارائه دهم. 

توضیح مختصری در مورد  Adobe Phonegap
در حوالی سال 2009 ، phonegap به‌واسطه‌ی استارت آپی بنام Nitobi با هدف ایجاد یک راه حل سورس باز  برای  ساخت اپلیکیشن‌های بومی موبایل با تکنولوژی‌های تحت وب، تولید شد. شرکت Adobe در حوالی سال 2011 ، Notobi را به همرا حق مالکیت phonegap خریداری کرد و هسته‌ی سورس باز آن را با نام Cordova به شرکت Apache اهدا کرد. نسبت بین Cordova و phonegap مانند نسبت بین مرورگر Blink و کروم است. در واقع phonegap ترکیبی‌است از Cordova و یک سری امکانات اضافه‌ی شرکت Adobe. تفاوت اصلی بین Cordova و Phonegap مربوط است به ابزارهای Command-Line و سرویس Build فون گپ است که در مقالات بعدی به آنها خواهیم پرداخت.
بیشتر : اینجا و اینجا

 مزایای استفاده از Cordova:
  • محیط برنامه نویسی قدرتمند
  • هسته اصلی  کد‌های همه اپلیکیشن‌ها  تولید شده شبیه به هم است
  • نیازی به یادگیری زبان‌های مربوط به هر پلتفرم را ندارید
  • کم هزینه و زمان کمتر
  • طراحی رابط گرافیکی سریع و منعطف به کمک HTML5 , CSS3 
  • برنامه نویسی آسان و سریع با javascript , Typescript
  • قابلیت اجرا بر روی چندین پلتفرم مختلف(Android,iOS,Widnows Phone )
  • قابلیت استفاده از فریم‌ورکهای تحت وب مانند Bootstrap , Angular JS, ...
  • قابلیت طراحی پلاگین برای ارتباط با سیستم عامل
  • مناسب برای برای برنامه‌های چت و استفاد از وب سرویس‌ها
  • مناسب برای ساخت بازیهای آنلاین و آفلاین با تکنولوژی‌های تحت وب
  • راحتی کار با آن برای برنامه نویسان تحت وب 

معایب  استفاده از Cordova  :
  • نداشتن ابزار گزارش خطاهای مناسب؛ درنتیجه برطرف کردن خطاها خسته کننده خواهد بود .
  • UI, UX اپلیکیشن‌ها باید به نحوی باشد که کاربر حس کند با نرم‌افزارهای بومی گوشی کار می‌کند.
  • کاهش سرعت اجرایی جزئی نسبت به سایر برنامه‌ها (به دلیل استفاده از WebView)
  • عدم دسترسی مستقیم به سیستم عامل و امکانات آن 

نصب اتوماتیک وابستگی ها
ابزارهایی که ما نیاز داریم:

لازم است تا Visual Studio 2013، با حداقل آپدیت 2 بر روی سیستم شما نصب باشد.

بعد از اتمام دانلود فایل، اقدام به نصب آن نمایید. در این حین، یک سری وابستگی‌های مربوط به خود را دانلود و نصب خواهد کرد. لیست وابستگی ها:
  • Node.js
  • Git CLI
  • Google Chrome
  • Apache Ant
  • Oracle Java JDK 7 (حتما نسخه x86 نصب شود)
  • Android SDK 
  • SQLLite For Windows Runtime
  • Apple iTunes
فایل نصاب، همه‌ی این وابستگی‌ها را به‌غیر از Android SDK، نصب می‌کند. 
در آخر هم سیستم خود را راستارت کنید.

نصب دستی وابستگی‌ها:
اگر به هر دلیلی در نصب خودکار این وابستگی‌ها  توسط نصاب با مشکل بر خورد کردید، می‌توانید تک تک آنها را دانلود کرده و نصب کنید. لینک‌های مورد نظر را هم به همین دلیل قرار دادم. 
  1. node.js را از لینک مقابل دانلود کنید:  اینجا  (پیشنهاد می‌کنیم نسخه‌ی x86 آن را نصب کنید)
  2. Google Chrome را نصب کنید
  3. Git Command Line Tools را نصب کنید و توجه کنید که در هنگام نصب، گزینه مربوط به افزودن Git را به مسیر  Command Prompt  شما، انتخاب کرده باشید.
  4. Apchage Ant را  دانلود  و در مسیری از سیستم خودتان قرار دهید. 
  5. Java JDK 7 x86 را از لینک مشخص شده دانلود کنید و سپس عملیات نصب را انجام دهید.
  6. Android SDK را از آدرس مشحص شده دانلود کنید. پکیچ‌های مورد نیاز، به این SDK افزوده شده است. بعد از دانلود آن را در مسیری از سیستم خود قرار دهید.
  7. Apple iTunes و  SQLite را دانلود و نصب کنید.
  8. اگر از ویندوز 7 استفاده می‌کنید ، WebSocket4Net را از لینک مقابل دانلود کنید ( اینجا ) و سپس  فایل net45\Release\WebSocket4Net.dll  در مسیر زیر کپی کنید:
%ProgramFiles(x86)%\Microsoft Visual Studio 12.0\Common7\IDE\CommonExtensions\Microsoft\WebClient\Diagnostics\ToolWindows 


ویژوال استودیو  پیکربندی‌های مربوط به نرم افزار‌های  thrid-party (سوم شخص/ثالث: نرم افزارهایی که برای دستکاری بر روی سیستم عامل، توسط شرکت‌هایی غیر از شرکت‌های تولید کننده سیستم عامل تولید می‌شوند) را که شما نصب کرده‌اید، تشخیص می‌دهد و مسیر‌های نصب آنها را درون متغیر‌های محیطی (environment variables)  به شکل زیر نگه می‌دارد:
ADT_HOME :به مسیر نصب اندروید اشاره می‌کند
ANT_HOME: به فولدری که Apache Ant در آن قرار دارد اشاره می‌کند
JAVA_HOME: به مسیر نصب جاوا اشاره می‌کند
GIT_HOME: به مسیر نصب GIT اشاره می‌کند.
دقت کنید باید نام‌های متغیر‌ها، دقیقا به همین نام‌ها باشند.
برای تنظیم این متغیر‌ها، به مسیر Control Panel\System and Security\System وارد شده و گزینه‌ی Advanced System Setting را انتخاب کنید. سپس در پنجره‌ی باز شده گزینه‌ی Environment Variables را انتخاب کنید و در قست system variables، این 4 متغیری که ذکر شد را ایجاد کنید. سپس نیاز است این مسیر‌ها را به system path اضافه کنید. برای این کار از همان قسمت system variables متغییر path را انتخاب کرده و گزینه‌ی ویرایش را بزنید و ابتدا محتویات آن را در یک فایل notepad کپی کنید و مسیر‌های زیر را به اول آن اضافه کنید :
%GIT_HOME%\cmd;C:\Program Files (x86)\nodejs\;%JAVA_HOME%\bin;%ANT_HOME%\bin;
%ANDROID_HOME%\tools;%ANDROID_HOME%\platform-tools; C:\ProgramData\Oracle\Java\javapath;
 دقت کنید که مسیر‌های ذکر شده فقط یکبار در کل فایل وجود داشته باشند و سپس کل محتوای فایل را کپی کرده و در قسمت مربوط به path پیست کنید و با کلیک بر روی دکمه‌های OK کار را به اتمام رسانید.












نکته تکمیلی
نیازمندی Apache Cordova CTP3.1   :
یکی از سیستم عامل‌های مقابل: Windows 7, Windows 8, Windows 8.1, or Windows Server 2012 R2. 
آپدیت 4 مربوط به ویژوال استدیو (دقت کنید قبل از نصب آپدیت 4 ویژوال استدیو باید نسخه قبلی  Cordova CTP  را حذف کنید(uninstall) )
امکان توسعه اپلیکیشن‌های windows phone , windows برای کاربران ویندوز 7 وجود ندارد .


در مقاله‌ی بعدی یک پروژه جدید خواهیم ساخت .
منبع مفید برای نصب و راه اندازی :اینجا
ادامه دارد...
مطالب
امن سازی برنامه‌های ASP.NET Core توسط IdentityServer 4x - قسمت چهارم - نصب و راه اندازی IdentityServer
معرفی IdentityServer 4


اگر استاندارد OpenID Connect را بررسی کنیم، از مجموعه‌ای از دستورات و رهنمودها تشکیل شده‌است. بنابراین نیاز به کامپوننتی داریم که این استاندارد را پیاده سازی کرده باشد تا بتوان بر اساس آن یک Identity Provider را تشکیل داد و پیاده سازی مباحثی که در قسمت قبل بررسی شدند مانند توکن‌ها، Flow، انواع کلاینت‌ها، انواع Endpoints و غیره چیزی نیستند که به سادگی قابل انجام باشند. اینجا است که IdentityServer 4، به عنوان یک فریم ورک پیاده سازی کننده‌ی استانداردهای OAuth 2 و OpenID Connect مخصوص ASP.NET Core ارائه شده‌است. این فریم ورک توسط OpenID Foundation تائید شده و داری مجوز رسمی از آن است. همچنین جزئی از NET Foundation. نیز می‌باشد. به علاوه باید دقت داشت که این فریم ورک کاملا سورس باز است.


نصب و راه اندازی IdentityServer 4

همان مثال «قسمت دوم - ایجاد ساختار اولیه‌ی مثال این سری» را در نظر بگیرید. داخل آن پوشه‌های جدید src\IDP\DNT.IDP را ایجاد می‌کنیم.


نام دلخواه DNT.IDP، به پوشه‌ی جدیدی اشاره می‌کند که قصد داریم IDP خود را در آن برپا کنیم. نام آن را نیز در ادامه‌ی نام‌های پروژه‌های قبلی که با ImageGallery شروع شده‌اند نیز انتخاب نکرده‌ایم؛ از این جهت که یک IDP را قرار است برای بیش از یک برنامه‌ی کلاینت مورد استفاده قرار دهیم. برای مثال می‌توانید از نام شرکت خود برای نامگذاری این IDP استفاده کنید.

اکنون از طریق خط فرمان به پوشه‌ی src\IDP\DNT.IDP وارد شده و دستور زیر را صادر کنید:
dotnet new web
این دستور، یک پروژه‌ی جدیدی را از نوع «ASP.NET Core Empty»، در این پوشه، بر اساس آخرین نگارش SDK نصب شده‌ی بر روی سیستم شما، ایجاد می‌کند. از این جهت نوع پروژه خالی درنظر گرفته شده‌است که قرار است توسط اجزای IdentityServer 4 به مرور تکمیل شود.

اولین کاری را که در اینجا انجام خواهیم داد، مراجعه به فایل Properties\launchSettings.json آن و تغییر شماره پورت‌های پیش‌فرض آن است تا با سایر پروژه‌های وبی که تاکنون ایجاد کرده‌ایم، تداخل نکند. برای مثال در اینجا شماره پورت SSL آن‌را به 6001 تغییر داده‌ایم.

اکنون نوبت به افزودن میان‌افزار IdentityServer 4 به پروژه‌ی خالی وب است. اینکار را نیز توسط اجرای دستور زیر در پوشه‌ی src\IDP\DNT.IDP انجام می‌دهیم:
 dotnet add package IdentityServer4

در ادامه نیاز است این میان‌افزار جدید را معرفی و تنظیم کرد. به همین جهت فایل Startup.cs پروژه‌ی خالی وب را گشوده و به صورت زیر تکمیل می‌کنیم:
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc();

            services.AddIdentityServer()
                .AddDeveloperSigningCredential();
        }
- متد الحاقی AddIdentityServer، کار ثبت و معرفی سرویس‌های توکار IdentityServer را به سرویس توکار تزریق وابستگی‌های ASP.NET Core انجام می‌دهد.
- متد الحاقی AddDeveloperSigningCredential کار تنظیمات کلید امضای دیجیتال توکن‌ها را انجام می‌دهد. در نگارش‌های قبلی IdentityServer، اینکار با معرفی یک مجوز امضاء کردن توکن‌ها انجام می‌شد. اما در این نگارش دیگر نیازی به آن نیست. در طول توسعه‌ی برنامه می‌توان از نگارش Developer این مجوز استفاده کرد. البته در حین توزیع برنامه به محیط ارائه‌ی نهایی، باید به یک مجوز واقعی تغییر پیدا کند.


تعریف کاربران، منابع و کلاینت‌ها

مرحله‌ی بعدی تنظیمات میان‌افزار IdentityServer4، تعریف کاربران، منابع و کلاینت‌های این IDP است. به همین جهت یک کلاس جدید را به نام Config، در ریشه‌ی پروژه ایجاد و به صورت زیر تکمیل می‌کنیم:
using System.Collections.Generic;
using System.Security.Claims;
using IdentityServer4.Models;
using IdentityServer4.Test;

namespace DNT.IDP
{
    public static class Config
    {
        // test users
        public static List<TestUser> GetUsers()
        {
            return new List<TestUser>
            {
                new TestUser
                {
                    SubjectId = "d860efca-22d9-47fd-8249-791ba61b07c7",
                    Username = "User 1",
                    Password = "password",

                    Claims = new List<Claim>
                    {
                        new Claim("given_name", "Vahid"),
                        new Claim("family_name", "N"),
                    }
                },
                new TestUser
                {
                    SubjectId = "b7539694-97e7-4dfe-84da-b4256e1ff5c7",
                    Username = "User 2",
                    Password = "password",

                    Claims = new List<Claim>
                    {
                        new Claim("given_name", "User 2"),
                        new Claim("family_name", "Test"),
                    }
                }
            };
        }

        // identity-related resources (scopes)
        public static IEnumerable<IdentityResource> GetIdentityResources()
        {
            return new List<IdentityResource>
            {
                new IdentityResources.OpenId(),
                new IdentityResources.Profile()
            };
        }

        public static IEnumerable<Client> GetClients()
        {
            return new List<Client>();
        }
    }
}
توضیحات:
- این کلاس استاتیک، اطلاعاتی درون حافظه‌ای را برای تکمیل دموی جاری ارائه می‌دهد.

- ابتدا در متد GetUsers، تعدادی کاربر آزمایشی اضافه شده‌اند. کلاس TestUser در فضای نام IdentityServer4.Test قرار دارد. در کلاس TestUser، خاصیت SubjectId، بیانگر Id منحصربفرد هر کاربر در کل این IDP است. سپس نام کاربری، کلمه‌ی عبور و تعدادی Claim برای هر کاربر تعریف شده‌اند که بیانگر اطلاعاتی اضافی در مورد هر کدام از آن‌ها هستند. برای مثال نام و نام خانوادگی جزو خواص کلاس TestUser نیستند؛ اما منعی هم برای تعریف آن‌ها وجود ندارد. اینگونه اطلاعات اضافی را می‌توان توسط Claims به سیستم اضافه کرد.

- بازگشت Claims توسط یک IDP مرتبط است به مفهوم Scopes. برای این منظور متد دیگری به نام GetIdentityResources تعریف شده‌است تا لیستی از IdentityResource‌ها را بازگشت دهد که در فضای نام IdentityServer4.Models قرار دارد. هر IdentityResource، به یک Scope که سبب دسترسی به اطلاعات Identity کاربران می‌شود، نگاشت خواهد شد. در اینجا چون از پروتکل OpenID Connect استفاده می‌کنیم، ذکر IdentityResources.OpenId اجباری است. به این ترتیب مطمئن خواهیم شد که SubjectId به سمت برنامه‌ی کلاینت بازگشت داده می‌شود. برای بازگشت Claims نیز باید IdentityResources.Profile را به عنوان یک Scope دیگری مشخص کرد که در متد GetIdentityResources مشخص شده‌است.

- در آخر نیاز است کلاینت‌های این IDP را نیز مشخص کنیم (در مورد مفهوم Clients در قسمت قبل بیشتر توضیح داده شد) که اینکار در متد GetClients انجام می‌شود. فعلا یک لیست خالی را بازگشت می‌دهیم و آن‌را در قسمت‌های بعدی تکمیل خواهیم کرد.


افزودن کاربران، منابع و کلاینت‌ها به سیستم

پس از تعریف و تکمیل کلاس Config، برای معرفی آن به IDP، به کلاس آغازین برنامه مراجعه کرده و آن‌را به صورت زیر تکمیل می‌کنیم:
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc();

            services.AddIdentityServer()
             .AddDeveloperSigningCredential()
             .AddTestUsers(Config.GetUsers())
             .AddInMemoryIdentityResources(Config.GetIdentityResources())
             .AddInMemoryClients(Config.GetClients());
        }
در اینجا لیست کاربران و اطلاعات آن‌ها توسط متد AddTestUsers، لیست منابع و Scopes توسط متد AddInMemoryIdentityResources و لیست کلاینت‌ها توسط متد AddInMemoryClients به تنظیمات IdentityServer اضافه شده‌اند.


افزودن میان افزار IdentityServer به برنامه

پس از انجام تنظیمات مقدماتی سرویس‌های برنامه، اکنون نوبت به افزودن میان‌افزار IdentityServer است که در کلاس آغازین برنامه به صورت زیر تعریف می‌شود:
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseIdentityServer();
            app.UseStaticFiles();
            app.UseMvcWithDefaultRoute();
        }

آزمایش IDP

اکنون برای آزمایش IDP، به پوشه‌ی src\IDP\DNT.IDP وارد شده و دستور dotnet run را اجرا کنید:


همانطور که ملاحظه می‌کنید، برنامه‌ی IDP بر روی پورت 6001 قابل دسترسی است. برای آزمایش Web API آن، آدرس discovery endpoint این IDP را به صورت زیر در مرورگر وارد کنید:
 https://localhost:6001/.well-known/openid-configuration


در این تصویر، مفاهیمی را که در قسمت قبل بررسی کردیم مانند authorization_endpoint ،token_endpoint و غیره، مشاهده می‌کنید.


افزودن UI به IdentityServer

تا اینجا میان‌افزار IdentityServer را نصب و راه اندازی کردیم. در نگارش‌های قبلی آن، UI به صورت پیش‌فرض جزئی از این سیستم بود. در این نگارش آن‌را می‌توان به صورت جداگانه دریافت و به برنامه اضافه کرد. برای این منظور به آدرس IdentityServer4.Quickstart.UI مراجعه کرده و همانطور که در readme آن ذکر شده‌است می‌توان از یکی از دستورات زیر برای افزودن آن به پروژه‌ی IDP استفاده کرد:
الف) در ویندوز از طریق کنسول پاورشل به پوشه‌ی src\IDP\DNT.IDP وارد شده و سپس دستور زیر را وارد کنید:
iex ((New-Object System.Net.WebClient).DownloadString('https://raw.githubusercontent.com/IdentityServer/IdentityServer4.Quickstart.UI/release/get.ps1'))
ب) و یا درmacOS و یا Linux، دستور زیر را اجرا کنید:
\curl -L https://raw.githubusercontent.com/IdentityServer/IdentityServer4.Quickstart.UI/release/get.sh | bash

یک نکته: در ویندوز اگر در نوار آدرس هر پوشه، عبارت cmd را وارد و enter کنید، کنسول خط فرمان ویندوز در همان پوشه باز خواهد شد. همچنین در اینجا از ورود عبارت powershell هم پشتیبانی می‌شود:


بنابراین در نوار آدرس پوشه‌ی src\IDP\DNT.IDP، عبارت powershell را وارد کرده و سپس enter کنید. پس از آن دستور الف را وارد (copy/paste) و اجرا کنید.


به این ترتیب فایل‌های IdentityServer4.Quickstart.UI به پروژه‌ی IDP جاری اضافه می‌شوند.
- پس از آن اگر به پوشه‌ی Views مراجعه کنید، برای نمونه ذیل پوشه‌ی Account آن، Viewهای ورود و خروج به سیستم قابل مشاهده هستند.
- در پوشه‌ی Quickstart آن، کدهای کامل کنترلرهای متناظر با این Viewها قرار دارند.
بنابراین اگر نیاز به سفارشی سازی این Viewها را داشته باشید، کدهای کامل کنترلرها و Viewهای آن هم اکنون در پروژه‌ی IDP جاری در دسترس هستند.

نکته‌ی مهم: این UI اضافه شده، یک برنامه‌ی ASP.NET Core MVC است. به همین جهت در انتهای متد Configure، ذکر میان افزارهای UseStaticFiles و همچنین UseMvcWithDefaultRoute انجام شدند.

اکنون اگر برنامه‌ی IDP را مجددا با دستور dotnet run اجرا کنیم، تصویر زیر را می‌توان در ریشه‌ی سایت، مشاهده کرد که برای مثال لینک discovery endpoint در همان سطر اول آن ذکر شده‌است:


همچنین همانطور که در قسمت قبل نیز ذکر شد، یک IDP حتما باید از طریق پروتکل HTTPS در دسترس قرار گیرد که در پروژه‌های ASP.NET Core 2.1 این حالت، جزو تنظیمات پیش‌فرض است.


کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید.
نظرات اشتراک‌ها
Bulk delete و Bulk update در Entity framework
ممنون،
در صورتیکه که امتحان کردید،اطلاع رسانی کنید،میخوام نظر شما رو هم بدونم.