using System; using System.ComponentModel.DataAnnotations; namespace AngularTemplateDrivenFormsLab.Models { public class Movie { public int Id { get; set; } [Required(ErrorMessage = "Movie Title is Required")] [MinLength(3, ErrorMessage = "Movie Title must be at least 3 characters")] public string Title { get; set; } [Required(ErrorMessage = "Movie Director is Required.")] public string Director { get; set; } [Range(0, 100, ErrorMessage = "Ticket price must be between 0 and 100.")] public decimal TicketPrice { get; set; } [Required(ErrorMessage = "Movie Release Date is required")] public DateTime ReleaseDate { get; set; } } }
using AngularTemplateDrivenFormsLab.Models; using Microsoft.AspNetCore.Mvc; namespace AngularTemplateDrivenFormsLab.Controllers { [Route("api/[controller]")] public class MoviesController : Controller { [HttpPost] public IActionResult Post([FromBody]Movie movie) { if (ModelState.IsValid) { // TODO: save ... return Ok(movie); } ModelState.AddModelError("", "This record already exists."); // a cross field validation return BadRequest(ModelState); } } }
الف) خطاهای اعتبارسنجی در سطح فیلدها
زمانیکه return BadRequest(ModelState) صورت میگیرد، محتویات شیء ModelState به همراه status code مساوی 400 به سمت کلاینت ارسال خواهد شد. در شیء ModelState یک دیکشنری که کلیدهای آن، نام خواص و مقادیر متناظر با آنها، خطاهای اعتبارسنجی تنظیم شدهی در مدل است، قرار دارند.
ب) خطاهای اعتبارسنجی عمومی
در این بین میتوان دیکشنری ModelState را توسط متد AddModelError نیز تغییر داد و برای مثال کلید آنرا مساوی "" تعریف کرد. در این حالت یک چنین خطایی به کل فرم اشاره میکند و نه به یک خاصیت خاص.
نمونهای از خروجی نهایی ارسالی به سمت کاربر:
{"":["This record already exists."],"TicketPrice":["Ticket price must be between 0 and 100."]}
به همین جهت نیاز است بتوان خطاهای حالت (الف) را دقیقا در ذیل هر فیلد و خطاهای حالت (ب) را در بالای فرم به صورت عمومی به کاربر نمایش داد:
پردازش و دریافت خطاهای اعتبارسنجی سمت سرور در یک برنامهی Angular
با توجه به اینکه سرور، شیء ModelState را توسط return BadRequest به سمت کلاینت ارسال میکند، برای پردازش دیکشنری دریافتی از سمت آن، تنها کافی است قسمت بروز خطای عملیات ارسال اطلاعات را بررسی کنیم:
در این HttpErrorResponse دریافتی، دو خاصیت error که همان آرایهی دیکشنری نام خواص و پیامهای خطای مرتبط با هر کدام و status code دریافتی مهم هستند:
errors: string[] = []; processModelStateErrors(form: NgForm, responseError: HttpErrorResponse) { if (responseError.status === 400) { const modelStateErrors = responseError.error; for (const fieldName in modelStateErrors) { if (modelStateErrors.hasOwnProperty(fieldName)) { const modelStateError = modelStateErrors[fieldName]; const control = form.controls[fieldName] || form.controls[this.lowerCaseFirstLetter(fieldName)]; if (control) { // integrate into Angular's validation control.setErrors({ modelStateError: { error: modelStateError } }); } else { // for cross field validations -> show the validation error at the top of the screen this.errors.push(modelStateError); } } } } else { this.errors.push("something went wrong!"); } } lowerCaseFirstLetter(data: string): string { return data.charAt(0).toLowerCase() + data.slice(1); }
در اینجا از آرایهی errors برای نمایش خطاهای عمومی در سطح فرم استفاده میکنیم. این خطاها در ModelState، دارای کلید مساوی "" هستند. به همین جهت حلقهای را بر روی شیء responseError.error تشکیل میدهیم. به این ترتیب میتوان به نام خواص و همچنین خطاهای متناظر با آنها رسید.
const control = form.controls[fieldName] || form.controls[this.lowerCaseFirstLetter(fieldName)];
در ادامه اگر control ایی یافت شد، توسط متد setErrors، کلید جدید modelStateError را که دارای خاصیت سفارشی error است، تنظیم میکنیم. با اینکار سبب خواهیم شد تا خطای اعتبارسنجی دریافتی از سمت سرور، با سیستم اعتبارسنجی Angular یکی شود. به این ترتیب میتوان این خطا را دقیقا ذیل همین کنترل در فرم نمایش داد. اگر کنترلی یافت نشد (کلید آن "" بود و یا جزو نام کنترلهای موجود در آرایهی form.controls نبود)، این خطا را به آرایهی errors اضافه میکنیم تا در بالاترین سطح فرم قابل نمایش شود.
نحوهی استفادهی از متد processModelStateErrors فوق را در متد submitForm، در قسمت شکست عملیات ارسال اطلاعات، مشاهده میکنید:
model = new Movie("", "", 0, ""); successfulSave: boolean; errors: string[] = []; constructor(private movieService: MovieService) { } ngOnInit() { } submitForm(form: NgForm) { console.log(form); this.errors = []; this.movieService.postMovieForm(this.model).subscribe( (data: Movie) => { console.log("Saved data", data); this.successfulSave = true; }, (responseError: HttpErrorResponse) => { this.successfulSave = false; console.log("Response Error", responseError); this.processModelStateErrors(form, responseError); }); }
نمایش خطاهای اعتبارسنجی عمومی فرم
اکنون که کار مقدار دهی آرایهی errors انجام شدهاست، میتوان حلقهای را بر روی آن تشکیل داد و عناصر آنرا در بالای فرم، به صورت عمومی و مستقل از تمام فیلدهای آن نمایش داد:
<form #form="ngForm" (submit)="submitForm(form)" novalidate> <div class="alert alert-danger" role="alert" *ngIf="errors.length > 0"> <ul> <li *ngFor="let error of errors"> {{ error }} </li> </ul> </div> <div class="alert alert-success" role="alert" *ngIf="successfulSave"> Movie saved successfully! </div>
نمایش خطاهای اعتبارسنجی در سطح فیلدهای فرم
با توجه به تنظیم خطاهای اعتبارسنجی کنترلهای Angular در متد processModelStateErrors و داشتن کلید جدید modelStateError
control.setErrors({ modelStateError: { error: modelStateError } });
<ng-template #validationErrorsTemplate let-ctrl="control"> <div *ngIf="ctrl.invalid && ctrl.touched"> <div class="alert alert-danger" *ngIf="ctrl.errors.required"> This field is required. </div> <div class="alert alert-danger" *ngIf="ctrl.errors.minlength"> This field should be minimum {{ctrl.errors.minlength.requiredLength}} characters. </div> <div class="alert alert-danger" *ngIf="ctrl.errors.maxlength"> This field should be max {{ctrl.errors.maxlength.requiredLength}} characters. </div> <div class="alert alert-danger" *ngIf="ctrl.errors.pattern"> This field's pattern: {{ctrl.errors.pattern.requiredPattern}} </div> <div class="alert alert-danger" *ngIf="ctrl.errors.modelStateError"> {{ctrl.errors.modelStateError.error}} </div> </div> </ng-template>
<div class="form-group" [class.has-error]="releaseDate.invalid && releaseDate.touched"> <label class="control-label" for="releaseDate">Release Date</label> <input type="text" name="releaseDate" #releaseDate="ngModel" class="form-control" required [(ngModel)]="model.releaseDate" /> <ng-container *ngTemplateOutlet="validationErrorsTemplate; context:{ control: releaseDate }"></ng-container> </div>
کدهای کامل این مطلب را از اینجا میتوانید دریافت کنید.