در این پست و با استفاده از سری پستهای آقای مهندس یوسف نژاد در این زمینه، یک Attribute جهت هماهنگ سازی با سیستم اعتبار سنجی ASP.NET MVC در سمت سرور و سمت کلاینت (با استفاده از jQuery Validation) بررسی خواهد شد.
قبل از شروع مطالعه سری پستهای MVC و Entity Framework الزامی است.
برای انجام این کار ابتدا مدل زیر را در برنامه خود ایجاد میکنیم.
using System;
public class SampleModel
{
public DateTime StartDate { get; set; }
public string Data { get; set; }
public int Id { get; set; }
}
using System; using System.ComponentModel.DataAnnotations; public class SampleModel { [Required(ErrorMessage = "Start date is required")] public DateTime StartDate { get; set; } [Required(ErrorMessage = "Data is required")] public string Data { get; set; } public int Id { get; set; } }
@model SampleModel @{ ViewBag.Title = "Index"; Layout = "~/Views/Shared/_Layout.cshtml"; } <section> <header> <h3>SampleModel</h3> </header> @Html.ValidationSummary(true, null, new { @class = "alert alert-error alert-block" }) @using (Html.BeginForm("SaveData", "Sample", FormMethod.Post)) { <p> @Html.LabelFor(x => x.StartDate) @Html.TextBoxFor(x => x.StartDate) @Html.ValidationMessageFor(x => x.StartDate) </p> <p> @Html.LabelFor(x => x.Data) @Html.TextBoxFor(x => x.Data) @Html.ValidationMessageFor(x => x.Data) </p> <input type="submit" value="Save"/> } </section>
public class SampleController : Controller { // // GET: /Sample/ public ActionResult Index() { return View(); } public ActionResult SaveData(SampleModel item) { if (ModelState.IsValid) { //save data } else { ModelState.AddModelError("","لطفا خطاهای زیر را برطرف نمایید"); RedirectToAction("Index", item); } return View("Index"); } }
تا اینجای کار روال عادی همیشگی است. اما برای طراحی Attribute دلخواه جهت اعتبار سنجی (مثلا برای مجبور کردن کاربر به وارد کردن یک فیلد با پیام دلخواه و زبان دلخواه) باید یک کلاس جدید تعریف کرده و از کلاس RequiredAttribute ارث ببرد. در پارامتر ورودی این کلاس جهت کار با Resourceهای ثابت در نظر گرفته شده است اما برای اینکه فیلد دلخواه را از دیتابیس بخواند این روش جوابگو نیست. برای انجام آن باید کلاس RequiredAttribute بازنویسی شود.
کلاس طراحی شده باید به صورت زیر باشد:
public class VegaRequiredAttribute : RequiredAttribute, IClientValidatable { #region Fields (2) private readonly string _resourceId; private String _resourceString = String.Empty; #endregion Fields #region Constructors (1) public VegaRequiredAttribute(string resourceId) { _resourceId = resourceId; ErrorMessage = _resourceId; AllowEmptyStrings = true; } #endregion Constructors #region Properties (1) public new String ErrorMessage { get { return _resourceString; } set { _resourceString = GetMessageFromResource(value); } } #endregion Properties #region Methods (2) // Public Methods (1) public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context) { yield return new ModelClientValidationRule { ErrorMessage = GetMessageFromResource(_resourceId), ValidationType = "required" }; } // Private Methods (1) private string GetMessageFromResource(string resourceId) { var errorMessage = HttpContext.GetGlobalResourceObject(_resourceId, "Yes") as string; return errorMessage ?? ErrorMessage; } #endregion Methods }
1- ابتدا دستور
HttpContext.GetGlobalResourceObject(_resourceId, "Yes") as string;
که عنوان کلید Resource را از سازنده کلاس گرفته (کد اقای یوسف نژاد) رشته معادل آن را از دیتابیس بازیابی میکند.
2- ارث بری از اینترفیس IClientValidatable، در صورتی که از این اینترفیس ارث بری نداشته باشیم. Validator طراحی شده در طرف کلاینت کار نمیکند. بلکه کاربر با کلیک بروی دکمه مورد نظر دادهها را به سمت سرور ارسال میکند. در صورت وجود خطا در پست بک خطا نمایش داده خواهد شد. اما با ارث بری از این اینترفیس و پیاده سازی متد GetClientValidationRules میتوان تعریف کرد که در طرف کلاینت با استفاده از Unobtrusive jQuery پیام خطای مورد نظر به کنترل ورودی مورد نظر (مانند تکست باکس) اعمال میشود. مثلا در این مثال خصوصیت data-val-required به input هایی که قبلا در مدل ما Reqired تعریف شده اند اعمال میشود.
حال در مدل تعریف شده میتوان به جای Required میتوان از VegaRequiredAttribute مانند زیر استفاده کرد. (همراه با نام کلید مورد نظر در دیتابیس)
public class SampleModel { [VegaRequired("RequiredMessage")] public DateTime StartDate { get; set; } [VegaRequired("RequiredMessage")] public string Data { get; set; } public int Id { get; set; } }
با اجرای برنامه اینبار خروجی به شکل زیر خواهد بود.
جهت فعال ساری اعتبار سنجی سمت کلاینت ابتدا باید اسکریپتهای زیر به صفحه اضافه شود.
<script src="@Url.Content("~/Scripts/jquery-1.9.1.min.js")" type="text/javascript"></script> <script src="@Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script> <script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
<appSettings> <add key="ClientValidationEnabled" value="true" /> <add key="UnobtrusiveJavaScriptEnabled" value="true"/> </appSettings>
<script type="text/javascript"> jQuery.validator.addMethod('required', function (value, element, params) { if (value == null | value == "") { return false; } else { return true; } }, ''); jQuery.validator.unobtrusive.adapters.add('required', {}, function (options) { options.rules['required'] = true; options.messages['required'] = options.message; }); </script>
موفق و موید باشید.
منابع ^ و ^ و ^ و ^
معرفی سرویس MatDialog
توسط سرویس MatDialog میتوان modal dialogs بستهی Angular Material را نمایش داد که به همراه طراحی متریال و پویانمایی مخصوص آن است.
let dialogRef = dialog.open(UserProfileComponent, { height: '400px’, width: '600px’ });
dialogRef.afterClosed().subscribe(result => { console.log(`Dialog result: ${result}`); }); dialogRef.close('value');
در این مثال اگر dialogRef را با متد close و پارامتر value فراخوانی کنیم، سبب بسته شدن این دیالوگ خواهیم شد. این پارامتر در قسمت Dialog result پیام دریافتی پس از بسته شدن دیالوگ نیز قابل دسترسی است.
کامپوننتهایی که توسط سرویس MatDialog نمایش داده میشوند، میتوانند توسط سرویس جنریک MatDialogRef، صفحهی دیالوگ باز شده را ببندند:
@Component({/* ... */}) export class YourDialog { constructor(public dialogRef: MatDialogRef<YourDialog>) { } closeDialog() { this.dialogRef.close('Value….!’); } }
نحوهی طراحی یک دیالوگ نیز به کمک تعدادی کامپوننت و دایرکتیو میسر است:
<h2 mat-dialog-title>Delete all</h2> <mat-dialog-content>Are you sure?</mat-dialog-content> <mat-dialog-actions> <button mat-button mat-dialog-close>No</button> <!-- Can optionally provide a result for the closing dialog. --> <button mat-button [mat-dialog-close]="true">Yes</button> </mat-dialog-actions>
ایجاد دکمهی نمایش دیالوگ افزودن تماسها و کاربران جدید
قبل از هر کاری نیاز است دکمهی افزودن یک کاربر جدید را به صفحه اضافه کنیم. برای اینکار یک منوی ویژه را در سمت راست، بالای صفحه ایجاد میکنیم. بنابراین ابتدا به مستندات toolbar و menu مراجعه میکنیم تا با نحوهی تعریف دکمهها و منوها به toolbar آشنا شویم. سپس فایل قالب toolbar\toolbar.component.html را به صورت زیر تکمیل میکنیم:
<mat-toolbar color="primary"> <button mat-button fxHide fxHide.xs="false" (click)="toggleSidenav.emit()"> <mat-icon>menu</mat-icon> </button> <span>Contact Manager</span> <span fxFlex="1 1 auto"></span> <button mat-button [matMenuTriggerFor]="menu"> <mat-icon>more_vert</mat-icon> </button> <mat-menu #menu="matMenu"> <button mat-menu-item>New Contact</button> </mat-menu> </mat-toolbar>
سپس ابتدا یک mat-button را با آیکن more_vert (آیکن علامت بیشتر عمودی) تعریف کردهایم:
این دکمه توسط ویژگی matMenuTriggerFor به template reference variable ایی به نام menu متصل شدهاست تا با کلیک بر روی آن، این mat-menu را نمایش دهد:
ایجاد دیالوگ افزودن تماسها و کاربران جدید
پس از تعریف دکمه و منویی که سبب نمایش عبارت افزودن یک تماس جدید میشوند، به رخداد کلیک آن متدی را جهت نمایش صفحهی دیالوگ جدید اضافه میکنیم:
<button mat-menu-item (click)="openAddContactDialog()">New Contact</button>
ng g c contact-manager/components/new-contact-dialog --no-spec
import { NewContactDialogComponent } from "./components/new-contact-dialog/new-contact-dialog.component"; @NgModule({ declarations: [ NewContactDialogComponent], entryComponents: [ NewContactDialogComponent ] }) export class ContactManagerModule { }
import { Component, EventEmitter, OnInit, Output } from "@angular/core"; import { MatDialog } from "@angular/material"; import { NewContactDialogComponent } from "../new-contact-dialog/new-contact-dialog.component"; @Component({ selector: "app-toolbar", templateUrl: "./toolbar.component.html", styleUrls: ["./toolbar.component.css"] }) export class ToolbarComponent implements OnInit { @Output() toggleSidenav = new EventEmitter<void>(); constructor(private dialog: MatDialog) { } ngOnInit() { } openAddContactDialog(): void { const dialogRef = this.dialog.open(NewContactDialogComponent, { width: "450px" }); dialogRef.afterClosed().subscribe(result => { console.log("The dialog was closed", result); }); } }
تکمیل قالب کامپوننت تماس جدید
در ادامه میخواهیم فرم افزودن یک تماس جدید را به همراه فیلدهای ورودی آن، به قالب new-contact-dialog.component.html اضافه کنیم:
<h2 mat-dialog-title>Add new contact</h2> <mat-dialog-content> <div fxLayout="column"> </div> </mat-dialog-content> <mat-dialog-actions> <button mat-button color="primary" (click)="save()"> <mat-icon>save</mat-icon> Save </button> <button mat-button color="primary" (click)="dismiss()"> <mat-icon>cancel</mat-icon> Cancel </button> </mat-dialog-actions>
import { Component, OnInit } from "@angular/core"; import { MatDialogRef } from "@angular/material"; @Component() export class NewContactDialogComponent implements OnInit { constructor( private dialogRef: MatDialogRef<NewContactDialogComponent> ) { } ngOnInit() { } save() { } dismiss() { this.dialogRef.close(null); } }
تا اینجا اگر برنامه را اجرا کنیم، به چنین شکلی خواهیم رسید:
تکمیل فیلدهای ورود اطلاعات فرم ثبت یک تماس جدید
تا اینجا ساختار فرم دیالوگ ثبت اطلاعات جدید را تکمیل کردیم. این فرم، به شیء user متصل خواهد شد. همچنین لیستی از avatars را هم جهت انتخاب، نمایش میدهد. به همین جهت این دو خاصیت عمومی را به کدهای کامپوننت آن اضافه میکنیم:
import { User } from "../../models/user"; @Component() export class NewContactDialogComponent implements OnInit { avatars = ["user1", "user2", "user3", "user4", "user5", "user6", "user7", "user8"]; user: User = { id: 0, birthDate: new Date(), name: "", avatar: "", bio: "", userNotes: null };
الف) فیلد نمایش و انتخاب avatar کاربر
<mat-form-field> <mat-select placeholder="Avatar" [(ngModel)]="user.avatar"> <mat-select-trigger> <mat-icon svgIcon="{{user.avatar}}"></mat-icon> {{ user.avatar }} </mat-select-trigger> <mat-option *ngFor="let avatar of avatars" [value]="avatar"> <mat-icon svgIcon="{{avatar}}"></mat-icon> {{ avatar }} </mat-option> </mat-select> </mat-form-field>
در اینجا از کامپوننت mat-select برای انتخاب avatar کاربر استفاده شدهاست که نتیجهی نهایی انتخاب آن به خاصیت user.avatar متصل شدهاست.
گزینههای این لیست (mat-option) بر اساس آرایهی avatars که در کامپوننت تعریف کردیم، تامین میشوند که در اینجا از mat-icon برای نمایش آیکن مرتبط نیز استفاده شدهاست. در این مورد در قسمت قبل چهارم، بخش «بارگذاری و معرفی فایل svg نمایش avatars کاربران به Angular Material» بیشتر توضیح داده شدهاست.
کار mat-select-trigger، سفارشی سازی برچسب نمایشی این کنترل است.
ب) فیلد دریافت نام کاربر به همراه اعتبارسنجی آن
<mat-form-field> <input matInput placeholder="Name" #name="ngModel" [(ngModel)]="user.name" required> <mat-error *ngIf="name.invalid && name.touched">You must enter a name</mat-error> </mat-form-field>
در اینجا فیلد نام کاربر، به user.name متصل و همچنین توسط ویژگی required، پر کردن آن الزامی اعلام شدهاست. به همین جهت در تصویر فوق یک ستاره را نیز کنار آن مشاهده میکند که به صورت خودکار توسط Angular Material نمایش داده شدهاست.
از کامپوننت mat-error برای نمایش خطاهای اعتبارسنجی یک فیلد استفاده میشود که نمونهای از آنرا در اینجا با بررسی خواص invalid و touched فیلد نام که بر اساس ویژگی required فعال میشوند، مشاهده میکنید. بدیهی است در اینجا به هر تعدادی که نیاز است میتوان mat-error را قرار داد.
ج) فیلد دریافت تاریخ تولد کاربر توسط یک date picker
<mat-form-field> <input matInput [matDatepicker]="picker" placeholder="Born" [(ngModel)]="user.birthDate"> <mat-datepicker-toggle matSuffix [for]="picker"></mat-datepicker-toggle> <mat-datepicker #picker></mat-datepicker> </mat-form-field>
در اینجا از کامپوننت mat-datepicker برای انتخاب تاریخ تولید یک شخص استفاده شدهاست و نتیجهی آن به خاصیت user.birthDate متصل خواهد شد.
برای افزودن آن ابتدا یک mat-datepicker را به mat-form-field اضافه میکنیم. سپس یک template reference variable را به آن نسبت خواهیم داد. از آن هم در فیلد ورودی با انتساب آن به ویژگی matDatepicker و هم در کامپوننت mat-datepicker-toggle که سبب نمایش آیکن انتخاب تقویم میشود، در ویژگی for آن استفاده خواهیم کرد.
د) فیلد چند سطری دریافت توضیحات و شرححال کاربر
<mat-form-field> <textarea matInput placeholder="Bio" [(ngModel)]="user.bio"></textarea> </mat-form-field>
در اینجا برای دریافت توضیحات چندسطری، از یک text area استفاده شدهاست که به خاصیت user.bio متصل است.
بنابراین همانطور که ملاحظه میکنید، روش طراحی فرمهای Angular Material ویژگیهای خاص خودش را دارد:
- دایرکتیو matInput را میتوان به المانهای استاندارد input و textarea اضافه کرد تا داخل mat-form-field نمایش داده شوند. این mat-form-field است که کار اعمال CSS ویژهی طراحی متریال را انجام میدهد و امکان نمایش پیامهای خطای اعتبارسنجی و پویانمایی ورود اطلاعات را سبب میشود.
- قسمت mat-dialog-content را توسط fxLayout به حالت ستونی تنظیم کردیم:
<mat-dialog-content> <div fxLayout="column"> </div> </mat-dialog-content>
برای مثال اگر خواستید المانهای فرم با فاصلهی بیشتری از هم قرار بگیرند، میتوان از fxLayoutGap استفاده کرد که در مورد آن در قسمت دوم «معرفی Angular Flex layout» بیشتر توضیح داده شد.
تکمیل سرویس کاربران جهت ذخیرهی اطلاعات تماس کاربر جدید
در ادامه میخواهیم با کلیک کاربر بر روی دکمهی Save، ابتدا این اطلاعات به سمت سرور ارسال و سپس در سمت سرور ذخیره شوند. پس از آن، Id این کاربر جدید به سمت کلاینت بازگشت داده شود، دیالوگ جاری بسته و در آخر این شیء جدید به لیست تماسهای نمایش دادهی شدهی در sidenav اضافه گردد.
الف) تکمیل سرویس Web API سمت سرور
در ابتدا متد Post را به Web API برنامه جهت ذخیره سازی اطلاعات User ارسالی از سمت کلاینت اضافه میکنیم. کدهای کامل آنرا از فایل پیوستی انتهای بحث میتوانید دریافت کنید:
namespace MaterialAspNetCoreBackend.WebApp.Controllers { [Route("api/[controller]")] public class UsersController : Controller { private readonly IUsersService _usersService; public UsersController(IUsersService usersService) { _usersService = usersService ?? throw new ArgumentNullException(nameof(usersService)); } [HttpPost] public async Task<IActionResult> Post([FromBody] User user) { if (!ModelState.IsValid) { return BadRequest(ModelState); } await _usersService.AddUserAsync(user); return Created("", user); } } }
ب) تکمیل سرویس کاربران سمت کلاینت
سپس به فایل user.service.ts مراجعه کرده و دو تغییر زیر را به آن اضافه میکنیم:
@Injectable({ providedIn: "root" }) export class UserService { private usersSource = new BehaviorSubject<User>(null); usersSourceChanges$ = this.usersSource.asObservable(); constructor(private http: HttpClient) { } addUser(user: User): Observable<User> { const headers = new HttpHeaders({ "Content-Type": "application/json" }); return this.http .post<User>("/api/users", user, { headers: headers }).pipe( map(response => { const addedUser = response || {} as User; this.notifyUsersSourceHasChanged(addedUser); return addedUser; }), catchError((error: HttpErrorResponse) => throwError(error)) ); } notifyUsersSourceHasChanged(user: User) { this.usersSource.next(user); } }
return Created("", user);
بنابراین نیاز است از طریق این سرویس به کامپوننت sidenav، در مورد تغییرات لیست کاربران اطلاعات رسانی کنیم که روش کار آنرا پیشتر در مطلب «صدور رخدادها از سرویسها به کامپوننتها در برنامههای Angular» نیز مرور کردهایم. برای این منظور یک BehaviorSubject از نوع User را تعریف کردهایم که اشتراک به آن از طریق خاصیت عمومی usersSourceChanges میسر است. هر زمانیکه متد next آن فراخوانی شود، تمام مشترکین به آن، از افزوده شدن کاربر جدید، به همراه اطلاعات کامل آن مطلع خواهند شد.
ج) تکمیل متد save کامپوننت new-contact-dialog
پس از تکمیل سرویس کاربران جهت افزودن متد addUser به آن، اکنون میتوانیم از آن در کامپوننت دیالوگ افزودن اطلاعات تماس جدید استفاده کنیم:
import { UserService } from "../../services/user.service"; @Component() export class NewContactDialogComponent { user: User = { id: 0, birthDate: new Date(), name: "", avatar: "", bio: "", userNotes: null }; constructor( private dialogRef: MatDialogRef<NewContactDialogComponent>, private userService: UserService ) { } save() { this.userService.addUser(this.user).subscribe(data => { console.log("Saved user", data); this.dialogRef.close(data); }); } }
د) تکمیل کامپوننت sidenav جهت واکنش نشان دادن به افزوده شدن اطلاعات تماس جدید
اکنون که سرویس کاربران به صفحه دیالوگ افزودن اطلاعات یک تماس جدید متصل شدهاست، نیاز است بتوانیم اطلاعات کاربر جدید را به لیست تماسهای sidenav اضافه کنیم. به همین جهت به sidenav.component مراجعه کرده و مشترک usersSourceChanges سرویس کاربران خواهیم شد:
import { UserService } from "../../services/user.service"; @Component() export class SidenavComponent implements OnInit, OnDestroy { users: User[] = []; subscription: Subscription | null = null; constructor( private userService: UserService) { } ngOnInit() { this.subscription = this.userService.usersSourceChanges$.subscribe(user => { if (user) { this.users.push(user); } }); } ngOnDestroy() { if (this.subscription) { this.subscription.unsubscribe(); } } }
استفاده از کامپوننت Snackbar جهت نمایش موفقیت آمیز بودن ثبت اطلاعات
متد save کامپوننت دیالوگ یک تماس جدید را به صورت زیر تکمیل کردیم:
save() { this.userService.addUser(this.user).subscribe(data => { console.log("Saved user", data); this.dialogRef.close(data); });
openAddContactDialog(): void { const dialogRef = this.dialog.open(NewContactDialogComponent, { width: "450px" }); dialogRef.afterClosed().subscribe(result => { console.log("The dialog was closed", result); }); }
کدهای کامل این تغییرات را در ذیل مشاهده میکنید:
@Component() export class ToolbarComponent { @Output() toggleSidenav = new EventEmitter<void>(); constructor(private dialog: MatDialog, private snackBar: MatSnackBar, private router: Router) { } openAddContactDialog(): void { const dialogRef = this.dialog.open(NewContactDialogComponent, { width: "450px" }); dialogRef.afterClosed().subscribe((result: User) => { console.log("The dialog was closed", result); if (result) { this.openSnackBar(`${result.name} contact has been added.`, "Navigate").onAction().subscribe(() => { this.router.navigate(["/contactmanager", result.id]); }); } }); } openSnackBar(message: string, action: string): MatSnackBarRef<SimpleSnackBar> { return this.snackBar.open(message, action, { duration: 5000, }); } }
برای گشودن snackbar که نمونهای از آنرا در تصویر فوق ملاحظه میکنید، ابتدا نیاز است سرویس MatSnackBar را به سازندهی کلاس تزریق کرد. سپس توسط آن میتوان یک کامپوننت مستقل را همانند دیالوگها نمایش داد و یا میتوان یک متن را به همراه یک Action منتسب به آن، به کاربر نمایش داد؛ مانند متد openSnackBar که در کامپوننت فوق از آن استفاده میشود. این متد در رخداد پس از بسته شدن dialog، نمایش داده شدهاست.
پارامتر اول آن پیامی است که توسط snackbar نمایش داده میشود و پارامتر دوم آن، برچسب دکمه مانندی است کنار این پیام، که سبب انجام عملی خواهد شد و در اینجا به آن Action گفته میشود. برای مدیریت آن باید متد onAction را فراخوانی کرد و مشترک آن شد. در این حالت اگر کاربر بر روی این دکمهی action کلیک کند، سبب هدایت خودکار او به صفحهی نمایش جزئیات اطلاعات تماس کاربر خواهیم شد. به همین جهت سرویس Router نیز به سازندهی کلاس تزریق شدهاست تا بتوان از متد navigate آن استفاده کرد.
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: MaterialAngularClient-05.zip
برای اجرای آن:
الف) ابتدا به پوشهی src\MaterialAngularClient وارد شده و فایلهای restore.bat و ng-build-dev.bat را اجرا کنید.
ب) سپس به پوشهی src\MaterialAspNetCoreBackend\MaterialAspNetCoreBackend.WebApp وارد شده و فایلهای restore.bat و dotnet_run.bat را اجرا کنید.
اکنون برنامه در آدرس https://localhost:5001 قابل دسترسی است.
Bootstrap 4 در واقع یک اقدام بزرگ بود که پس از یک سال توسعه، بزرگی این اقدام در خط به خط کدها احساس میگردد. تصمیم گرفتیم تا نسخهی اولیهی آن را به اشتراک بگذاریم و انتقادات و پیشنهادات شما را بشنویم. برای بهبود و پیشرفت در این زمینه، بسیاری از اخبار مرتبط را در اختیار شما قرار میدهیم. امیدواریم که ما را در بهتر شدن یاری کنید.
امکانات جدید Bootstrap
انتقال از Less به Sass
بهبود grid system مبتنی بر "rems"
h1 { /* 16 * 2.5 = 40px */ }
تغییر panel و wells به cards
Reset Component جایگزینی برای normalize.css
ASP.NET MVC #4
بررسی نحوه ارتباطات بین اجزای مختلف الگوی MVC در ASP.NET MVC
اینبار برخلاف قسمت قبل، قالب پروژه خالی ASP.NET MVC را در VS.NET انتخاب کرده (ASP.NET MVC 3 Web Application و بعد انتخاب قالب Empty نمایش داده شده) و سپس پروژه جدیدی را شروع میکنیم. View Engine را هم Razor در نظر خواهیم گرفت.
پس از ایجاد ساختار اولیه پروژه، بدون اعمال هیچ تغییری، برنامه را اجرا کنید. بلافاصله با پیغام The resource cannot be found یا 404 یافت نشد، مواجه خواهیم شد.
همانطور که در پایان قسمت دوم نیز ذکر شد، پردازشها در ASP.NET MVC از کنترلرها شروع میشوند و نه از صفحات وب. بنابراین برای رفع این مشکل نیاز است تا یک کلاس کنترلر جدید را اضافه کنیم. به همین جهت بر روی پوشه استاندارد Controllers کلیک راست کرده، از منوی ظاهر شده قسمت Add، گزینهی Controller را انتخاب کنید:
در صفحه بعدی که ظاهر میشود، نام HomeController را وارد کنید (با توجه به اینکه مطابق قراردادهای ASP.NET MVC، نام کنترلر باید به کلمه Controller ختم شود). البته لازم به ذکر است که این مراحل را به همان شکل متداول مراجعه به منوی Project و انتخاب Add Class و سپس ارث بری از کلاس Controller نیز میتوان انجام داد و طی این مراحل الزامی نیست. کلاسی که به صورت خودکار از طریق منوی Add Controller یاد شده ایجاد میشود، به شکل زیر است:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
namespace MvcApplication1.Controllers
{
public class HomeController : Controller
{
//
// GET: /Home/
public ActionResult Index()
{
return View();
}
}
}
سؤال:
در قسمت دوم عنوان شد که کنترلر باید کلاسی باشد که اینترفیس IController را پیاده سازی کرده است، اما در اینجا ارث بری از کلاس Controller را شاهد هستیم. جریان چیست؟!
سلسله مراتبی که بکارگرفته شده به صورت زیر است:
public abstract class ControllerBase : IController
public abstract class Controller : ControllerBase
ControllerBase، اینترفیس IController را پیاده سازی کرده و سپس کلاس Controller از کلاس ControllerBase مشتق شده است. شاید بپرسید که این همه پیچ و تاب برای چیست؟!
مشکلی که با اینترفیس خالص وجود دارد، عدم نگارش پذیری آن است. به این معنا که اگر متدی یا خاصیتی در نگارش بعدی به این اینترفیس اضافه شد، هیچکدام از پروژههای قدیمی دیگر کامپایل نخواهند شد و باید ابتدا این متد یا خاصیت جدید را نیز لحاظ کنند. اینجا است که کار کلاسهای abstract شروع میشود. در یک کلاس abstract میتوان پیاده سازی پیش فرضی را نیز ارائه داد. به این ترتیب مصرف کننده نهایی کلاس Controller متوجه این تغییرات نخواهد شد.
اگر برنامه نویس «من» باشم، شما رو وادار خواهم کرد که متدهای جدید اینترفیس تعریفیام را پیاده سازی کنید! همینه که هست! اما اگر طراح مایکروسافت باشد، بلافاصله انبوهی از جماعت ایرادگیر که بالای 100 تا از کنترلرهای اونها الان فقط در یک پروژه از کار افتاده، ممکن است جلوی دفتر مایکروسافت دست به خود سوزی بزنند! اینجا است که مایکروسافت مجبور است تا این پیچ و تابها را اعمال کند که اگر روزی متدی در اینترفیس IController بنابر نیازهای جدید درنظر گرفته شد، بتوان سریع یک پیاده سازی پیش فرض از آنرا در کلاسهای abstract یاد شده قرار داد (یکی از تفاوتهای مهم کلاسهای abstract با اینترفیسها) تا جماعت ایرادگیر و نقزن متوجه تغییری نشوند و باز هم پروژههای قدیمی بدون مشکل کامپایل شوند. تابحال به فلسفه وجودی کلاسهای abstract از این دیدگاه فکر کرده بودید؟!
بعد از این توضیحات و کارها، اگر اینبار برنامه را اجرا کنیم، خطای زیر نمایش داده میشود:
The view 'Index' or its master was not found or no view engine supports the searched locations. The following locations were searched:
~/Views/Home/Index.aspx
~/Views/Home/Index.ascx
~/Views/Shared/Index.aspx
~/Views/Shared/Index.ascx
~/Views/Home/Index.cshtml
~/Views/Home/Index.vbhtml
~/Views/Shared/Index.cshtml
~/Views/Shared/Index.vbhtml
همانطور که ملاحظه میکنید چون نام کنترلر ما Home است، فریم ورک در پوشه استاندارد Views در زیر پوشهای به همان نام Home، به دنبال یک سری فایل میگردد. فایلهای aspx مربوط به View Engine ایی به همین نام بوده و فایلهای cshtml و vbhtml مربوط به View Engine دیگری به نام Razor هستند.
بنابراین نیاز است تا یکی از این فایلها را در مکانهای یاد شده ایجاد کنیم. برای این منظور حداقل دو راه وجود دارد. یا دستی اینکار را انجام دهیم یا اینکه از ابزار توکار خود VS.NET برای ایجاد یک View جدید استفاده کنیم.
برای ایجاد View ایی مرتبط با متد Index (در ASP.NET MVC نام دیگر متدهای قرار گرفته در یک کنترلر، Action نیز میباشد)، روی خود متد کلیک راست کرده و گزینه Add View را انتخاب کنید:
در صفحه بعدی ظاهر شده، پیش فرضها را پذیرفته و بر روی دکمه Add کلیک نمائید. اتفاقی که رخ خواهد داد شامل ایجاد فایل Index.cshtml، با محتوای زیر است (با توجه به اینکه زبان پروژه سی شارپ است و View Engine انتخابی Razor میباشد، cshtml تولید گردید و گرنه vbhtml ایجاد میشد):
@{
ViewBag.Title = "Index";
}
<h2>Index</h2>
مجددا برنامه را اجرا کنید. اینبار بدون خطایی کلمه Index را در صفحه تولیدی میتوان مشاهده کرد. نکته جالب این فایلهای View جدید، عدم مشاهده ویژگیهای runat=server و سایر موارد مشابه است.
چند سؤال مهم:
در حین ایجاد اولین کنترلر جهت نمایش صفحه پیش فرض برنامه، نام HomeController انتخاب شد. چرا مثلا نام TestController وارد نشد؟ برنامه از کجا متوجه شد که باید حتما این کنترلر را پردازش کند. نقش متد Index چیست؟ آیا حتما باید Index باشد و در اینجا نام دیگری را نمیتوان وارد کرد؟ «قرارداد» پردازشی اینها کجا تنظیم میشود؟ فریم ورک، این سیم کشیها را چگونه انجام میدهد؟
پاسخ به تمام این سؤالها، در ویژگی «مسیریابی یا Routing» نهفته است. فایل Global.asax.cs برنامه را باز کنید. تعاریف مرتبط با مسیریابی پیش فرض را در متد RegisterRoutes آن میتوان مشاهده کرد:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
}
پروسه هدایت یک درخواست HTTP به یک کنترلر، در اینجا مسیریابی یا Routing نامیده میشود. این قابلیت در فضای نام System.Web.Routing تعریف شده است و باید دقت داشت که جزو ASP.NET MVC نیست. این امکانات جزو ASP.NET Runtime است و به همراه دات نت 3.5 سرویس پک یک برای اولین بار ارائه شد. بنابراین جهت استفاده در ASP.NET Web forms نیز مهیا است. در ASP.NET MVC از این امکانات برای ارسال درخواستها به کنترلرها استفاده میشود.
در متد routes.MapRoute فراخوانی شدهای که در کدهای بالا ملاحظه میکنید، کار نگاشت یک URL به Actionهای یک کنترلر صورت میگیرد (یا همان متدهای تعریف شده در کنترلرها). همچنین از این URLها پارامترهای این متدها یا اکشنها نیز قابل استخراج است.
در متد MapRoute، اولین پارامتر تعریف شده، یک نام پیش فرض است و در ادامه اگر آدرسی را به فرم «یک چیزی اسلش یک چیزی اسلش یک چیزی» یافت، اولین قسمت آنرا به عنوان نام کنترلر تفسیر خواهد کرد، دومین قسمت آن، نام متد عمومی موجود در کنترلر فرض شده و سومین قسمت به عنوان پارامتر ارسالی به این متد پردازش میشود.
برای مثال از آدرس زیر اینطور میتوان دریافت که:
http://hostname/home/about
Home نام کنترلی است که فریم ورک به دنبال آن خواهد گشت تا این درخواست رسیده را پردازش کند و about نام متدی عمومی در این کلاس است که به صورت خودکار فراخوانی میگردد. در اینجا پارامتر id ایی هم وجود ندارد. در یک برنامه امکان تعریف چندین و چند مسیریابی وجود دارد.
پارامتر سوم متد routes.MapRoute، یک سری پیش فرض را تعریف میکند و این مورد همانجایی است که از اطلاعات آن جهت تعریف کنترلر پیش فرض استفاده کردیم. برای مثال به چهار آدرس زیر دقت نمائید:
http://localhost/
http://localhost/home
http://localhost/home/about
http://localhost/process/list
در حالت http://localhost/، هر سه مقدار پیش فرض مورد استفاده قرار خواهند گرفت چون سه جزئی ({controller}/{action}/{id}) که موتور مسیریابی به دنبال آنها میگردد، در این آدرس وجود خارجی ندارد. بنابراین نام کنترلر پیش فرض در این حالت همان Home مشخص شده در پارامتر سوم متد routes.MapRoute خواهد بود و متد پیش فرض نیز Index و پارامتری هم به آن ارسال نخواهد شد.
در حالت http://localhost/home نام کنترلر مشخص است اما دو جزء دیگر ذکر نشدهاند، بنابراین مقادیر آنها از پیش فرضهای صریح ذکر شده در متد routes.MapRoute گرفته میشود. یعنی نام متد اکشن مورد پردازش، Index خواهد بود به همراه هیچ آرگومان خاصی.
به علاوه باید خاطرنشان کرد که این مقادیر case sensitive یا حساس به بزرگی و کوچکی حروف نیستند. بنابراین مهم نیست که http://localhost/HoMe باشد یا http://localhost/HOMe یا هر ترکیب دیگری.
یا اگر آدرس رسیده http://localhost/process/list باشد، این مسیریابی پیش فرض تعریف شده قادر به پردازش آن میباشد. به این معنا که کنترلری به نام Process وهله سازی و سپس متدی به نام List در آن فراخوانی خواهند شد.
یک نکته:
ترتیب routes.MapRouteهای تعریف شده در اینجا مهم است و اگر اولین URL رسیده با الگوی تعریف شده مطابقت داشت، کار را تمام خواهد کرد و به سایر تعاریف نخواهد رسید. مثلا اگر در اینجا یک مسیریابی دیگر را به نام Process تعریف کنیم:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Process", // Route name
"Process/{action}/{id}", // URL with parameters
new { controller = "Process", action = "List", id = UrlParameter.Optional } // Parameter defaults
);
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
}
آدرسی به فرم http://localhost/Process، به صورت خودکار به کنترلر Process و متد عمومی List آن نگاشت خواهد شد و کار به مسیریابی بعدی نخواهد رسید.
ترفندهای یونیکد برای زبانهای راست به چپ
یک نکتهی تکمیلی: اگر میخواهید کاربران موبایل به سادگی بتوانند اعداد صحیح را وارد کنند، از یک ورودی با ویژگیهای type=tel و inputmode=numeric استفاده کنید:
<input type="tel" inputmode="numeric">
مزیت اینکار، نمایش خودکار صفحه کلید تمام عددی تنظیم شدهی بر روی حالت انگلیسی است؛ به این ترتیب مشکل «... در دستگاههای موبایل، زمانیکه صفحه کلید در حالت فارسی قرار دارد، اعداد را هم فارسی وارد میکند ...» اصلا رخ نمیدهد.
«مشکل ی و ک فارسی و عربی در یک دیتابیس اس کیوال سرور»
اولین پروژه
معمولا برای شروع از تاریخچه و توضیحات دیگر استفاده میکنند اما روش آموزشی که در پیش خواهیم گرفت با انجام پروژههای عملی بوده و هر جا که نیاز به توضیح باشد ، بیان میکنیم ...
ایجاد اولین پروژه Win32 Console Application
ویژوال استادیو را اجرا نمایید و از گزینه File -> New -> Project و سپس طبق عکس زیر پروژه Win32 Console Application را انتخاب نمایید ، دقت کنید که زبان انتخاب شده ++Visual C باشد.
در این مرحله میتوانید محل ذخیره شده پروژه را در قسمت Location تنظیم نمایید و از قسمت Name میتوانید نام دلخواه را وارد کنید در حالت پیش فرض اگر اولین پروژه Win32 Console در مسیر تعین شدهی قسمت Location باشد ، نام ConsoleApplication1 قرار گرفته است . پس از تنظیمات Ok کنید .
در این مرحله Next را بزنید .
در این مرحله در قسمت Additional options تیک Empty project را بزنید ، همانند عکس فوق تنظیمات را انجام دهید .
پس از انجام مراحل فوق پروژه بصورت شکل زیر ظاهر میشود .
برای کد نویسی روی نام پروژه که در اینجا ConsoleApplication1 میباشد ، راست کلیک میکنیم و گزینه Add و سپس New Item را انتخاب میکنیم .
طبق عکس زیر فایل با پسوند cpp را انتخاب و Add میکنیم .
فایلی که اکنون به پروژه اضافه کردیم خالی و با نام پیش فرض Source.cpp میباشد ، دستورات زیر را در آن تایپ کنید .حال پروژه به شکل زیر خواهد بود .
#include<iostream> int main() { std::cout<<"Hello world ...\n"; return 0; }
برای اجرای پروژه کلید F5 را فشار دهید و اگر میخواهید نتیجه کار را مشاهده کنید کلید Ctrl + F5 را امتحان کنید .
شما اولین پروژه VC++ را اجرا نمودید ( آفرین ) .
اما توضیحات :
خط اول برنامه یک راهنمای پیش پردازنده است ، کاراکتر # که نشان میدهد این خط یک راهنمای پیش پردازنده است و بعد عبارت include و نام یک فایل کتابخانه ای که بین علامت <> قرار داده شده ، فایل سرآیند استفاده شده در اینجا iostream میباشد . (به فایلهای کتابخانه ای ، فایلهای سرآیند (Header Files) نیز گفته میشود. ) راهنمای پیش پردازنده خطی است که به کامپایلر اطلاع میدهد در برنامه موجودیتی است که تعریف آن را در فایل سرآیند مذکور جستجو کند . در این برنامه از std::cout استفاده شده ، که کامپایلر در مورد آن چیزی نمیداند لذا به فایل iostream مراجعه نموده ، تعریف آن را مییابد و آن را اجرا میکند .
خط 3 :
بخشی از هر برنامه تابع میباشد . پرانتزهای واقع پس از آن main نشان میدهند که main یک بلوک برنامه بنام تابع است. برنامهها میتوانند حاوی یک یا چندین تابع باشند، اما main تابع اصلی برنامه است که وجود آن الزامی میباشد . کلمه کلیدی int که در سمت چپ main قرار گرفته، بر این نکته دلالت دارد که main یک مقدار صحیح برمیگرداند.
خط 5 :
با استفاده از این دستور رشته ای را به خروجی استاندارد که معمولا صفحه نمایش باشد ارسال میکنیم .
خط 6 :
که ;0 return میباشد مقدار برگشتی تابع را مشخص میکند در حقیقت این خط که مقدار 0 را برمیگرادند نشان دهنده اتمام موفقیت آمیز برنامه میباشد .
به مرور زمان نسبت به موارد بالا بیشتر و مفصل صحبت خواهیم نمود .
ASP.NET MVC #21
آشنایی با تکنیکهای Ajax در ASP.NET MVC
اهمیت آشنایی با Ajax، ارائه تجربه کاربری بهتری از برنامههای وب، به مصرف کنندگان نهایی آن میباشد. به این ترتیب میتوان درخواستهای غیرهمزمانی (asynchronous) را با فرمت XML یا Json به سرور ارسال کرد و سپس نتیجه نهایی را که حجم آن نسبت به یک صفحه کامل بسیار کمتر است، به کاربر ارائه داد. غیرهمزمان بودن درخواستها سبب میشود تا ترد اصلی رابط کاربری برنامه قفل نشده و کاربر در این بین میتواند به سایر امور خود بپردازد. به این ترتیب میتوان برنامههای وبی را که شبیه به برنامههای دسکتاپ هستند تولید نمود؛ کل صفحه مرتبا به سرور ارسال نمیشود، flickering و چشمک زدن صفحه کاهش خواهد یافت (چون نیازی به ترسیم مجدد کل صفحه نخواهد بود و عموما قسمتی جزئی از یک صفحه به روز میشود) یا بدون نیاز به ارسال کل صفحه به سرور، به کاربری خواهیم گفت که آیا اطلاعاتی که وارد کرده است معتبر میباشد یا نه (نمونهای از آن را در قسمت Remote validation اعتبار سنجی اطلاعات ملاحظه نمودید).
مروری بر محتویات پوشه Scripts یک پروژه جدید ASP.NET MVC در ویژوال استودیو
با ایجاد هر پروژه ASP.NET MVC جدیدی در ویژوال استودیو، یک سری اسکریپت هم به صورت خودکار در پوشه Scripts آن اضافه میشوند. تعدادی از این فایلها توسط مایکروسافت پیاده سازی شدهاند. برای مثال:
MicrosoftAjax.debug.js
MicrosoftAjax.js
MicrosoftMvcAjax.debug.js
MicrosoftMvcAjax.js
MicrosoftMvcValidation.debug.js
MicrosoftMvcValidation.js
این فایلها از ASP.NET MVC 3 به بعد، صرفا جهت سازگاری با نگارشهای قبلی قرار دارند و استفاده از آنها اختیاری است. بنابراین با خیال راحت آنها را delete کنید! روش توصیه شده جهت پیاده سازی ویژگیهای Ajax ایی، استفاده از کتابخانههای مرتبط با jQuery میباشد؛ از این جهت که 100ها افزونه برای کار با آن توسط گروه وسیعی از برنامه نویسها در سراسر دنیا تاکنون تهیه شده است. به علاوه فریم ورک jQuery تنها منحصر به اعمال Ajax ایی نیست و از آن جهت دستکاری DOM (document object model) و CSS صفحه نیز میتوان استفاده کرد. همچنین حجم کمی نیز داشته، با انواع و اقسام مرورگرها سازگار است و مرتبا هم به روز میشود.
در این پوشه سه فایل دیگر پایه کتابخانه jQuery نیز قرار دارند:
jquery-xyz-vsdoc.js
jquery-xyz.js
jquery-xyz.min.js
فایل vsdoc برای ارائه نهایی برنامه طراحی نشده است. هدف از آن ارائه Intellisense بهتری از jQuery در VS.NET میباشد. فایلی که باید به کلاینت ارائه شود، فایل min یا فشرده شده آن است. اگر به آن نگاهی بیندازیم به نظر obfuscated مشاهده میشود. علت آن هم حذف فواصل، توضیحات و همچنین کاهش طول متغیرها است تا اندازه فایل نهایی به حداقل خود کاهش پیدا کند. البته این فایل از دیدگاه مفسر جاوا اسکریپت یک مرورگر، فایل بینقصی است!
اگر علاقمند هستید که سورس اصلی jQuery را مطالعه کنید، به فایل jquery-xyz.js مراجعه نمائید.
محل الحاق اسکریپتهای عمومی مورد نیاز برنامه نیز بهتر است در فایل master page یا layout برنامه باشد که به صورت پیش فرض اینکار انجام شده است.
سایر فایلهای اسکریپتی که در این پوشه مشاهده میشوند، یک سری افزونه عمومی یا نوشته شده توسط تیم ASP.NET MVC برفراز jQuery هستند.
به چهار نکته نیز حین استفاده از اسکریپتهای موجود باید دقت داشت:
الف) همیشه از متد Url.Content همانند تعاریفی که در فایل Views\Shared\_Layout.cshtml مشاهده میکنید، برای مشخص سازی مسیر ریشه سایت، استفاده نمائید. به این ترتیب صرفنظر از آدرس جاری صفحه، همواره آدرس صحیح قرارگیری پوشه اسکریپتها در صفحه ذکر خواهد شد.
ب) ترتیب فایلهای js مهم هستند. ابتدا باید کتابخانه اصلی jQuery ذکر شود و سپس افزونههای آنها.
ج) اگر اسکریپتهای jQuery در فایل layout سایت تعریف شدهاند؛ نیازی به تعریف مجدد آنها در Viewهای سایت نیست.
د) اگر View ایی به اسکریپت ویژهای جهت اجرا نیاز دارد، بهتر است آنرا به شکل یک section داخل view تعریف کرد و سپس به کمک متد RenderSection این قسمت را در layout سایت مقدار دهی نمود. مثالی از آنرا در قسمت 20 این سری مشاهده نمودید (افزودن نمایش جمع هر ستون گزارش).
یک نکته
اگر آخرین به روز رسانیهای ASP.NET MVC را نیز نصب کرده باشید، فایلی به نام packages.config به صورت پیش فرض به هر پروژه جدید ASP.NET MVC اضافه میشود. به این ترتیب VS.NET به کمک NuGet این امکان را خواهد یافت تا شما را از آخرین به روز رسانیهای این کتابخانهها مطلع کند.
آشنایی با Ajax Helpers توکار ASP.NET MVC
اگر به تعاریف خواص و متدهای کلاس WebViewPage دقت کنیم:
using System;
namespace System.Web.Mvc
{
public abstract class WebViewPage<TModel> : WebViewPage
{
protected WebViewPage();
public AjaxHelper<TModel> Ajax { get; set; }
public HtmlHelper<TModel> Html { get; set; }
public TModel Model { get; }
public ViewDataDictionary<TModel> ViewData { get; set; }
public override void InitHelpers();
protected override void SetViewData(ViewDataDictionary viewData);
}
}
علاوه بر خاصیت Html که وهلهای از آن امکان دسترسی به Html helpers توکار ASP.NET MVC را در یک View فراهم میکند، خاصیتی به نام Ajax نیز وجود دارد که توسط آن میتوان به تعدادی متد AjaxHelper توکار دسترسی داشت. برای مثال توسط متد Ajax.ActionLink میتوان قسمتی از صفحه را به کمک ویژگیهای Ajax، به روز رسانی کرد.
مثالی در مورد به روز رسانی قسمتی از صفحه به کمک متد Ajax.ActionLink
ابتدا نیاز است فایل Views\Shared\_Layout.cshtml را اندکی ویرایش کرد. برای این منظور سطر الحاق jquery.unobtrusive-ajax.min.js را به فایل layout برنامه اضافه نمائید (اگر این سطر اضافه نشود، متد Ajax.ActionLink همانند یک لینک معمولی رفتار خواهد کرد):
<head>
<title>@ViewBag.Title</title>
<link href="@Url.Content("~/Content/Site.css")" rel="stylesheet" type="text/css" />
<script src="@Url.Content("~/Scripts/jquery-1.5.1.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.unobtrusive-ajax.min.js")" type="text/javascript"></script>
</head>
سپس مدل ساده و منبع داده زیر را نیز به پروژه اضافه کنید:
namespace MvcApplication18.Models
{
public class Employee
{
public int Id { set; get; }
public string Name { set; get; }
}
}
using System.Collections.Generic;
namespace MvcApplication18.Models
{
public static class EmployeeDataSource
{
public static IList<Employee> CreateEmployees()
{
var list = new List<Employee>();
for (int i = 0; i < 1000; i++)
{
list.Add(new Employee { Id = i + 1, Name = "name " + i });
}
return list;
}
}
}
در ادامه کنترلر جدیدی را به برنامه با محتوای زیر اضافه کنید:
using System.Linq;
using System.Web.Mvc;
using MvcApplication18.Models;
namespace MvcApplication18.Controllers
{
public class HomeController : Controller
{
[HttpGet]
public ActionResult Index()
{
return View();
}
[HttpPost] //for IE-8
public ActionResult EmployeeInfo(int? id)
{
if (!Request.IsAjaxRequest())
return View("Error");
if (!id.HasValue)
return View("Error");
var list = EmployeeDataSource.CreateEmployees();
var data = list.Where(x => x.Id == id.Value).FirstOrDefault();
if (data == null)
return View("Error");
return PartialView(viewName: "_EmployeeInfo", model: data);
}
}
}
بر روی متد Index کلیک راست کرده و گزینه Add view را انتخاب کنید. یک View خالی را به آن اضافه نمائید. همچنین بر روی متد EmployeeInfo کلیک راست کرده و با انتخاب گزینه Add view در صفحه ظاهر شده یک partial view را اضافه نمائید. جهت تمایز بین partial view و view هم بهتر است نام partial view با یک underline شروع شود.
کدهای partial view مورد نظر را به نحو زیر تغییر دهید:
@model MvcApplication18.Models.Employee
<strong>Name:</strong> @Model.Name
سپس کدهای View متناظر با متد Index را نیز به صورت زیر اعمال کنید:
@{
ViewBag.Title = "Index";
}
<h2>
Index</h2>
<div id="EmployeeInfo">
@Ajax.ActionLink(
linkText: "Get Employee-1 info",
actionName: "EmployeeInfo",
controllerName: "Home",
routeValues: new { id = 1 },
ajaxOptions: new AjaxOptions
{
HttpMethod = "POST",
InsertionMode = InsertionMode.Replace,
UpdateTargetId = "EmployeeInfo",
LoadingElementId = "Progress"
})
</div>
<div id="Progress" style="display: none">
<img src="@Url.Content("~/Content/images/loading.gif")" alt="loading..." />
</div>
توضیحات جزئیات کدهای فوق
متد Ajax.ActionLink لینکی را تولید میکند که با کلیک کاربر بر روی آن، اطلاعات اکشن متد واقع در کنترلری مشخص، به کمک ویژگیهای jQuery Ajax دریافت شده و سپس در مقصدی که توسط UpdateTargetId مشخص میگردد، بر اساس مقدار InsertionMode، درج خواهد شد (میتواند قبل از آن درج شود یا پس از آن و یا اینکه کل محتوای مقصد را بازنویسی کند). HttpMethod آن هم به POST تنظیم شده تا با IE مشکلی نباشد. از این جهت که IE پیغامهای GET را کش میکند و مساله ساز خواهد شد. توسط پارامتر routeValues، آرگومان مورد نظر به متد EmployeeInfo ارسال خواهد شد.
به علاوه یکی دیگر از خواص کلاس AjaxOptions، برای معرفی حالت بروز خطایی در سمت سرور به نام OnFailure در نظر گرفته شده است. در اینجا میتوان نام یک متد JavaScript ایی را مشخص کرده و پیغام خطای عمومی را در صورت فراخوانی آن به کاربر نمایش داد. یا توسط خاصیت Confirm آن میتوان یک پیغام را پیش از ارسال اطلاعات به سرور به کاربر نمایش داد.
به این ترتیب در مثال فوق، id=1 به متد EmployeeInfo به صورت غیرهمزمان ارسال میگردد. سپس کارمندی بر این اساس یافت شده و در ادامه partial view مورد نظر بر اساس اطلاعات کاربر مذکور، رندر خواهد شد. نتیجه کار، در یک div با id مساوی EmployeeInfo درج میگردد (InsertionMode.Replace). متد Ajax.ActionLink از این جهت داخل div تعریف شدهاست که پس از کلیک کاربر و جایگزینی محتوا، محو شود. اگر نیازی به محو آن نبود، آنرا خارج از div تعریف کنید.
عملیات دریافت اطلاعات از سرور ممکن است مدتی طول بکشد (برای مثال دریافت اطلاعات از بانک اطلاعاتی). به همین جهت بهتر است در این بین از تصاویری که نمایش دهنده انجام عملیات است، استفاده شود. برای این منظور یک div با id مساوی Progress تعریف شده و id آن به LoadingElementId انتساب داده شده است. این div با توجه به display: none آن، در ابتدای امر به کاربر نمایش داده نخواهد شد؛ در آغاز کار دریافت اطلاعات از سرور توسط متد Ajax.ActionLink نمایان شده و پس از خاتمه کار مجددا مخفی خواهد شد.
به علاوه اگر به کدهای فوق دقت کرده باشید، از متد Request.IsAjaxRequest نیز استفاده شده است. به این ترتیب میتوان تشخیص داد که آیا درخواست رسیده از طرف jQuery Ajax صادر شده است یا خیر. البته آنچنان روش قابل ملاحظهای نیست؛ چون امکان دستکاری Http Headers همیشه وجود دارد؛ اما بررسی آن ضرری ندارد. البته این نوع بررسیها را در ASP.NET MVC بهتر است تبدیل به یک فیلتر سفارشی نمود؛ به این ترتیب حجم if و else نویسی در متدهای کنترلرها به حداقل خواهد رسید. برای مثال:
[AttributeUsage(AttributeTargets.Class|AttributeTargets.Method)]
public class AjaxOnlyAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (filterContext.HttpContext.Request.IsAjaxRequest())
{
base.OnActionExecuting(filterContext);
}
else
{
throw new InvalidOperationException("This operation can only be accessed via Ajax requests");
}
}
}
و برای استفاده از آن خواهیم داشت:
[AjaxOnly]
public ActionResult SomeAjaxAction()
{
return Content("Hello!");
}
در مورد کلمه unobtrusive در قسمت بررسی نحوه اعتبار سنجی اطلاعات، توضیحاتی را ملاحظه نمودهاید. در اینجا نیز از ویژگیهای data-* برای معرفی پارامترهای مورد نیاز حین ارسال اطلاعات به سرور، استفاده میگردد. برای مثال خروجی متد Ajax.ActionLink به شکل زیر است. به این ترتیب امکان حذف کدهای جاوا اسکریپت از صفحه فراهم میشود و توسط یک فایل jquery.unobtrusive-ajax.min.js که توسط تیم ASP.NET MVC تهیه شده، اطلاعات مورد نیاز به سرور ارسال خواهد گردید:
<a data-ajax="true" data-ajax-loading="#Progress" data-ajax-method="POST"
data-ajax-mode="replace" data-ajax-update="#EmployeeInfo"
href="/Home/EmployeeInfo/1">Get Employee-1 info</a>
در کل این روش قابلیت نگهداری بهتری نسبت به روش اسکریپت نویسی مستقیم داخل صفحات را به همراه دارد. به علاوه جدا سازی افزونه اسکریپت وفق دهنده این اطلاعات با متد jQuery.Ajax از صفحه جاری، که امکان کش شدن آنرا به سادگی میسر میسازد.
به روز رسانی اطلاعات قسمتی از صفحه بدون استفاده از متد Ajax.ActionLink
الزامی به استفاده از متد Ajax.ActionLink و فایل jquery.unobtrusive-ajax.min.js وجود ندارد. اینکار را مستقیما به کمک jQuery نیز میتوان به نحو زیر انجام داد:
<a href="#" onclick="LoadEmployeeInfo()">Get Employee-1 info</a>
@section javascript
{
<script type="text/javascript">
function LoadEmployeeInfo() {
showProgress();
$.ajax({
type: "POST",
url: "/Home/EmployeeInfo",
data: JSON.stringify({ id: 1 }),
contentType: "application/json; charset=utf-8",
dataType: "json",
// controller is returning a simple text, not json
complete: function (xhr, status) {
var data = xhr.responseText;
if (status === 'error' || !data) {
//handleError
}
else {
$('#EmployeeInfo').html(data);
}
hideProgress();
}
});
}
function showProgress() {
$('#Progress').css("display", "block");
}
function hideProgress() {
$('#Progress').css("display", "none");
}
</script>
}
توضیحات:
توسط متد jQuery.Ajax نیز میتوان درخواستهای Ajax ایی خود را به سرور ارسال کرد. در اینجا type نوع http verb مورد نظر را مشخص میکند که به POST تنظیم شده است. Url آدرس کنترلر را دریافت میکند. البته حین استفاده از متد توکار Ajax.ActionLink، این لینک به صورت خودکار بر اساس تعاریف مسیریابی برنامه تنظیم میشود. اما در صورت استفاده مستقیم از jQuery.Ajax باید دقت داشت که با تغییر تعاریف مسیریابی برنامه نیاز است تا این Url نیز به روز شود.
سه سطر بعدی نوع اطلاعاتی را که باید به سرور POST شوند مشخص میکند. نوع json است و همچنین contentType آن برای ارسال اطلاعات یونیکد ضروری است. از متد JSON.stringify برای تبدیل اشیاء به رشته کمک گرفتهایم. این متد در تمام مرورگرهای امروزی به صورت توکار پشتیبانی میشود و استفاده از آن سبب خواهد شد تا اطلاعات به نحو صحیحی encode شده و به سرور ارسال شوند. بنابراین این رشته ارسالی اطلاعات را به صورت دستی تهیه نکنید؛ چون کاراکترهای زیادی هستند که ممکن است مشکل ساز شده و باید پیش از ارسال به سرور اصطلاحا escape یا encode شوند.
متداول است از پارامتر success برای دریافت نتیجه عملیات متد jQuery.Ajax استفاده شود. اما در اینجا از پارامتر complete آن استفاده شده است. علت هم اینجا است که return PartialView یک رشته را بر میگرداند. پارامتر success انتظار دریافت خروجی از نوع json را دارد. به همین جهت در این مثال خاص باید از پارامتر complete استفاده کرد تا بتوان به رشته بدون فرمت خروجی بدون مشکل دسترسی پیدا کرد.
به علاوه چون از یک section برای تعریف اسکریپتهای مورد نیاز استفاده کردهایم، برای درج خودکار آن در هدر صفحه باید قسمت هدر فایل layout برنامه را به صورت زیر مقدار دهی کرد:
@RenderSection("javascript", required: false)
دسترسی به اطلاعات یک مدل در View، به کمک jQuery Ajax
اگر جزئی از صفحه که قرار است به روز شود، پیچیده است، روش استفاده از partial viewها توصیه میشود؛ برای مثال میتوان اطلاعات یک مدل را به همراه یک گرید کامل از اطلاعات، رندر کرد و سپس در صفحه درج نمود. اما اگر تنها به اطلاعات چند خاصیت از مدلی نیاز داشتیم، میتوان از روشهایی با سربار کمتر نیز استفاده کرد. برای مثال متد جدید زیر را به کنترلر Home اضافه کنید:
[HttpPost] //for IE-8
public ActionResult EmployeeInfoData(int? id)
{
if (!Request.IsAjaxRequest())
return Json(false);
if (!id.HasValue)
return Json(false);
var list = EmployeeDataSource.CreateEmployees();
var data = list.Where(x => x.Id == id.Value).FirstOrDefault();
if (data == null)
return Json(false);
return Json(data);
}
سپس View برنامه را نیز به نحو زیر تغییر دهید:
<a href="#" onclick="LoadEmployeeInfoData()">Get Employee-2 info</a>
@section javascript
{
<script type="text/javascript">
function LoadEmployeeInfoData() {
showProgress();
$.ajax({
type: "POST",
url: "/Home/EmployeeInfoData",
data: JSON.stringify({ id: 1 }),
contentType: "application/json; charset=utf-8",
dataType: "json",
// controller is returning the json data
success: function (result) {
if (result) {
alert(result.Id + ' - ' + result.Name);
}
hideProgress();
},
error: function (result) {
alert(result.status + ' ' + result.statusText);
hideProgress();
}
});
}
function showProgress() {
$('#Progress').css("display", "block");
}
function hideProgress() {
$('#Progress').css("display", "none");
}
</script>
}
در این مثال، کنترلر برنامه، اطلاعات مدل را تبدیل به Json کرده و بازگشت خواهد داد. سپس میتوان به اطلاعات این مدل و خواص آن در View برنامه، در پارامتر success متد jQuery.Ajax، مطابق کدهای فوق دسترسی یافت. اینبار چون خروجی کنترلر تعریف شده از نوع Json است، امکان استفاده از پارامتر success فراهم شده است. همه چیز هم در اینجا خودکار است؛ تبدیل یک شیء به Json و برعکس.
یک نکته: اگر نوع متد کنترلر، HttpGet باشد، نیاز خواهد بود تا پارامتر دوم متد بازگشت Json، مساوی JsonRequestBehavior.AllowGet قرار داده شود.
ارسال اطلاعات فرمها به سرور، به کمک ویژگیهای Ajax
متد کمکی توکار دیگری به نام Ajax.BeginForm در ASP.NET MVC وجود دارد که کار ارسال غیرهمزمان اطلاعات یک فرم را به سرور انجام داده و سپس اطلاعاتی را از سرور دریافت و قسمتی از صفحه را به روز خواهد کرد. مکانیزم کاری کلی آن بسیار شبیه به متد Ajax.ActionLink میباشد. در ادامه با تکمیل مثال قسمت جاری، به بررسی این ویژگی خواهیم پرداخت.
ابتدا متد جستجوی زیر را به کنترلر برنامه اضافه کنید:
[HttpPost] //for IE-8
public ActionResult SearchEmployeeInfo(string data)
{
if (!Request.IsAjaxRequest())
return Content(string.Empty);
if (string.IsNullOrWhiteSpace(data))
return Content(string.Empty);
var employeesList = EmployeeDataSource.CreateEmployees();
var list = employeesList.Where(x => x.Name.Contains(data)).ToList();
if (list == null || !list.Any())
return Content(string.Empty);
return PartialView(viewName: "_SearchEmployeeInfo", model: list);
}
سپس بر روی نام متد کلیک راست کرده و گزینه add view را انتخاب کنید. در صفحه باز شده، گزینه create a stronlgly typed view را انتخاب کرده و قالب scaffolding را هم بر روی list قرار دهید. سپس گزینه ایجاد partial view را نیز انتخاب کنید. نام آنرا هم _SearchEmployeeInfo وارد نمائید. برای نمونه خروجی حاصل به نحو زیر خواهد بود:
@model IEnumerable<MvcApplication18.Models.Employee>
<table>
<tr>
<th>
Name
</th>
</tr>
@foreach (var item in Model) {
<tr>
<td>
@Html.DisplayFor(modelItem => item.Name)
</td>
</tr>
}
</table>
تا اینجا یک متد جستجو را ایجاد کردهایم که میتواند لیستی از رکوردهای کارمندان را بر اساس قسمتی از نام آنها که توسط کاربری جستجو شده است، بازگشت دهد. سپس این اطلاعات را به partial view مورد نظر ارسال کرده و یک جدول را بر اساس آن تولید خواهیم نمود.
اکنون به فایل Index.cshtml مراجعه کرده و فرم Ajax ایی زیر را اضافه نمائید:
@using (Ajax.BeginForm(actionName: "SearchEmployeeInfo",
controllerName: "Home",
ajaxOptions: new AjaxOptions
{
HttpMethod = "POST",
InsertionMode = InsertionMode.Replace,
UpdateTargetId = "EmployeeInfo",
LoadingElementId = "Progress"
}))
{
@Html.TextBox("data")
<input type="submit" value="Search" />
}
اینبار بجای استفاده از متد Html.BeginForm از متد Ajax.BeginForm استفاده شده است. به کمک آن اطلاعات Html.TextBox تعریف شده، به کنترلر Home و متد SearchEmployeeInfo آن، بر اساس HttpMethod تعریف شده، ارسال گردیده و نتیجه آن در یک div با id مساوی EmployeeInfo درج میگردد. همچنین اگر اطلاعاتی یافت نشد، به کمک متد return Content یک رشته خالی بازگشت داده میشود.
متد Ajax.BeginForm نیز از ویژگیهای data-* برای تعریف اطلاعات مورد نیاز ارسالی به سرور استفاده میکند. بنابراین نیاز به سطر الحاق jquery.unobtrusive-ajax.min.js در فایل layout برنامه جهت وفق دادن این اطلاعات unobtrusive به اطلاعات مورد نیاز متد jQuery.Ajax وجود دارد.
<form action="/Home/SearchEmployeeInfo" data-ajax="true"
data-ajax-loading="#Progress" data-ajax-method="POST"
data-ajax-mode="replace" data-ajax-update="#EmployeeInfo"
id="form0" method="post">
<input id="data" name="data" type="text" value="" />
<input type="submit" value="Search" />
</form>
کتابخانه کمکی «ASP.net MVC Awesome - jQuery Ajax Helpers»
علاوه بر متدهای توکار Ajax همراه با ASP.NET MVC، سایر علاقمندان نیز یک سری Ajax helper را بر اساس افزونههای jQuery تدارک دیدهاند که از آدرس زیر قابل دریافت هستند:
http://awesome.codeplex.com/
افزودن فرمها به کمک jQuery.Ajax و فعال سازی اعتبار سنجی سمت کلاینت
در ASP.NET MVC چون ViewState حذف شده است، امکان تزریق فرمهای جدید به صفحه یا به روز رسانی قسمتی از صفحه توسط jQuery Ajax به سهولت و بدون دریافت پیغام «viewstate is corrupted» در حین ارسال اطلاعات به سرور، میسر است.
در این حالت باید به یک نکته مهم نیز دقت داشت: «اعتبار سنجی سمت کلاینت دیگر کار نمیکند». علت اینجا است که در حین بارگذاری متداول یک صفحه، متد زیر به صورت خودکار فراخوانی میگردد:
$.validator.unobtrusive.parse("#{form-id}");
اما با به روز رسانی قسمتی از صفحه، دیگر اینچنین نخواهد بود و نیاز است این فراخوانی را دستی انجام دهیم. برای مثال:
$.ajax
({
url: "/{controller}/{action}/{id}",
type: "get",
success: function(data)
{
$.validator.unobtrusive.parse("#{form-id}");
}
});
//or
$.get('/{controller}/{action}/{id}', function (data) { $.validator.unobtrusive.parse("#{form-id}"); });
شبیه به همین مساله را حین استفاده از Ajax.BeginForm نیز باید مد نظر داشت:
@using (Ajax.BeginForm(
"Action1",
"Controller",
null,
new AjaxOptions {
OnSuccess = "onSuccess",
UpdateTargetId = "result"
},
null)
)
{
<input type="submit" value="Save" />
}
var onSuccess = function(result) {
// enable unobtrusive validation for the contents
// that was injected into the <div id="result"></div> node
$.validator.unobtrusive.parse("#result");
};
در این مثال در پارامتر UpdateTargetId، مجددا یک فرم رندر میشود. بنابراین اعتبار سنجی سمت کلاینت آن دیگر کار نخواهد کرد مگر اینکه با مقدار دهی خاصیت OnSuccess، مجددا متد unobtrusive.parse را فراخوانی کنیم.