- ایجاد یک پروژهی جدید ASP.NET Core در VS 2017
- تنظیمات یک برنامهی ASP.NET Core خالی برای اجرای یک برنامهی Angular CLI
- تنظیمات فایل آغازین یک برنامهی ASP.NET Core جهت ارائهی برنامههای Angular
- ایجاد ساختار اولیهی برنامهی Angular CLI در داخل پروژهی جاری: این مورد را تاکنون انجام دادهایم و تکمیل کردهایم. بنابراین تنها کاری که نیاز است انجام شود، cut و paste محتوای پوشهی angular-template-driven-forms-lab (پروژهی این سری) به ریشهی پروژهی ASP.NET Core است.
- تنظیم محل خروجی نهایی Angular CLI به پوشهی wwwroot
- روش اول و یا دوم اجرای برنامههای مبتنی بر ASP.NET Core و Angular CLI
البته سورس کامل تمام این تنظیمات را از انتهای بحث نیز میتوانید دریافت کنید.
ضمن اینکه هیچ نیازی هم به استفاده از VS 2017 نیست و هر دوی برنامهی Angular و ASP.NET Core را میتوان توسط VSCode به خوبی مدیریت و اجرا کرد.
ایجاد ساختار مقدماتی سرویس ارسال اطلاعات به سرور
در برنامههای Angular مرسوم است جهت کاهش مسئولیتهای یک کلاس و امکان استفادهی مجدد از کدها، منطق ارسال اطلاعات به سرور، به درون کلاس یک سرویس منتقل شود و سپس این سرویس به کلاسهای کامپوننتها، برای مثال یک فرم ثبت اطلاعات، برای ارسال و یا دریافت اطلاعات، تزریق گردد. به همین جهت، ابتدا ساختار ابتدایی این سرویس و تنظیمات مرتبط با آنرا انجام میدهیم.
ابتدا از طریق خط فرمان به پوشهی ریشهی برنامه وارد شده (جائیکه فایل Startup.cs قرار دارد) و سپس دستور ذیل را اجرا میکنیم:
>ng g s employee/FormPoster -m employee.module
installing service create src\app\employee\form-poster.service.spec.ts create src\app\employee\form-poster.service.ts update src\app\employee\employee.module.ts
ساختار ابتدایی این سرویس را نیز به نحو ذیل تغییر میدهیم:
import { Injectable } from '@angular/core'; import { Http } from '@angular/http'; import { Employee } from './employee'; @Injectable() export class FormPosterService { constructor(private http:Http) { } postEmployeeForm(employee: Employee) { } }
چون این کلاس از ماژول توکار Http استفاده میکند، نیاز است این ماژول را نیز به قسمت imports فایل src\app\app.module.ts اضافه کنیم:
import { HttpModule } from "@angular/http"; @NgModule({ imports: [ BrowserModule, FormsModule, HttpModule, EmployeeModule, AppRoutingModule ]
import { FormPosterService } from "../form-poster.service"; export class EmployeeRegisterComponent implements OnInit { constructor(private formPoster: FormPosterService) {} }
در ادامه برای آزمایش برنامه، به ریشهی پروژه وارد شده و دو پنجرهی کنسول مجزا را باز کنید. در اولی، دستورات:
>npm install >ng build --watch
>dotnet restore >dotnet watch run
به همین جهت برای آزمایش ابتدایی آن، آدرس http://localhost:5000 را در مرورگر باز کنید. برگهی developer tools مرورگر را نیز بررسی کنید تا خطایی در آن ظاهر نشده باشد. برای مثال اگر فراموش کرده باشید تا HttpModule را به app.module اضافه کنید، خطای no provider for HttpModule را مشاهده خواهید کرد.
مدیریت رخداد submit فرم در Angular
تا اینجا کار برپایی تنظیمات اولیهی کار با سرویس Http را انجام دادیم. مرحلهی بعد مدیریت رخداد submit فرم است. به همین جهت فایل src\app\employee\employee-register\employee-register.component.html را گشوده و سپس رخدادگردان submit را به فرم آن اضافه کنید:
<form #form="ngForm" (submit)="submitForm(form)" novalidate>
export class EmployeeRegisterComponent implements OnInit { submitForm(form: NgForm) { console.log(this.model); console.log(form.value); } }
در همین حال اگر بر روی دکمهی ok کلیک کنیم، چنین خروجی را در کنسول developer مروگر میتوان مشاهده کرد:
اولین مورد، محتوای this.model است و دومی محتوای form.value را گزارش کردهاست. همانطور که مشاهده میکنید، مقدار form.value بسیار شبیه است به وهلهای از مدلی که در سطح کلاس تعریف کردهایم و این مقدار همواره توسط Angular نگهداری و مدیریت میشود. بنابراین حتما الزامی نیست تا مدلی را جهت کار با فرمهای مبتنی بر قالبها به صورت جداگانهای تهیه کرد. توسط شیء form نیز میتوان به تمام اطلاعات فیلدها دسترسی یافت.
تکمیل سرویس ارسال اطلاعات به سرور
در ادامه میخواهیم اطلاعات مدل فرم را به سرور ارسال کنیم. برای این منظور سرویس FormPoster را به صورت ذیل تکمیل میکنیم:
import { Injectable } from "@angular/core"; import { Http, Response, Headers, RequestOptions } from "@angular/http"; 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 { Employee } from "./employee"; @Injectable() export class FormPosterService { private baseUrl = "api/employee"; constructor(private http: Http) {} private extractData(res: Response) { const body = res.json(); return body.fields || {}; } private handleError(error: Response): Observable<any> { console.error("observable error: ", error); return Observable.throw(error.statusText); } postEmployeeForm(employee: Employee): Observable<Employee> { const body = JSON.stringify(employee); const headers = new Headers({ "Content-Type": "application/json" }); const options = new RequestOptions({ headers: headers }); return this.http .post(this.baseUrl, body, options) .map(this.extractData) .catch(this.handleError); } }
در متد postEmployeeForm، ابتدا توسط JSON.stringify محتوای شیء کارمند encode میشود. البته متد post اینکار را به صورت توکار نیز میتواند مدیریت کند. سپس ذکر هدر مناسب در اینجا الزامی است تا در سمت سرور بتوانیم اطلاعات دریافتی را به شیء متناظری نگاشت کنیم. در غیراینصورت model binder سمت سرور نمیداند که چه نوع فرمتی را دریافت کردهاست و چه نوع decoding را باید انجام دهد.
در قسمت map، کار بررسی اطلاعات دریافتی از سرور را انجام خواهیم داد و اگر در این بین خطایی وجود داشت، توسط متد handleError در کنسول developer مرورگر نمایش داده میشود.
خروجی متد postEmployeeForm یک Observable است. بنابراین تا زمانیکه یک subscriber نداشته باشد، اجرا نخواهد شد. به همین جهت به کلاس EmployeeRegisterComponent مراجعه کرده و متد submitForm را به نحو ذیل تکمیل میکنیم:
submitForm(form: NgForm) { console.log(this.model); console.log(form.value); // validate form this.validatePrimaryLanguage(this.model.primaryLanguage); if (this.hasPrimaryLanguageError) { return; } this.formPoster .postEmployeeForm(this.model) .subscribe( data => console.log("success: ", data), err => console.log("error: ", err) ); }
یک نکته: اگر علاقمند باشید تا ساختار واقعی شیء NgForm را مشاهده کنید، در ابتدای متد فوق، console.log(form.form) را فراخوانی کنید و سپس شیء حاصل را در کنسول developer مرورگر بررسی نمائید.
تکمیل Web API برنامهی ASP.NET Core جهت دریافت اطلاعات از کلاینتها
در ابتدای سرویس formPoster، یک چنین تعریفی را داریم:
export class FormPosterService { private baseUrl = "api/employee";
ابتدا مدل زیر را به پروژهی ASP.NET Core جاری، معادل نمونهی تایپاسکریپتی سمت کلاینت آن اضافه میکنیم. البته در اینجا یک Id نیز اضافه شدهاست:
namespace AngularTemplateDrivenFormsLab.Models { public class Employee { public int Id { set; get; } public string FirstName { get; set; } public string LastName { get; set; } public bool IsFullTime { get; set; } public string PaymentType { get; set; } public string PrimaryLanguage { get; set; } } }
سپس کنترلر جدید EmployeeController را با محتوای ذیل اضافه خواهیم کرد:
using Microsoft.AspNetCore.Mvc; using AngularTemplateDrivenFormsLab.Models; namespace AngularTemplateDrivenFormsLab.Controllers { [Route("api/[controller]")] public class EmployeeController : Controller { public IActionResult Post([FromBody] Employee model) { //todo: save model model.Id = 100; return Created("", new { fields = model }); } } }
در اینجا پس از ثبت فرضی مدل، Id آن به همراه اطلاعات مدل، به نحوی که ملاحظه میکنید، بازگشت داده شدهاست. این نوع خروجی، یک چنین JSON ایی را تولید میکند:
{"fields":{"id":100,"firstName":"Vahid","lastName":"N","isFullTime":true,"paymentType":"FullTime","primaryLanguage":"Persian"}}
private extractData(res: Response) { const body = res.json(); return body.fields || {}; }
نمایش success به همراه شیءایی که از سمت سرور دریافت شدهاست؛ که حاصل اجرای سطر ذیل در متد submitForm است:
data => console.log("success: ", data)
بارگذاری اطلاعات drop down از سرور
تا اینجا اطلاعات drop down نمایش داده شده از یک آرایهی مشخص سمت کلاینت تامین شدند. در ادامه قصد داریم تا آنها را از سرور دریافت کنیم. به همین جهت اکشن متد ذیل را به کنترلر سمت سرور برنامه اضافه کنید:
[HttpGet("/api/[controller]/[action]")] public IActionResult Languages() { string[] languages = { "Persian", "English", "Spanish", "Other" }; return Ok(languages); }
پس از آن در سمت کلاینت این تغییرات نیاز هستند:
ابتدا به سرویس FormPosterService دو متد ذیل را اضافه میکنیم که کار آنها دریافت و پردازش اطلاعات از api/employee/languages سمت سرور هستند:
private extractLanguages(res: Response) { const body = res.json(); return body || {}; } getLanguages(): Observable<any> { return this.http .get(`${this.baseUrl}/languages`) .map(this.extractLanguages) .catch(this.handleError); }
پس از آن دو تغییر ذیل را نیاز است به EmployeeRegisterComponent اعمال کنیم:
languages = []; ngOnInit() { this.formPoster .getLanguages() .subscribe( data => this.languages = data, err => console.log("get error: ", err) ); }
مشکل! ممکن است مدت زمانی طول بکشد تا این اطلاعات از سمت سرور دریافت شوند. در این حالت میتوان به شکل زیر در فایل employee-register.component.html فرم را تا زمان پر شدن دراپ داون آن مخفی کرد:
<h3 *ngIf="languages.length == 0">Loading...</h3> <div class="container" *ngIf="languages.length > 0">
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: angular-template-driven-forms-lab-05.zip
برای اجرای آن فرض بر این است که پیشتر Angular CLI را نصب کردهاید. سپس به ریشهی پروژه وارد شده و دو پنجرهی کنسول مجزا را باز کنید. در اولی دستورات:
>npm install >ng build --watch
>dotnet restore >dotnet watch run