کتابخانه jQuery-hemi-intro
Source could be also accessed from GitHub -> https://github.com/fpetru/WebApiMongoDB.
Problem / solution format brings an easier understanding on how to build things, giving an immediate feedback. Starting from this idea, the blog post will present step by step how to build
a web application to store your ideas in an easy way, adding text notes, either from desktop or mobile, with few characteristics: run fast, save on the fly whatever you write, and be reasonably reliable and secure.
This blog post will implement just the backend, WebApi and the database access, in the most simple way. Next blog post will cover the front end, using Angular. Then, there will be an additional article on how to increase the performance and the security.
- canActivate : جهت محافظت دسترسی به یک مسیر
- canActivateChild: برای محافظت دسترسی به یک Child Route
- canDeactivate : برای جلوگیری کردن از ترک مسیر جاری و هدایت به مسیری دیگر (برای مثال جهت نمایش پیام «هنوز اطلاع تغییر یافته را ذخیره نکردهاید»)
- canLoad : برای جلوگیری از مسیریابی غیرهمزمان (async routing) که در قسمت بعدی بررسی خواهد شد
- resolve: برای پیش واکشی اطلاعات، پیش از نمایش مسیر (که آنرا در قسمت چهارم این سری بررسی کردیم)
لزوم استفادهی از محافظهای مسیرها
گاهی از اوقات میخواهیم دسترسی به یک مسیر را محدود به کاربران وارد شدهی به سیستم کنیم و یا مسیرهایی را داشته باشیم که تنها توسط گروه خاصی از کاربران قابل دسترسی باشند. همچنین در بسیاری از اوقات نیاز است به کاربران اخطارهایی را پیش از ترک یک مسیر نمایش دهیم. برای مثال پیش از ترک صفحهی ویرایش اطلاعاتی که دارای اطلاعات ذخیره نشدهاست، بهتر است پیامی را جهت یادآوری این مساله نمایش دهیم. برای پیاده سازی هر کدام از این قابلیتها از یک محافظ مسیر ویژه استفاده میشود.
ترتیب اجرای محافظهای مسیرها
مسیریاب سیستم، ابتدا محافظ canDeactivate را اجرا میکند تا مشخص شود که آیا کاربر میتواند مسیر جاری را ترک کند یا خیر؟ سپس اگر مسیریابی تعریف شده غیرهمزمان باشد، محافظ canLoad اجرا میشود. پس از آن محافظ canActivateChild بررسی میشود. در ادامه محافظ canActivate اجرا میگردد. در پایان کار بررسی محافظهای موجود، کار بررسی محافظ resolve، جهت پیش واکشی اطلاعات مسیر درخواستی، انجام خواهد شد.
در اینجا اگر یکی از محافظها مقدار false را برگرداند، پردازش مابقی آنها لغو خواهد شد و کار هدایت کاربر به مسیر درخواستی، خاتمه مییابد.
مراحل ساخت و اعمال یک محافظ مسیر
ساخت و اعمال یک محافظ مسیر شامل سه مرحله است:
الف) یک محافظ مسیر عموما به صورت یک سرویس جدید پیاده سازی میشود:
import { Injectable } from '@angular/core'; import { CanActivate } from '@angular/router'; @Injectable() export class AuthGuard implements CanActivate { canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean { } }
ب) از آنجائیکه محافظها، سرویس هستند، نیاز است تعریف کلاس آنها را در قسمت providers ماژول مرتبط نیز ذکر کنیم تا در برنامه قابل دسترسی شوند. باید دقت داشت که برخلاف سایر سرویسها، امکان تعریف محافظها صرفا در سطح یک ماژول مسیر است و نه در سطح یک کامپوننت. به این ترتیب مسیریاب میتواند به آن، در طی هدایت کاربر به مسیر درخواستی، دسترسی پیدا کند.
ج) پس از آن برای فعالسازی یک محافظ مسیر، آنرا به عنوان یک خاصیت جدید، به تنظیمات مسیریابی اضافه خواهیم کرد. نام این خاصیت دقیقا مساوی با نوع محافظی است که تعریف شدهاست. برای مثال اگر محافظ تعریف شده از نوع CanActivate است، نام خاصیتی که ذکر خواهد شد، canActivate میباشد. مقدار آن نیز میتواند آرایهای از سرویسهایی از این نوع باشد.
امکان به اشتراک گذاشتن یک محافظ بین چندین مسیر نیز وجود دارد. فرض کنید میخواهیم تمام مسیرهای مربوط به محصولات را محافظت کنیم. در این حالت میتوان محافظ را به تک تک Child routes موجود اعمال کرد و یا میتوان محافظ را به والد آنها نیز اعمال کنیم تا به صورت خودکار سبب محافظت از فرزندان آن نیز شویم.
یک مثال: ساخت محافظ canActivate
جهت بررسی شرط یا شرایطی پیش از فعال سازی یک مسیر درخواستی، از محافظهایی از نوع canActivate میتوان استفاده کرد. این نوع محافظها عموما جهت اعتبارسنجی کاربران و محدود سازی دسترسی آنها به قسمتهای مختلف برنامه استفاده میشوند. این نوع محافظها حتی با تغییر پارامترهای مسیریابی نیز فعال شده و بررسی میشوند.
در ادامهی مثال این سری میخواهیم کاربران را پیش از دسترسی به قسمتهای مختلف مرتبط با محصولات، وادار به لاگین کنیم. برای این منظور دستور ذیل را اجرا کنید:
>ng g guard user/auth -m user/user.module
installing guard create src\app\user\auth.guard.spec.ts create src\app\user\auth.guard.ts update src\app\user\user.module.ts
در ادامه کدهای این محافظ را به صورت ذیل تکمیل کنید:
import { Injectable } from '@angular/core'; import { ActivatedRouteSnapshot, RouterStateSnapshot, CanActivate, Router } from '@angular/router'; import { AuthService } from './auth.service'; @Injectable() export class AuthGuard implements CanActivate { constructor(private authService: AuthService, private router: Router) { } canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean { return this.checkLoggedIn(state.url); } checkLoggedIn(url: string): boolean { if (this.authService.isLoggedIn()) { return true; } this.authService.redirectUrl = url; this.router.navigate(['/login']); return false; } }
export class AuthService { currentUser: IUser; redirectUrl: string;
توضیحات:
این سرویس چون از نوع CanActivate است، این اینترفیس را پیاده سازی کردهاست و همچنین متد canActivate آنرا نیز به همراه دارد:
export class AuthGuard implements CanActivate { canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
یک نکته: هرچند در اینجا میتوان به پارامتر id مسیر، مانند route.params['id'] در صورت نیاز دسترسی یافت، اما امکان دسترسی به اطلاعات از پیش واکشی شده مانند route.data['product'] وجود ندارد. علت آنرا نیز در قسمت «ترتیب اجرای محافظهای مسیرها» ابتدای بحث جاری، بررسی کردیم: محافظ resolve در انتهای کار پردازش تمام محافظهای موجود فراخوانی میشود.
در متد canActivate میخواهیم بررسی کنیم که آیا کاربر، لاگین کردهاست یا خیر؟ اگر بله، تنها کافی است true را بازگشت دهیم تا کار این محافظ پایان یابد. در غیراینصورت false را بازگشت داده و همچنین سبب هدایت کاربر به صفحهی لاگین میشویم.
به همین منظور سرویس AuthService را به سازندهی این کلاس تزریق کردهایم تا بتوانیم به متد isLoggedIn آن دسترسی پیدا کنیم (این سرویس را در قسمت دوم این سری تکمیل کردیم).
این متد نیز به صورت ذیل تعریف شدهاست:
isLoggedIn(): boolean { return !this.currentUser; }
در ادامه برای استفادهی از این محافظ مسیر، به فایل src\app\product\product-routing.module.ts مراجعه کرده و آنرا به نحو ذیل اعمال خواهیم کرد:
import { AuthGuard } from './../user/auth.guard'; const routes: Routes = [ { path: 'products', canActivate: [ AuthGuard ], children: [ ] } ];
اکنون برنامه را با دستور ng s -o ساخته و اجرا کنید. سپس بر روی لینک لیست محصولات و یا افزودن یک محصول جدید کلیک کنید. بلافاصله صفحهی لاگین را مشاهده خواهید کرد.
به خاطر سپاری و بازیابی مسیر درخواستی کاربر پس از لاگین
در اینجا اگر کاربر بر روی لینک افزودن یک محصول جدید کلیک کند، صفحهی لاگین را مشاهده خواهد کرد. اما پس از لاگین، همواره به مسیر لیست محصولات هدایت میشود و در این حالت مسیر درخواستی اولیه فراموش خواهد شد. برای رفع این مشکل نیاز است آدرس درخواستی کاربر را نیز ذخیره و بازیابی کرد. به همین جهت خاصیت this.authService.redirectUrl = url را در متد checkLoggedIn محافظ تعریف شده مقدار دهی کردیم. در اینجا از سرویس Auth، برای به اشتراک گذاری اطلاعات با محافظهای مسیر استفاده کردهایم. طول عمر یک سرویس، singleton است. بنابراین تنها یک وهله از آن در طول عمر برنامه وجود خواهد داشت. به این ترتیب با ذخیرهی اطلاعاتی در آن، این اطلاعات در تمام برنامه قابل دسترسی خواهد شد.
با توجه به این نکته، اکنون به فایل src\app\user\login\login.component.ts مراجعه کرده و قسمت this.router.navigate آنرا به صورت ذیل بهبود خواهیم بخشید:
if (this.authService.login(userName, password)) { if (this.authService.redirectUrl) { this.router.navigateByUrl(this.authService.redirectUrl); } else { this.router.navigate(['/products']); } }
در ادامه برای آزمایش آن، پس از اجرای برنامه، صفحهی افزودن یک محصول جدید را درخواست دهید. سپس لاگین کنید. اکنون مشاهده خواهید کرد که برنامه مسیر درخواستی پیش از لاگین را به خاطر سپردهاست.
بررسی محافظ canActivateChild
این محافظ نیز شبیه به محافظ canActivate است؛ با این تفاوت که تنها زمانی فعالسازی خواهد شد که فرزند یک مسیر قرار است نمایش داده شود و نه خود مسیر اصلی.
محافظ canActivateChild با تغییر قسمت child یک مسیر فعالسازی میشود؛ حتی اگر این تغییر در حد تغییر پارامترهای آن مسیر باشد. اما باید درنظر داشت که اگر تنها قسمت child یک مسیر تغییر کند، دیگر محافظ canActivate مجددا اجرا نخواهد شد.
یک مثال: اگر کاربر در حال مشاهدهی صفحهی لیست محصولات باشد و بر روی لینک مشاهدهی یک محصول کلیک کند، تنها قسمت child مسیر تغییر میکند. در این حالت canActivate مسیر اصلی دیگر اجرا نخواهد شد؛ اما تمام محافظهای canActivateChild مرتبط مجددا اجرا خواهند شد.
بررسی محافظ canDeactivate
محافظ canDeactivate پیش از ترک یک مسیر، فعالسازی و بررسی میشود. عموما از آن جهت بررسی وضعیت اطلاعات ذخیره نشده و اطلاع رسانی به کاربر، پیش از ترک مسیر جاری استفاده استفاده میگردد. این محافظ با هر تغییری در آدرس جاری مسیر، بررسی میشود. بدیهی است این تغییر صرفا درون یک برنامهی Angular معنا پیدا میکند و نه هدایت به سایتی دیگر.
در حال حاضر در مثال جاری این سری، اگر کاربر، تغییری را در صفحهی ویرایش اطلاعات ایجاد کند و بدون کلیک بر روی دکمهی Save به صفحهی دیگری مراجعه کند، این اطلاعات تغییر یافته، از دست خواهند رفت. برای رفع این مشکل میتوان محافظ canDeactivate ایی را برای آن طراحی کرد. به همین جهت دستور ذیل را اجرا کنید:
>ng g guard product/ProductEdit -m product/product.module
installing guard create src\app\product\product-edit.guard.spec.ts create src\app\product\product-edit.guard.ts update src\app\product\product.module.ts
امضای ابتدایی یک محافظ CanDeactivate به صورت ذیل است:
export class ProductEditGuard implements CanDeactivate<ProductEditComponent> { canDeactivate(component: ProductEditComponent): boolean {
اکنون این محافظ نیاز دارد تا بداند که آیا کامپوننت ویرایش محصولات، دارای اطلاعات ذخیره نشدهای هست یا خیر؟ چون کامپوننت ویرایش محصولات، به عنوان پارامتر به متد canDeactivate آن ارسال شدهاست، بنابراین میتواند به خواص و متدهای عمومی آن کلاس نیز دسترسی پیدا کند. به همین جهت تغییرات ذیل را به کامپوننت ویرایش محصولات در فایل src\app\product\product-edit\product-edit.component.ts اعمال میکنیم:
get product(): IProduct { return this.currentProduct; } set product(value: IProduct) { this.currentProduct = value; // Clone the object to retain a copy this.originalProduct = Object.assign({}, value); } get isDirty(): boolean { return JSON.stringify(this.originalProduct) !== JSON.stringify(this.currentProduct); }
برای اینکه این امر میسر شود، خاصیت product به حالت get/set دار تغییر یافتهاست تا بتوان کپی اولیهی محصول را جهت مقایسه، نگهداری کرد. استفاده از متد Object.assign سبب ایجاد یک کپی از شیء اولیه شده و به این صورت دو وهلهی غیرمشترک را خواهیم داشت. اگر value مستقیما به originalProduct انتساب داده میشد، در این حالت هر دوی currentProduct و originalProduct به یک شیء اشاره میکردند.
اکنون میتوان از این خاصیت جدید کامپوننت ویرایش محصولات، در محافظ ترک صفحهی آن استفاده کرد:
import { Injectable } from '@angular/core'; import { CanDeactivate } from '@angular/router'; import { ProductEditComponent } from './product-edit/product-edit.component'; @Injectable() export class ProductEditGuard implements CanDeactivate<ProductEditComponent> { canDeactivate(component: ProductEditComponent): boolean { if (component.isDirty) { let productName = component.product.productName || 'New Product'; return confirm(`Navigate away and lose all changes to ${productName}?`); } return true; } }
در آخر برای استفادهی از این محافظ جدید، باید آنرا به تنظیمات مسیریابی برنامه اضافه کنیم. به همین جهت به فایل src\app\product\product-routing.module.ts مراجعه کرده و این محافظ را به والد مسیریابی ویرایش یک محصول اضافه میکنیم:
import { ProductEditGuard } from './product-edit.guard'; const routes: Routes = [ { path: 'products', canActivate: [ AuthGuard ], children: [ { path: '', component: ProductListComponent }, { path: ':id', component: ProductDetailComponent, resolve: { product: ProductResolverService } }, { path: ':id/edit', component: ProductEditComponent, resolve: { product: ProductResolverService }, canDeactivate: [ ProductEditGuard ], children: [ { path: '', redirectTo: 'info', pathMatch: 'full' }, { path: 'info', component: ProductEditInfoComponent }, { path: 'tags', component: ProductEditTagsComponent } ] } ] } ];
برای آزمایش آن، به صفحهی ویرایش یکی از محصولات مراجعه کرده و تغییری را ایجاد کنید. سپس درخواست مشاهدهی صفحهی دیگری را با کلیک بر روی یکی از لینکهای منوی برنامه ارائه دهید. بلافاصله دیالوگ confirm ظاهر خواهد شد (تصویر فوق).
مشکل! در همین حالت بر روی دکمهی Ok کلیک کنید تا اطلاعات ذخیره نشده را از دست داده و به مسیر دیگری هدایت شویم. مجددا همین پروسه را تکرار کنید. اینبار اگر بر روی دکمهی Save کلیک کنید، باز هم دیالوگ confirm ظاهر میشود. علت اینجا است که شیء محصول اصلی و جاری، پس از ذخیره سازی به حالت اولیه بازگشت داده نشدهاند. برای این منظور متد reset را به کامپوننت ویرایش اطلاعات اضافه کرده:
reset(): void { this.dataIsValid = null; this.currentProduct = null; this.originalProduct = null; }
onSaveComplete(message?: string): void { if (message) { this.messageService.addMessage(message); } this.reset(); // Navigate back to the product list this.router.navigate(['/products']); }
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: angular-routing-lab-08.zip
برای اجرای آن فرض بر این است که پیشتر Angular CLI را نصب کردهاید. سپس از طریق خط فرمان به ریشهی پروژه وارد شده و دستور npm install را صادر کنید تا وابستگیهای آن دریافت و نصب شوند. در آخر با اجرای دستور ng s -o برنامه ساخته شده و در مرورگر پیش فرض سیستم نمایش داده خواهد شد.
- اگر از JWE استفاده نمیکنید، بهتر است اطلاعات حساسی مانند شماره تلفن کاربر (و شاید در مواردی حتی آیدی کاربر) را در بدنه توکن قرار ندهیم چرا که قابل خوانده شدن است (که در این صورت استفاده از Guid برای آیدی کاربر می تواند کمی مفید باشد چرا که حداقل آیدی بقیه کاربران قابل پیش بینی نمیباشد).
- توکن JWT هیچ امنیتی در برابر خوانده شدن ندارد؛ ولی به لطف امضای (signature) آن، در برابر تغییر محتوا، ایمن است؛ چرا که در صورت تغییر محتوای آن، دیگر مقدار hash محتوا با امضای آن همخوانی نداشته و عملا از اعتبار ساقط میگردد.
var secretKey = Encoding.UTF8.GetBytes("LongerThan-16Char-SecretKey"); // must be 16 character or longer var signingCredentials = new SigningCredentials(new SymmetricSecurityKey(secretKey), SecurityAlgorithms.HmacSha256Signature); var encryptionkey = Encoding.UTF8.GetBytes("16CharEncryptKey"); //must be 16 character var encryptingCredentials = new EncryptingCredentials(new SymmetricSecurityKey(encryptionkey), SecurityAlgorithms.Aes128KW, SecurityAlgorithms.Aes128CbcHmacSha256); var claims = new List<Claim> { new Claim(ClaimTypes.Name, "UserName"), //user.UserName new Claim(ClaimTypes.NameIdentifier, "123"), //user.Id }; var descriptor = new SecurityTokenDescriptor { Issuer = _siteSetting.JwtSettings.Issuer, Audience = _siteSetting.JwtSettings.Audience, IssuedAt = DateTime.Now, NotBefore = DateTime.Now.AddMinutes(_siteSetting.JwtSettings.NotBeforeMinutes), Expires = DateTime.Now.AddMinutes(_siteSetting.JwtSettings.ExpirationMinutes), SigningCredentials = signingCredentials, EncryptingCredentials = encryptingCredentials, Subject = new ClaimsIdentity(claims) }; var tokenHandler = new JwtSecurityTokenHandler(); var securityToken = tokenHandler.CreateToken(descriptor); string encryptedJwt = tokenHandler.WriteToken(securityToken);
در ادامه لازم است در مرحله اعتبار سنجی و رمزگشایی توکن در سمت سرور، کلید و الگوریتم لازم را به آن معرفی کنیم تا middleware مربوطه بتواند توکن دریافتی را رمزگشایی و سپس اعتبار سنجی کند. بدین منظور در متد ConfigureServices کلاس Startup.cs خواهیم داشت:
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { var secretkey = Encoding.UTF8.GetBytes("LongerThan-16Char-SecretKey"); var encryptionkey = Encoding.UTF8.GetBytes("16CharEncryptKey"); var validationParameters = new TokenValidationParameters { ClockSkew = TimeSpan.Zero, // default: 5 min RequireSignedTokens = true, ValidateIssuerSigningKey = true, IssuerSigningKey = new SymmetricSecurityKey(secretkey), RequireExpirationTime = true, ValidateLifetime = true, ValidateAudience = true, //default : false ValidAudience = "MyWebsite", ValidateIssuer = true, //default : false ValidIssuer = "MyWebsite", TokenDecryptionKey = new SymmetricSecurityKey(encryptionkey) }; options.RequireHttpsMetadata = false; options.SaveToken = true; options.TokenValidationParameters = validationParameters; });
کد بالا مانند کد فعال سازی احراز هویت توسط JWT معمولی در ASP.NET Core است؛ با این تفاوت که:
ابتدا آرایه بایتی همان کلید رمزنگاری (encryptionkey) که قبلا توکن را با آن رمزنگاری کرده بودیم، گرفته شده و سپس توسط مقداردهی خاصیت TokenDecryptionKey کلاس TokenValidationParameters، معرفی شده است.
ولی شاید این سؤال برایتان پیش آید که چرا الگوریتم رمزنگاری مشخص نشده است؟ پس سرور از کجا میفهمد که این توکن بر اساس چه الگوریتمی رمزنگاری شده است؟
دلیل آن این است که به هنگام تولید توکن، اسم الگوریتم مربوطه، داخل بخش header توکن نوشته میشود. اگر تصویر قبل را مشاهده کنید مقدار header توکن به شرح زیر است.
{ "alg": "A128KW", "enc": "A128CBC-HS256", "typ": "JWT" }
پس سرور بر اساس این قسمت از توکن (header)، که هیچگاه رمزنگاری نمیشود، میفهمد که توسط چه الگوریتمی باید توکن را رمزگشایی کند که در اینجا A128CBC-HS256 (اختصار AES-128-CBC و HMAC-SHA256) است.
مثال کامل و قابل اجرای این مطلب را میتوانید از این ریپازیتوری دریافت کنید.
NativeScript یک فریمورک متن باز برای ساخت برنامههای نیتیو موبایل با جاوا اسکریپت است.
Ionic فریمورک مبتنی بر Angular، به صورت متن باز برای توسعه نرم افزار تلفن همراه بکار میرود که باعث میشود برنامههای نیتیو و پیشرفته وب با تکنولوژیهای بالا ساخته شود.
React Native یک فریمورک جاوا اسکریپت برای ساخت برنامههای تلفن همراه نیتیو است که از فریمورک React استفاده میکند.
انتخاب یکی از این 3 گزینه در دنیای صنعت رو به رشد امروز دشوار است.
تم ادمین Angular 6 و Bootstrap 4
تم آنگولار راستچین و دارای 3 نوع تم به همراه ویژگیهای زیر
- Angular 6+ & Typescript
- Bootstrap 4+ & SCSS
- Responsive layout
- RTL support
- High resolution
- Flexibly configurable themes with hot-reload (2 themes included)
- Authentication module with multiple providers
- Lots of awesome features:
- Buttons
- Modals
- Popovers
- Icons
- Typography
- Animated searches
- Forms
- Tabs
- Notifications
- Tables
- Maps
- Charts
- Editors
And many more!
دریافت وابستگیهای سمت کاربر مباحث اعتبارسنجی
زمانیکه گزینهی ایجاد یک پروژهی جدید ASP.NET Core را در VS.NET انتخاب میکنیم، علاوه بر قالب empty آن، قالب دیگری به نام web application نیز در آن موجود است. با انتخاب این قالب، فایلی را به نام bower.json نیز با این محتوا مشاهده میکنید:
{ "name": "asp.net", "private": true, "dependencies": { "bootstrap": "3.3.6", "jquery": "2.2.0", "jquery-validation": "1.14.0", "jquery-validation-unobtrusive": "3.2.6" } }
این بستهها را پس از دریافت، در پوشهی bower_components خواهید یافت:
البته باید دقت داشت که استفاده از bower در اینجا الزامی نیست. اگر علاقمند بودید از npm و node.js استفاده کنید.
افزودن وابستگیهای سمت کاربر مباحث اعتبارسنجی و عمومی کردن آنها
پس از دریافت وابستگیهای مورد نیاز توسط bower، به فایل layout برنامه مراجعه کرده و سپس آنها را به ترتیب ذیل اضافه میکنیم:
<script src="~/bower_components/jquery/dist/jquery.min.js"></script> <script src="~/bower_components/jquery-validation/dist/jquery.validate.min.js"></script> <script src="~/bower_components/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"></script> @RenderSection("scripts", required: false) </body> </html>
// Serve wwwroot as root app.UseFileServer(); // Serve /bower_components as a separate root app.UseFileServer(new FileServerOptions { // Set root of file server FileProvider = new PhysicalFileProvider(Path.Combine(Directory.GetCurrentDirectory(), "bower_components")), // Only react to requests that match this path RequestPath = "/bower_components", // Don't expose file system EnableDirectoryBrowsing = false });
اگر RequestPath را به مسیر دیگری تنظیم کردید، نیاز است ابتدای سه مدخل ذکر شده را بر این اساس اصلاح کنید، تا فایلها توسط وب سرور قابل ارائه شوند.
استفاده از CDN برای توزیع اسکریپتهای اعتبارسنجی مورد نیاز
در مورد environment tag helper در مطلب «ارتقاء به ASP.NET Core 1.0 - قسمت 12 - معرفی Tag Helpers» پیشتر بحث شد. در اینجا نیز میتوان برای مثال در حال توسعه، از اسکریپتهای محلی
<environment name="Development"> <script src="~/bower_components/jquery/dist/jquery.min.js"></script> <script src="~/bower_components/jquery-validation/dist/jquery.validate.min.js"></script> <script src="~/bower_components/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"></script> </environment>
<environment names="Staging, Production"> <script src="https://ajax.aspnetcdn.com/ajax/jquery/jquery-2.1.4.min.js" asp-fallback-src="/bower_components/jquery/dist/jquery.min.js" asp-fallback-test="window.jQuery"> </script> <script src="https://ajax.aspnetcdn.com/ajax/jquery.validate/1.14.0/jquery.validate.min.js" asp-fallback-src="bower_components/jquery-validation/dist/jquery.validate.min.js" asp-fallback-test="window.jQuery && window.jQuery.validator"> </script> <script src="https://ajax.aspnetcdn.com/ajax/mvc/5.2.3/jquery.validate.unobtrusive.min.js" asp-fallback-src="/bower_components/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js" asp-fallback-test="window.jQuery && window.jQuery.validator && window.jQuery.validator.unobtrusive"> </script> </environment>
روش عملکرد fallback هم به این صورت است که بررسی میشود آیا عبارت ذکر شدهی در قسمت asp-fallback-test قابل اجرا است یا خیر؟ اگر خیر، یعنی CDN قابل دسترسی نیست و از نمونهی محلی استفاده میکند.
خلاصهای از Tag helpers اعتبارسنجی
در جدول «راهنمای تبدیل HTML Helpers به Tag Helpers» مطلب «ارتقاء به ASP.NET Core 1.0 - قسمت 12 - معرفی Tag Helpers»، معادلهای HTML Helpers مباحث اعتبارسنجی را نیز ملاحظه کردید. خلاصهی تکمیلی آن به صورت ذیل است:
ValidationSummary.All سبب نمایش خطاهای اعتبارسنجی خواص و همچنین کل مدل میشود:
@Html.ValidationSummary(false)
<div asp-validation-summary="All"></div>
ValidationSummary.ModelOnly صرفا خطاهای اعتبارسنجی در سطح مدل را نمایش میدهد:
@Html.ValidationSummary(true)
<div asp-validation-summary="ModelOnly"></div>
و برای تعیین نمایش خطاهای اعتبارسنجی یک خاصیت از مدل:
@Html.ValidationMessageFor(m => m.UserName, "", new { @class = "text-danger" })
<span asp-validation-for="UserName" class="text-danger"></span>