نظرات مطالب
نمایش خطاهای اعتبارسنجی سمت سرور ASP.NET Core در برنامه‌های Angular
نکته تکمیلی
برای خلوت کردن قالب‌های مرتبط با فرم‌ها برای نمایش خطاهای اعتبارسنجی و همچنین برای جلوگیری از تکرار، می‌توان کامپوننت ValidationMessage را به شکل زیر نیز توسعه داد:
import { Component, Input, OnInit } from '@angular/core';
import { AbstractControl } from '@angular/forms';

@Component({
  selector: 'validation-message',
  template: `
    <ng-container *ngIf="control.invalid && control.touched">
      {{ message }}
    </ng-container>
  `
})
export class ValidationMessageComponent implements OnInit {
  @Input() control: AbstractControl;
  @Input() fieldDisplayName: string;
  @Input() rules: { [key: string]: string };

  get message(): string {
    return this.control.hasError('required')
      ? `${this.fieldDisplayName} را وارد نمائید.`
      : this.control.hasError('pattern')
      ? `${this.fieldDisplayName} را به شکل صحیح وارد نمائید.`
      : this.control.hasError('email')
      ? `${this.fieldDisplayName} را به شکل صحیح وارد نمائید.`
      : this.control.hasError('minlength')
      ? `${this.fieldDisplayName} باید بیشتر از  ${
          this.control.errors.minlength.requiredLength
        } کاراکتر باشد.`
      : this.control.hasError('maxlength')
      ? `${this.fieldDisplayName} باید کمتر از  ${
          this.control.errors.maxlength.requiredLength
        } کاراکتر باشد.`
      : this.control.hasError('min')
      ? `${this.fieldDisplayName} باید بیشتر از  ${
          this.control.errors.min.requiredLength
        } باشد.`
      : this.control.hasError('max')
      ? `${this.fieldDisplayName} باید کمتر از  ${
          this.control.errors.max.requiredLength
        } باشد.`
      : this.hasRule()
      ? this.findRule()
      : this.control.hasError('model')
      ? `${this.control.errors.model.messages[0]}`
      : '';
  }
  constructor() {}

  private hasRule() {
    return (
      this.rules &&
      Object.keys(this.control.errors).some(ruleKey =>
        this.rules[ruleKey] ? true : false
      )
    );
  }

  private findRule(): string {
    let message = '';
    Object.keys(this.control.errors).forEach(ruleKey => {
      if (this.rules[ruleKey]) {
        message += `${this.rules[ruleKey]} `;
      }
    });

    return message;
  }

  ngOnInit(): void {}
}

این کامپوننت، کنترل مورد نظر، یک نام نمایشی برای فیلد متناظر و یک شیء تحت عنوان rules را دریافت می‌کند. در بدنه پراپرتی message، به ترتیب اولویت validator، بررسی انجام شده و پیغام مناسب بازگشت داده خواهد شد. همچنین خطاهای سمت سروری هم که با کد "model" به لیست خطاهای کنترل اضافه شده اند نیز مورد بررسی قرار گرفته شده اند. 
 در اینجا اگر لازم باشد با یکسری قواعد سفارشی هم به صورت یکپارچه با اعتبارسنج‌های پیش فرض رفتار کنیم و از یک مسیر این پیغام‌ها نمایش داده شوند، می‌توان از خصوصیت rules بهره برد. به عنوان مثال:
 <mat-error *ngIf="form.controls['userName'].invalid && form.controls['userName'].touched" 
 class="mat-text-warn">
    <validation-message
        [control]="form.controls['userName']"
        fieldDisplayName="نام کاربری"
        [rules]="{rule1:'پیغام متناظر با rule1'}">
    </validation-message>
</mat-error>

به همراه یک Validator سفارشی
this.form = this.formBuilder.group({
      userName: [
        '',
        [Validators.required, UserNameValidators.rule1)]
      ],
      password: ['', Validators.required],
      rememberMe: [false]
    });

export class UserNameValidators{
   static rule1(control: AbstractControl) {
        if (control.value.indexOf(' ') >= 0) {
            return { rule1: true };
        }
        return null;
    }
}

مطالب
مبانی TypeScript؛ متغیرها و نوع‌ها
روش‌های مختلف تعریف متغیرها در TypeScript

تمام توسعه دهنده‌های JavaScript با واژه‌ی کلیدی var آشنایی دارند؛ اما TypeScript واژه‌های کلیدی let و const را نیز اضافه کرده‌است (که جزئی از ES 6 نیز می‌باشند). تفاوت مهم بین var و let، در میدان دید متغیرهای تعریف شده‌ی توسط آن‌ها خلاصه می‌شود. پیشتر در سری مباحث بررسی ES 6، مطلب «متغیرها در ES 6» را نیز بررسی کردیم که در TypeScript نیز صادق می‌باشند؛ با این تفاوت که TypeScript را می‌توان به ES 5 نیز کامپایل کرد و بدون مشکل با تمام مرورگرهای موجود، اجرا نمود.
- متغیرهایی که با var تعریف می‌شوند، به صورت سراسری در متدی که تعریف شده‌اند، قابل دسترسی هستند؛ حتی اگر 5 سطح داخل ifهای تو در تو تعریف شده باشند. اما let و const تنها در block و قطعه‌ای که معرفی شده‌اند، معتبر بوده و خارج از آن تعریف نشده‌اند. در اینجا یک block توسط {} معرفی می‌شود.
- متغیرهای از نوع var به دلیل مفهومی به نام hoisting توسط runtime جاوا اسکریپت، به بالاترین سطح متد منتقل می‌شوند. به همین دلیل عمق تعریف آن‌ها، اثری در دسترسی به این متغیرها ندارد. اما hoisting در مورد let و const اعمال نمی‌شود.
- متغیرهای var را می‌توان چندبار مجددا تعریف کرد (هرچند این روش توصیه نمی‌شود؛ اما مجاز است). یک چنین تعریف مجددی با متغیرهای از نوع let و const مجاز نیست.
برای توضیحات بیشتر به مثال ذیل دقت کنید:
function ScopeTest() {
    if (true) {
        var foo = 'use anywhere';
        let bar = 'use in this block';
    }
    console.log(foo); // works!
    console.log(bar); // error!
}
در اینجا داخل قطعه‌ی if تعریف شده، یک متغیر، از نوع var و متغیر دیگری از نوع let تعریف شده‌است. خارج از بدنه‌ی این متد، متغیر foo هنوز قابل دسترسی است، اما متغیر bar خیر.


نوع‌های پایه‌ی TypeScript

نوع‌های پایه‌ی TypeScript شامل موارد ذیل هستند:
Boolean: برای ذخیره سازی true یا false.
let isDone: boolean = false;
Number: معرف مقادیر عددی اعشاری هستند.
let decimal: number = 6;
let hex: number = 0xf00d;
let binary: number = 0b1010;
let octal: number = 0o744;
String: مقادیر رشته‌ای را ذخیره می‌کند.
let name: string = "bob";
name = 'smith';
Array: روش‌های متفاوتی برای تعریف آرایه‌ها در TypeScript وجود دارند که در ادامه آن‌ها را بیشتر بررسی خواهیم کرد.
let list: number[] = [1, 2, 3];
Enum: نوع‌های شمارشی؛ جهت دادن نام‌هایی بهتر به مجموعه‌ی مشخصی از مقادیر.
enum Color {Red, Green, Blue};
let c: Color = Color.Green;
Any: به این معنا که یک متغیر می‌تواند به هر نوع دلخواهی اشاره کند. هدف از وجود این نوع، امکان استفاده‌ی از کتابخانه‌های فعلی جاوا اسکریپتی است که نوعی برای متغیرهای آن‌ها تعریف نشده‌اند و در اساس هر نوعی می‌توانند باشند. برای مثال در جاوا اسکریپت مجاز است در سطر اول متغیری را تعریف کرد و در سطر دوم به آن یک رشته و در سطر سوم به آن یک عدد را انتساب داد.
let notSure: any = 4;
notSure = "maybe a string instead";
notSure = false; // okay, definitely a boolean
Void: به معنای فقدان یک نوع است. برای مثال مشخص کردن اینکه متدی، خروجی ندارد.
 function warnUser(): void {
        alert("This is my warning message");
}

یک نکته: قابلیت Template string در ES 6 نیز در TypeScript پشتیبانی می‌شود.



مفهوم Type Inference در TypeScript

در TypeScript الزاما نیازی نیست تا نوع متغیری را صریحا مشخص کرد. در اینجا اگر نوع متغیری را در ابتدای کار تعریف نکنید، نوع آن در اولین باری که مقدار دهی می‌شود، مشخص خواهد شد که به آن Type Inference نیز می‌گویند.
let myString= 'this is a string';
myString= 42; // error!
در مثال فوق، نوع متغیر myString در زمان تعریف آن مشخص نشده‌است؛ اما با یک رشته، مقدار دهی و آغاز شده‌است. بر این اساس، TypeScript نوع این متغیر را رشته‌ای در نظر می‌گیرد و در سطر بعدی، از انتساب یک مقدار عددی به آن جلوگیری خواهد کرد.
و یا در مثال ذیل، نوع خروجی متد ReturnNumber به صورت صریح مشخص نشده‌است:
function ReturnNumber() {
   return 42;
}
let anotherString = 'this is also a string';
anotherString = ReturnNumber(); // error!
اما با توجه به اینکه خروجی آن عددی است، نوع آن نیز عددی درنظر گرفته شده و از انتساب آن به یک متغیر رشته‌ای جلوگیری می‌شود.


تعریف صریح نوع‌ها در TypeScript با استفاده از Type Annotations

برای لغو Type Inference و تعیین صریح نوع‌ها در TypeScript می‌توان به صورت زیر عمل کرد:
let myString : string = 'this is a string';
myString = 42; // error!

function ReturnNumber() : number {
   return 42;
}
let anotherString : string = 'this is also a string';
anotherString = ReturnNumber(); // error!
با قراردادن یک کولن پس از نام متغیر و سپس تعریف نوع داده‌ی مدنظر، می‌توان نوع‌ها را در TypeScript به صورت صریحی تعریف کرد. در مورد متدها نیز به همین صورت است. کولن پس از پایان بسته شدن پرانترها قرار می‌گیرد و سپس نوع بازگشتی متد مشخص می‌گردد.


نوع‌های شمارشی در TypeScript

Enums در بسیاری از زبان‌های برنامه نویسی متداول وجود دارند و هدف از آن‌ها، دادن نام‌هایی بهتر و مشخص، به مجموعه‌ای از مقادیر است:
 enum Category { Biography, Poetry, Fiction }; // 0, 1, 2
در مثال فوق یک enum جهت تعریف گروه‌های کتاب‌ها تعریف شده‌است. در اینجا بجای استفاده از مقادیر نامفهوم 0 تا 2، نام‌هایی مشخص و بهتر، جهت معرفی آن‌ها ارائه شده‌اند. به این ترتیب می‌توان در نهایت به کدهایی خواناتر رسید.
اگر می‌خواهید این مقادیر با اعداد دیگری شروع شوند (بجای صفر پیش فرض)، می‌توان مقدار اولین نام را به صورت صریحی مشخص کرد:
 enum Category { Biography = 1, Poetry, Fiction }; // 1, 2, 3
و یا الزامی به استفاده از مقادیر افزایش یابنده‌ای در اینجا نیست. در صورت نیاز می‌توان مقدار هر المان را جداگانه تعریف کرد:
 enum Category{ Biography = 5, Poetry = 8, Fiction = 9 }; // 5, 8, 9
پس از اینکه نوع enum تعریف شده، استفاده از آن همانند سایر نوع‌های پایه‌ی TypeScript است:
 let favoriteCategory: Category = Category.Biography;
در اینجا متغیر favoriteCategory از نوع enum گروه کتاب‌ها تعیین شده‌است؛ با مقدار اولیه‌ی Category.Biography.
در این حالت اگر مقدار favoriteCategory را چاپ کنیم، خروجی عددی 5 نمایش داده می‌شود:
 console.log(favoriteCategory); // 5
اگر نیاز به بازگشت مقدار رشته‌ای این آیتم است، می‌توان از ایندکس‌های یک enum استفاده کرد:
let categoryString= Category[favoriteCategory]; // Biography


آرایه‌ها در TypeScript

در حالت عمومی، آرایه‌ها در TypeScript همانند جاوا اسکریپت تعریف می‌شوند؛ البته به همراه تعدادی استثناء. در TypeScript سه روش برای تعریف آرایه‌ها وجود دارند:
الف) در مثال زیر آرایه‌ای از رشته‌ها تعریف شده‌است. در اینجا نوع آرایه به همراه [] مشخص می‌شود:
let strArray1: string[] = ['here', 'are', 'strings'];
ب) روش دوم تعریف آرایه‌ها در TypeScript با استفاده از مفهوم Generics است که در بحثی جداگانه به آن خواهیم پرداخت. در اینجا از واژه‌ی کلیدی Array به همراه مشخص سازی نوع آن در داخل <> استفاده شده‌است:
let strArray2: Array<string> = ['more', 'strings', 'here'];
ج) اگر نیاز به آرایه‌هایی شبیه به جاوا اسکریپت دارید که می‌توانند حاوی انواع و اقسام المان‌هایی با نوع‌های مختلف باشند، نوع آرایه را []any تعریف کنید:
let anyArray: any[] = [42, true, 'banana'];


نوع Tuples در TypeScript

Tuples در TypeScript نوع خاصی از آرایه‌ها هستند که نوع مقادیر اعضای آن‌ها به صورت صریح مشخص می‌شوند:
 let myTuple: [number, string] = [25, 'truck'];
برای مثال در اینجا نوع متغیر myTuple به صورت آرایه‌ای از دو عنصر عددی و رشته‌ای تعریف شده‌است.
اکنون برای دسترسی به مقادیر این المان‌ها، همانند کار با آرایه‌های معمولی، از ایندکس‌های آرایه‌ی تعریف شده استفاده می‌شود:
 let firstElement= myTuple[0]; // 25
let secondElement= myTuple[1]; // truck

یک نکته: می‌توان به آرایه‌ی تعریف شده، عناصر جدیدی را نیز افزود؛ با این شرط که نوع آن‌ها، یکی از نوع‌های مشخص شده‌ی در تعریف Tuple باشند:
 // other elements can have numbers or strings
myTuple[2] = 100;
myTuple[2] = 'this works!';


مفهوم Type assertions در TypeScript

حتما با مفهوم cast و تبدیل نوع‌های مختلف به یکدیگر، در زبان‌های دیگر برنامه نویسی آشنا هستید. در TypeScript نیز این مفهوم تحت عنوان Type assertions پشتیبانی می‌شود و دو روش برای تعریف آن وجود دارد:
الف) تعریف cast توسط angle-bracket syntax که در آن نوع مدنظر داخل یک <> قرار می‌گیرد:
 let someValue: any = "this is a string";
let strLength: number = (<string>someValue).length;
در اینجا نوع نامشخص any به string تبدیل شده و سپس امکان دسترسی به خاصیت طول آن که تحت کنترل کامپایلر است و قابل انتساب به یک مقدار عددی، میسر شده‌است.
ب) تعریف cast توسط as syntax به نحو ذیل:
 let someValue: any = "this is a string";
let strLength: number = (someValue as string).length;
هر دو حالت تعریف شده معادل هستند. فقط باید دقت داشت در حین کار با ReactJS توسط TypeScript، فقط حالت as syntax پشتیبانی می‌شود.
نظرات مطالب
غیرمعتبر شدن کوکی‌های برنامه‌های ASP.NET Core هاست شده‌ی در IIS پس از ری‌استارت آن
- services.AddDataProtection یعنی همان مقدمه‌ی بحث؛ یا ذخیره سازی کلیدها در حافظه به صورت پیش‌فرض. مابقی بحث جهت دائمی کردن این کلیدها است. البته دائمی کردن هم طول عمری دارد.
- در سرورهای اشتراکی یا از روش «یک نکته‌ی تکمیلی: روش ذخیره سازی کلید موقتی تولید شده در بانک اطلاعاتی بجای حافظه‌ی سرور » استفاده کنید، یا با هاست تماس بگیرید و تنظیم گزینه‌ی 2 یا همان Load user profile به true را به آن‌ها اعلام کنید (چون تنظیمات برنامه‌های ASP.NET Core با نگارش‌های قبلی یکی نیست؛ این یک مورد را هم بهتر است به لیست تنظیمات اولیه‌ی برنامه اضافه کنند).
- در حالت سوم، ذکر Certificate برای رمزنگاری اطلاعات ضروری است؛ در غیراینصورت این کلیدها به صورت معمولی و واضح ذخیره خواهند شد.
نظرات مطالب
Cookie - قسمت اول
سلام
چند تا سوال:
1-چرا expiration مربوط به کوکی رو مرورگر در مراجعات بعدی به سرور نمیفرسته؟اگه لازمش داشته باشیم چه جور باید بدستش بیاریم؟
2-اگه زمان expire مربوط به کوکی برسه کی مسئولیت نابودی cookie رو داره؟
3-مسیر دخیره سازی کوکی‌ها کجا هست؟چه زمانی که IsPersistent برابر true یا false باشه(هر چی میگردم پیداشون نمیکنم)؟
البته مورد 3 رو در استفاده formsauthentication بررسی کردم طبق همون نامی که توی تگ forms توی web.config تنظیم شده.
نظرات مطالب
ASP.NET MVC #18
<location path="content">    
   <system.web>
      <authorization>     
         <allow users="*" />
      </authorization>    
   </system.web>  
</location>


<authentication mode="forms">
      <forms  loginurl="~/account/logon" slidingexpiration="true" timeout="2"/>
</authentication>    
<authorization>
    <!--<allow roles="?"/>-->      
  <deny users="?"/>
</authorization>

سلام آقای نصیری
ممنون از مطالب خوبتون
من تو این قسمت مشکل دارم تا اینجا درست کار میکنه که کسانی که authenticate نکردن برن به صفحه لاگین اما css‌ها رو با اینکه location هم گذاشتم نمیاره
بالای system.web  هم می‌زنم  
نظرات مطالب
ASP.NET MVC #16

1- وقتی در web.config مقدار debug برابر با true هستش اداره خطا انجام نمیشه. آیا وقتی سایت publish شد و روی هاست قرار گرفت این مساله برطرف میشه یا حتما باید debug="false" رو به خاطر داشته باشیم؟

2- با وجود اینکه خطا صادر میشه اما رویداد application_error فراخوانی نمیشه. راه حل هایی برای این مساله گفته شده اما جواب نداد. ضمن اینکه یک فیلتر سفارشی با توسعه iexceptionfilter هم ایجاد و تغییرات لازم در global.asax  انجام شد اما onexception در اونجا هم فراخوانی نمی‌شد.

3- آیا راهی وجود داره که برای هر area بشه مسیر اختصاصی برای خطاهای 403و 404 معرفی کرد؟ چون در بخش customerrors یک مسیر کلی داده میشه

4- این ادیتوری که شما اینجا استفاده کردید خیلی سبک و قویه. اگه میشه  معرفیش کنید!

نظرات مطالب
کامپایل پویای کد در دات نت
1- احتمالا برای اجرا از کلاس Process مربوط به فضای نام System.Diagnostics کمک می‌گیرد. اگر اینطور است باید خاصیت process.StartInfo.CreateNoWindow به true تنظیم شود.
2- متد parameters.EmbeddedResources.Add هم موجود است. کمی در مورد آن تحقیق کنید.
3- سرعت این روش فقط در حد زمان انجام کامپایل کامل، کند است؛ مابقی آن تفاوتی با اجرای یک برنامه واقعی ندارد. ضمنا محدودیتی هم من ندیدم. محدودیت‌های آن همان محدودیت‌های برای مثال کامپایلر سی شارپ است. مثلا یک سطر نباید از 16777214 کاراکتر بیشتر باشد و امثال آن.
ضمنا استفاده از + هنگام چسباندن رشته‌ها به هم در حجم کم تاثیر آنچنانی روی کارآیی ندارد. ولی اگر تعداد زیاد است بهتر است از StringBuilder استفاده شود.
نظرات مطالب
مروری بر چند تجربه‌ی کاری با SQLite
چند سال بعد ...!

2- اگر از EF-Core استفاده می‌کنید، رشته‌ی اتصالی نهایی آن (که در پشت صحنه تنظیم می‌شود) به همراه تنظیم فعالسازی کلیدهای خارجی نیز هست و نیازی نیست تا تنظیم foreign keys=true به آن اضافه شود.
3- در بانک‌های اطلاعاتی جدید SQLite، حالت WAL فعال است و این مورد دسترسی همزمان به آن‌را بدون بروز مشکل قفل بودن بانک اطلاعاتی میسر می‌کند.
مطالب
افزونه نویسی برای مرورگرها : قسمت دوم : فایرفاکس
در مقاله پیشین، افزونه نویسی برای فایرفاکس را آغاز و مسائل مربوط به رابط‌های کاربری را بررسی کردیم. در این قسمت که قسمت پایانی افزونه نویسی برای فایرفاکس است، به مباحث پردازشی و دیگر خصوصیت‌ها می‌پردازیم.
اولین موردی که باید برای برنامه‌ی ما در نظر گرفت، ذخیره و بازیابی مقادیر است که باید روی پنجره‌ی popup.html اعمال گردد و همچنین مقداردهی مقادیر پیش فرض برنامه بعد از نصب افزونه اعمال شود. برای ذخیره‌ی مقادیر، طبق نوشته موجود در راهنمای موزیلا، از روش زیر بهره برده و می‌توان مقادیر زیر را به راحتی در آن‌ها ذخیره کرد:
var ss = require("sdk/simple-storage");
ss.storage.myArray = [1, 1, 2, 3, 5, 8, 13];
ss.storage.myBoolean = true;
ss.storage.myNull = null;
ss.storage.myNumber = 3.1337;
ss.storage.myObject = { a: "foo", b: { c: true }, d: null };
ss.storage.myString = "O frabjous day!";
برای خواندن موارد ذخیره شده هم که مشخصا نوشتن اسم property کفایت می‌کند و برای حذف مقادیر نیز به راحتی از عبارت delete در جلوی پراپرتی استفاده کنید:
delete ss.storage.value;
برای ذخیره مقادیر پیش فرض اولین، کاری که می‌کنیم اسم متغیرها را چک می‌کنیم. اگر مخالف null بود، یعنی قبلا ست شده‌اند؛ ولی اگر null شد، عمل ذخیره سازی اولیه را انجام می‌دهیم:
if (!ss.storage.Variables)
 {
 ss.storage.Variables=[];
 ss.storage.Variables.push(true);
 ss.storage.Variables.push(false);
 ss.storage.Variables.push(false);
 ss.storage.Variables.push(false);
 }

if (!ss.storage.interval)
ss.storage.interval=1;

 if (!ss.storage.DateVariables)
 {
 var now=String(new Date());
 ss.storage.DateVariables=[];
 ss.storage.DateVariables.push(now);
 ss.storage.DateVariables.push(now);
 ss.storage.DateVariables.push(now);
 ss.storage.DateVariables.push(now);
 }

برای ذخیره مقادیر popup.html، به طور مستقیم نمی‌توانیم از کدهای بالا در جاوااسکریپت استفاده کنیم. مجبور هستیم که یک پل ارتباطی بین فایل main.js و فایل جاوااسکریپت داشته باشیم. در مقاله پیشین در مورد postmessage که ارتباطی از/به محتوا یا فایل جاوااسکریپت به/از main.js برقرار می‌کرد، صحبت کردیم و در این قسمت راه حل بهتری را مورد استفاده قرار می‌دهیم. برای ایجاد چنین ارتباطی، آن هم به صورت دو طرفه از port استفاده می‌کنیم. دستور پورت در اکثر اشیایی که ایجاد می‌کنید وجود دارد ولی باز هم همیشه قبل از استفاده، از مستندات موزیلا حتما استفاده کنید تا مطمئن شوید دسترسی به شیء پورت در همه‌ی اشیا وجود دارد. ولی به صورت کلی تا آنجایی که من دیدم، در همه‌ی اشیا قرار دارد. از کد port.emit برای ارسال مقادیر به سمت فایل اسکریپت یا حتی بالعکس مورد استفاده قرار می‌گیرد و port.on هم یک شنونده برای آن است. شکل زیر به خوبی این مبحث را نشان می‌دهد.
addon process در شکل بالا همان فایل main.js هست که کد اصلی addon داخل آن است و content process نیز محتوای اسکریپت است و حالا میتواند با استفاده از خاصیت contentscrip که به صورت رشته ای اعمال شده باشد یا اینکه با استفاده از خاصیت contentscriptfile، در یک یا چند فایل js استفاده نماید:
contentScriptFile: self.data.url("jquery.min.js")
contentScriptFile: [self.data.url("jquery.min.js"),self.data.url("const.js"),self.data.url("popup.js")]

از شیء port به صورت عملی استفاده می‌کنیم. کد  main.js را به صورت زیر تغییر دادیم:
function handleChange(state) {
  if (state.checked) {
    panel.show({
      position: button
    });

 var v1=[],v2;
 if (ss.storage.Variables)
v1=ss.storage.Variables;

if (ss.storage.interval)
v2=ss.storage.interval;

panel.port.emit("vars",v1,v2);
  }
}
panel.port.on("vars", function (vars,interval) {
  ss.storage.Variables=vars;
  ss.storage.interval=interval;
});
در شماره پیشین گفتیم که رویداد handlechange وظیفه نمایش پنل را دارد، ولی الان به غیر آن چند سطر، کد دیگری را هم اضافه کردیم تا موقعی که پنل باز می‌شود، تنظیمات قبلی را که ذخیره کرده‌ایم روی صفحه نمایش داده شوند. با استفاده از شیء port.emit محتواهای دریافت شده را به سمت فایل اسکریپت ارسال می‌کنیم تا تنظیمات ذخیره شده را برای نمایش اعمال کند. پارامتر اول رشته vars نام پیام رسان شما خواهد بود و در فایل مقصد هم تنها به پیامی با این نام گوش داده خواهد شد. این خصوصیت زمانی سودمندی خود را نشان می‌دهد که بخواهید در زمینه‌های مختلف، از چندین پیام رسان به سمت یک مقصد استفاده کنید. شیء panel.port.on هم برای گوش دادن به متغیرهایی است که از آن سمت برای ما ارسال می‌شود و از آن برای ذخیره‌ی مواردی استفاده میگردد که کاربر از آن سمت برای ما ارسال می‌کند. پس ما در این مرحله، یک ارتباطه کاملا دو طرفه داریم.
کد  فایل popup.js که به صورت تگ script در popup.html معرفی شده است:
$(document).ready(function () {
addon.port.on("vars",  function(vars,interval) {
 if (vars)
{
 $("#chkarticles").attr("checked", vars[0]);
$("#chkarticlescomments").attr("checked", vars[1]);
$("#chkshares").attr("checked", vars[2]);
$("#chksharescomments").attr("checked", vars[3]);
}

$("#interval").val(interval);
});   

    $("#btnsave").click(function() {

         var Vposts = $("#chkarticles").is(':checked');
         var VpostsComments = $("#chkarticlescomments").is(':checked');
         var  Vshares = $("#chkshares").is(':checked');
         var VsharesComments = $("#chksharescomments").is(':checked');
 var Vinterval = $("#interval").val() ;
 var Variables=[];
 Variables[0]=Vposts;
 Variables[1]=VpostsComments;
 Variables[2]=Vshares;
 Variables[3]=VsharesComments;
 interval=Vinterval;
 
 addon.port.emit("vars", Variables,Vinterval);
$("#messageboard").text( Messages.SettingsSaved);

    });
});
در همان ابتدای امر با استفاده از addon.port.on منتظر یک پیام رسان، به اسم vars می‌شود تا اطلاعات آن را دریافت کند که در اینجا بلافاصله بعد از نمایش پنل، اطلاعات برای آن ارسال شده و در صفحه، جایگذاری می‌کند. در قسمت رویداد کلیک دکمه ذخیره هم با استفاده از addon.port.emit اطلاعاتی را که کاربر به روز کرده است، به یک پیام رسان میدهیم تا برای آن سمت نیز ارسال کند تا در آن سمت، تنظیمات جدید ذخیره و جایگزین شوند.
نکته بسیار مهم: در کد بالا ما فایل جاوااسکریت را از طریق فایل popup.html معرفی کردیم، نه از طریق خصوصیت contentscriptfile. این نکته را همیشه به خاطر داشته باشید. فایل‌های js خود را تنها در دو حالت استفاده کنید:
  1. از طریق دادن رشته به خصوصیت contentScript و استفاده از self به جای addon
  2. معرفی فایل js داخل خود فایل html با تگ script که به درد اسکریپت‌های با کد زیاد می‌خورد.
اگر فایل شما شامل استفاده از کلمه‌ی کلیدی addon نمی‌شود، می‌توانید فایل js خود را از طریق contentScriptFile هم اعمال کنید.
فایل popup.html
<script src="jquery.min.js"></script> <!-- Including jQuery -->
<script type="text/javascript" src="const.js"></script> 
<script type="text/javascript" src="popup.js"></script>


خواندن فید RSS سایت
خواندن فید سایت توسط فایل Rssreader.js انجام می‌شود که تمام اسکریپت‌های مورد نیاز برای اجرای آن، توسط background.htm صدا زده شده است:
<script type="text/javascript" src="const.js"></script>
<script type="text/javascript" src="jquery.min.js"></script>
    <script type="text/javascript" src="https://www.google.com/jsapi"></script>
<script type="text/javascript" src="rssreader.js"></script>
تنها کاری که باید انجام دهیم اجرای این فایل به عنوان یک فرآیند پس زمینه است. در کروم ما عادت داشتیم برای این کار در فایل manifest.json از خصوصیت background استفاده کنیم، ولی از آنجا که خود فایل main.js یک فایل اسکریپتی است که در پس زمینه اجرا می‌شود، طبق منابع موجود در نت چنین چیزی وجود ندارد و این فرآیند را به خود فایل main.js مربوط می‌دانستند. ولی من با استفاده از page worker چنین خصوصیتی را پیاده سازی کردم. page worker وظیفه دارد تا یک آدرس یا فایلی را در یک تب پنهان و در پشت صحنه اجرا کرده و به شما اجازه‌ی استفاده از DOM آن بدهد. نحوه‌ی دسترسی به فایل background.htm توسط page worker به صورت زیر تعریف می‌شود:
pageWorker = require("sdk/page-worker");
  page= pageWorker.Page({
  contentScriptWhen: "ready",
  contentURL: self.data.url("./background.htm")
});
page.port.emit("vars",ss.storage.Variables,ss.storage.DateVariables,ss.storage.interval);
در فایل بالا شیء pageworker ساخته شد و درخواست یک پیج نهان را برای فایل background.htm در دایرکتوری data می‌کند. استفاده از گزینه‌ی contentScriptWhen  برای دسترسی به شیء addon در فایل‌های جاوااسکریپتی که استفاده می‌کنید ضروری است. در صورتی که حذف شود و نوشته نشود با خطای addon is not defined روبرو خواهید شد، چرا که هنوز این شیء شناسایی نشده است. در خط نهایی هم برای آن سمت یک پیام ارسال شده که حاوی مقادیر ذخیره شده می‌باشد.

فایل RSSReader.js
در اینجا هم مانند مطلبی که برای کروم گذاشتیم، خواندن فید، در یک دوره‌ی زمانی اتفاق می‌افتد. در کروم ما از chrome.alarm استفاده می‌کردیم، ولی در فایرفاکس از همان تایمرهای جاوااسکریپتی بهره میبریم. کد زیر را به فایلی به اسم rssreader.js اضافه می‌کنیم:
var variables=[];
var datevariables=[];
var period_time=60000;
var timer;
google.load("feeds", "1");

$(document).ready(function () {
 addon.port.on("vars",  function(vars,datevars,interval) {
 if (vars)
{
Variables=vars;
}
if (datevars)
{
datevariables=datevars;
}
if(interval)
period_time=interval*60000;
alarmManager();
});
});

function alarmManager()
{
timer = setInterval(Run,period_time);
}

function Run() {
if(Variables[0]){RssReader(Links.postUrl,0, Messages.PostsUpdated);}
if(Variables[1]){RssReader(Links.posts_commentsUrl,1,Messages.CommentsUpdated); }
if(Variables[2]){RssReader(Links.sharesUrl,2,Messages.SharesUpdated);}
if(Variables[3]){RssReader(Links.shares_CommentsUrl,3,Messages.SharesCommentsUpdated);}
}

function RssReader(URL,index,Message) {


            var feed = new google.feeds.Feed(URL);
            feed.setResultFormat(google.feeds.Feed.XML_FORMAT);
                    feed.load(function (result) {
if(result!=null)
{
var strRssUpdate = result.xmlDocument.firstChild.firstChild.childNodes[5].textContent;
var RssUpdate=new Date(strRssUpdate);
var lastupdate=new Date(datevariables[index]);
if(RssUpdate>lastupdate)
{
datevariables[index]=strRssUpdate;
addon.port.emit("notification",datevariables,Message);
}

}
                      });
        }
در خطوط بالا متغیرها تعریف و توابع گوگل بارگزاری می‌شوند. سپس توسط addon.port یک شنونده ایجاد شده، تا بتواند مقادیر ذخیره شده را بازیابی کند. این مقادیر شامل موارد زیر است:
  • چه بخش‌هایی از سایت باید بررسی شوند.
  • آخرین تاریخ تغییر هر کدام که در زمان نصب افزونه، تاریخ نصب افزونه می‌شود و با اولین به روز رسانی، تاریخ جدیدی جای آن را می‌گیرد.
  • دوره‌ی سیکل زمانی یا همان interval بر اساس دقیقه
پس از اینکه شنونده مقادیر را دریافت کرد، تابع alarmManager اجرا شده و یک تایمر ایجاد می‌کند. بر خلاف کروم که برای این کار api تدارک دیده بود، اینجا شما باید از تایمرهای خود جاوااسکریپت مانند SetTimeout یا SetInterval استفاده کنید. موقع دریافت interval یا period_time ما آن را در 60000 ضرب کردیم تا دقیقه تبدیل به میلی ثانیه شود؛ چرا که تایمر، زمان را بر حسب میلی ثانیه دریافت می‌کند. وظیفه تایمر این هست که در هر دوره‌ی زمانی تابع Run را اجرا کند.

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

RSSReader
این تابع را قبلا در این مقاله  توضیح دادیم. تنها تغییری که کرده است، بدنه‌ی شرط بررسی تاریخ است که در صورت موفقیت، تاریخ جدید، جایگزین تاریخ قبلی شده و یک پیام به فایل main.js ارسال می‌کند تا از آن درخواست ذخیره‌ی تاریخی جدید و هچنین ایجاد یک notification برای آگاه سازی کاربر کند. پس باز به فایل main.js رفته و شنونده آن را تعریف می‌کنیم:
page.port.on("notification",function(lastupdate,Message)
{
ss.storage.DateVariables=lastupdate;
Make_a_Notification(Message);
})
function Make_a_Notification(Message)
{
var notifications = require("sdk/notifications");
notifications.notify({
  title: "سایت به روز شد",
  text: Message,
  iconURL:self.data.url("./icon-64.png"),
  data:"https://www.dntips.ir",
  onClick: function (data) {
tabs.open(data);
  }
});
}
شنونده مورد نظر دو پارامتر تاریخ آخرین به روزرسانی را دریافت کرده و آن را جایگزین قبلی می‌کند و پیام را به تایع Make_a_Notification پاس میکند. پارامترهای ساخت نوتیفیکیشن به ترتیب شامل عنوان، متن پیام، آیکن و نهایتا data است. دیتا شامل آدرس سایت است. زمانیکه کاربر روی نوتیفیکیشن کلیک می‌کند، استفاده شده و یک تب جدید را با آدرس سایت باز می‌کنیم. به این ترتیب افزونه‌ی ما تکمیل می‌شود. برای اجرا و تست افزونه بر روی مرورگر فایرفاکس از دستور cfx run استفاده کنید.


البته این نکته قابل ذکر است که اگر کاربر طلاعات پنل را به روزرسانی کند، تا وقتی که مرورگر بسته نشده و دوباره باز نشود تغییری نمی‌کند؛ چرا که ما تنها در ابتدای امر مقادیر ذخیره شده را به RSSReader فرستاده و اگر کاربر آن‌ها را به روز کند، ارسال پیام دیگری توسط page worker صورت نمی‌گیرد. پس کد موجود در main.js را به صورت زیر ویرایش می‌کنیم:
  pageWorker = require("sdk/page-worker");
  page= pageWorker.Page({
  contentScriptWhen: "ready",
  contentURL: self.data.url("./background.htm")
});

function SendData()
{
page.port.emit("vars",ss.storage.Variables,ss.storage.DateVariables,ss.storage.interval);
}
SendData();
panel.port.on("vars", function (vars,interval) {
  ss.storage.Variables=vars;
  ss.storage.interval=interval;
  SendData();
});
در کد بالا ما خطی که به سمت rssreader.js پیام ارسال می‌کند را داخل یک تابع به اسم SendDate قرار دادیم و بعد از تشکیل page worker آن را صدا زدیم و کد آن دقیقا مانند قبل است؛ با این تفاوت که اینبار این تابع را در جای دیگری هم صدا میزنیم و آن زمانی است که برای پنل، پیام مقادیر جدید ارسال می‌شود که در آن پس از ذخیره موارد جدید تابع SendData را صدا می‌زنیم. پس موقع به روزرسانی هم مقادیر ارسال خواهند شد. مقادیر جدید به سمت rssreader.js رفته و تشکیل یک تایمر جدید را می‌دهند و البته چون قبلا تایمر ایجاد شده است، پس باید چند خطی را هم به فایل rssreader.js اضافه کنیم تا تایمر قبلی را نابود کرده و تایمر جدیدی را ایجاد کند:
var timer;

function alarmManager()
{
timer = setInterval(Run,period_time);
}

 addon.port.on("vars",  function(vars,datevars,interval) {
 if (vars)
{
Variables=vars;

}
if (datevars)
{
datevariables=datevars;
}
if(interval)
period_time=interval*60000;

if(timer!=null)
{
clearInterval(timer);
}

alarmManager();
});
در خط بالا متغیری به اسم timer ایجاد شده است که کد timer را در خود ذخیره می‌کند. پس موقع دریافت مقادیر بررسی میکنیم که اگر مقدار timer مخالف نال بود تایمر قبلی را با clearInterval از بین برده و تایمر جدیدی ایجاد کند. پس مشکل تایمری که از قبل موجود است نیز حل می‌گردد.
افزونه‌ی ما تکمیل شد. اجازه بدهید قبل از بستن بحث چندتا از موارد مهم موجود در sdk را نام ببریم:

Page Mod
page mod موقعی که کاربر آدرسی را مطابق با الگویی (pattern) که ما دادیم، باز کند یک اسکریپت را اجرا خواهد کرد:
var pageMod = require("sdk/page-mod");

pageMod.PageMod({
  include: "*.mozilla.org",
  contentScript: 'window.alert("Page matches ruleset");'
});
var data = require("sdk/self").data;
var pageMod = require("sdk/page-mod");

pageMod.PageMod({
  include: "*.mozilla.org",
  contentScriptFile: [data.url("jquery-1.7.min.js"),
                      data.url("my-script.js")]
});

پنل تنظیمات
موقعی که شما افزونه‌ای را در فایرفاکس اضافه می‌کنید، در پنلی که مدیریت افزونه‌ها قرار دارد می‌توانید در تنظیمات هر افزونه، تغییری ایجاد کنید. برای ساخت چنین صفحه‌ای از خصوصیت preferences در فایل package.json کمک می‌گیریم که مقادیر به صورت آرایه ای داخل آن قرار می‌گیرند. مثال زیر پنج کنترل را به بخش تنظیمات افزونه اضافه می‌کند که چهار کنترل اول چک باکس Checkbox هستند؛ چرا که خصوصیت type آنها به bool ست شده است و شامل یک نام و عنوان یا برچسب label و یک توضیح کوتاه است و مقدار پیش فرض آن با خصوصیت value مشخص شده است. آخرین کنترل هم یک کادر عددی است؛ چرا که خاصیت type آن با integer مقداردهی شده و مقدار پیش فرض آن 10 می‌باشد.
  "preferences": [{
    "description": "مطالب سایت",
    "type": "bool",
    "name": "post",
    "value": true,
    "title": "مطالب سایت"
},
{
    "description": "نظرات مطالب سایت",
    "type": "bool",
    "name": "postcomments",
    "value": false,
    "title": "نظرات مطالب سایت"
},
{
    "description": "اشتراک ها",
    "type": "bool",
    "name": "shares",
    "value": false,
    "title": "اشتراک ها"
},
{
    "description": "نظرات اشتراک ها",
    "type": "bool",
    "name": "sharescomments",
    "value": false,
    "title": "نظرات اشتراک ها"
},
    {
        "description": "دوره زمان برای بررسی سایت",
        "name": "interval",
        "type": "integer",
        "value": 10,
        "title": "دوره زمانی"
    }]

از آنجا که مقادیر بالا تنها مقادیر پیش فرض خودمان هست و اگر کاربر آن‌ها را تغییر دهد، در این صفحه هم باید اطلاعات تصحیح شوند، برای همین از کد زیر برای دسترسی به پنل تنظیمات و کنترل‌های موجود آن استفاده می‌کنیم. همانطور که می‌بینید کد مورد نظر را در یک تابع به نام Perf_Default_Value قرار دادیم و آن را در بدو اجرا صدا زدیم. پس کاربر اگر به پنل تنظمیات رجوع کند، می‌تواند تغییراتی را که قبلا داده است، ببیند. بنابراین اگر الان تغییری را ایجاد کند، تا باز شدن مجدد مرورگر چیزی نمایش داده نمیشود. برای همین دقیقا مانند تابع SendData این تابع را هم در کد شنود پنل panel اضافه میکنیم؛ تا اگر کاربر اطلاعات را از طریق روش قبلی تغییر داد، اطلاعات هم اینک به روز شوند.

function Perf_Default_Value()
{
var preferences = require("sdk/simple-prefs").prefs;

preferences.post = ss.storage.Variables[0];
preferences.postcomments = ss.storage.Variables[1];
preferences.shares = ss.storage.Variables[2];
preferences.sharescomments = ss.storage.Variables[3];
preferences["myinterval"] =parseInt(ss.storage.interval);

}
Perf_Default_Value();

panel.port.on("vars", function (vars,interval) {
  ss.storage.Variables=vars;
  ss.storage.interval=interval;
  SendData();
  Perf_Default_Value();
});
البته کاربر فقط برای دیدن اطلاعات بالا به این صفحه‌ی تنظیمات نمی‌آید؛ بلکه بیشتر برای تغییر آن‌ها می‌آید. پس باید به تغییر مقدار کنترل‌ها گوش فرا دهیم. برای گوش دادن به تغییر تنظیمات، برای موقعی که کاربر قسمتی از تنظیمات را ذخیره کرد، از کدهای زیر بهره می‌بریم: 
perf=require("sdk/simple-prefs");
var preferences = perf.prefs;
function onPrefChange(prefName) {

  switch(prefName)
  {
    case "post":
ss.storage.Variables[0]=preferences[prefName];
break;
case "postcomments":
ss.storage.Variables[1]=preferences[prefName];
break;
case "shares":
ss.storage.Variables[2]=preferences[prefName];
break;
case "sharescomments":
ss.storage.Variables[3]=preferences[prefName];
break;
case "myinterval":
ss.storage.interval=preferences[prefName];
break;
  }
}
//perf.on("post", onPrefChange);
//perf.on("postcomments", onPrefChange);

perf.on("", onPrefChange);
متد on دو پارامتر دارد: اولی، نام کنترل مورد نظر که با خصوصیت name تعریف کردیم و دومی هم تابع callback آن می‌باشد و در صورتی که پارامتر اول با "" مقداردهی شود، هر تغییری که در هر کنترلی رخ بدهد، تابع callback صدا زده می‌شود. از آنجا که نام کنترل‌ها به صورت string برگشت داده می‌شوند، برای دسترسی به مقادیر موجود در تنظیمات از همان روش داخل [""] بهره می‌گیریم. مقادیر را گرفته و داخل storage ذخیره می‌کنیم.

  اشکال زدایی Debug

یکی از روش‌های اشکال زدایی، استفاده از console.log هست که میتونید برای بازبینی مقادیر و وضعیت‌ها، از آن استفاده کنید که نتیجه‌ی آن داخل کنسول نمایش داده می‌شود و اگر هم دربرنامه خطایی رخ دهد، داخل کنسول به شما نمایش خواهد داد.
مطالب
Blazor 5x - قسمت دهم - مبانی Blazor - بخش 7 - مسیریابی
تا اینجا به صورت بسیار مختصری با نحوه‌ی مسیریابی برنامه‌های مبتنی بر Blazor توسط دایرکتیو page@ آشنا شدیم. برای مثال با داشتن تعریف زیر در ابتدای یک کامپوننت:
@page "/LearnRouting"

<h3>Learn Routing</h3>
این کامپوننت جدید، صرفنظر از محل قرارگیری فایل آن که برای مثال در پوشه‌ی Pages\LearnBlazor\LearnRouting.razor است، در مسیر https://localhost:5001/LearnRouting قابل دسترسی خواهد شد و برای تعریف مدخل منوی جدید آن، به کامپوننت Shared\NavMenu.razor مراجعه کرده و NavLink جدیدی را برای آن تعریف می‌کنیم:
<li class="nav-item px-3">
    <NavLink class="nav-link" href="LearnRouting">
        <span class="oi oi-list-rich" aria-hidden="true"></span> Learn Routing
    </NavLink>
</li>
در اینجا برچسب مدخل جدید تعریف شده، Learn Routing است و href لینک به آن، دقیقا به مسیریابی تعریف شده اشاره می‌کند.

یک نکته: مسیریابی‌های تعریف شده‌ی در Blazor، حساس به حروف کوچک و بزرگ نیستند.


امکان تعریف بیش از یک مسیریابی برای یک کامپوننت نیز وجود دارد

در کامپوننت‌های Blazor، محدودیتی از لحاظ تعداد بار تعریف دایرکتیو page@ وجود ندارد:
@page "/LearnRouting"
@page "/NewRouting"

<h3>Learn Routing</h3>
در این حالت می‌توان در حین تعریف یک مسیریابی جدید، مسیریابی قبلی را نیز حفظ کرد. در اینجا کامپوننت فوق، از طریق هر دو آدرس https://localhost:5001/LearnRouting و https://localhost:5001/NewRouting تعریف شده، قابل دسترسی‌است.


روش تعریف پارامترهای مسیریابی

تا اینجا اگر مسیر جدید https://localhost:5001/NewRouting/1/2 را درخواست کنیم چه اتفاقی رخ می‌دهد؟


در مورد نحوه‌ی تعریف قالب «یافت نشد» فوق، در قسمت دوم بیشتر بحث شد.
برای تعریف پارامترهای مسیریابی، می‌توان مسیریابی سومی را با پارامترهای مدنظر تعریف کرد که در مثال زیر، ذکر پارامتر دوم اختیاری است؛ چون سومین مسیریابی تعریف شده، امکان پردازش مسیرهایی با یک پارامتر را هم ممکن می‌کند:
@page "/LearnRouting"
@page "/NewRouting"
@page "/LearnRouting/{parameter1}"
@page "/LearnRouting/{parameter1}/{parameter2}"

<h3>Learn Routing</h3>

<p>Parameter1: @Parameter1</p>
<p>Parameter2: @Parameter2</p>

@code
{
    [Parameter]
    public string Parameter1 { set; get; }

    [Parameter]
    public string Parameter2 { set; get; }
}
سپس جهت دست‌یابی به مقادیر این پارامترها می‌توان در قسمت کدهای کامپوننت، از خواص عمومی مزین شده‌ی با ویژگی Parameter استفاده کرد. در اینجا هر خاصیت تعریف شده، باید هم نام پارامتر تعریف شده باشد (و این مورد نیز غیر حساس به حروف بزرگ و کوچک است).
پس از این تعاریف، مسیریابی مانند https://localhost:5001/LearnRouting/1 با یک پارامتر و یا https://localhost:5001/LearnRouting/1/2 که به همراه دو پارامتر است، قابل فراخوانی می‌شود.





روش تعریف لینک به سایر کامپوننت‌های Blazor

در ادامه کامپوننت جدید Pages\LearnBlazor\LearnAdvancedRouting.razor را اضافه می‌کنیم؛ با این محتوای آغازین:
@page "/LearnAdvancedRouting"

<h3>Learn Advanced Routing</h3>
در اینجا قصد نداریم لینک به این کامپوننت را به منوی اصلی برنامه اضافه کنیم؛ بلکه می‌خواهیم از طریق همان کامپوننت LearnRouting.razor ابتدای بحث، این مسیریابی را برقرار کنیم. برای اینکار یا می‌توان از یک anchor tag استاندارد استفاده کرد و یا همانند کامپوننت Shared\NavMenu.razor، از کامپوننت NavLink استفاده نمود. NavLink‌ها نیز همانند anchor tag‌های استاندارد HTML هستند، با این تفاوت که این کامپوننت، افزودن کلاس active مخصوص بوت استرپ را هم بر اساس فعال بودن مسیریابی مرتبط به آن، انجام می‌دهد ("class="nav-link active). به همین علت است که اگر گزینه‌ی منوی خاصی را انتخاب کنیم، این گزینه با رنگ متمایزی نشان داده می‌شود:


بنابراین یک روش تعریف لینک به کامپوننتی دیگر، استفاده از کامپوننت NavLink است که href آن به مسیریابی مقصد اشاره می‌کند:
<NavLink class="btn btn-secondary" href="LearnAdvancedRouting">
    <span class="oi oi-list-rich" aria-hidden="true"></span> Learn Advanced Routing
</NavLink>


ارسال کوئری استرینگ‌ها به کامپوننت‌های مختلف

پس از تعریف لینکی به کامپوننتی دیگر از درون یک کامپوننت، اکنون می‌خواهیم دو کوئری استرینگ param1 و param2 را نیز به آن ارسال کنیم:
<NavLink class="btn btn-secondary" href="LearnAdvancedRouting?param1=value1&param2=value2">
    <span class="oi oi-list-rich" aria-hidden="true"></span> Learn Advanced Routing
</NavLink>
در کامپوننت LearnAdvancedRouting برای دریافت این پارامترها، نیاز است آن‌ها را از URL جاری استخراج کرد:
@page "/LearnAdvancedRouting"
@inject NavigationManager NavigationManager

<h3>Learn Advanced Routing</h3>

<h4>Parameter 1 : @Param1</h4>
<h4>Parameter 2 : @Param2</h4>

@code
{
    string Param1;
    string Param2;

    protected override void OnInitialized()
    {
        base.OnInitialized();

        var absoluteUri = new Uri(NavigationManager.Uri);
        var queryParam = System.Web.HttpUtility.ParseQueryString(absoluteUri.Query);
        Param1 = queryParam["Param1"];
        Param2 = queryParam["Param2"];
    }
}
ابتدا سرویس توکار NavigationManager توسط دایرکتیو inject@ به کامپوننت جاری تزریق شده‌است که برای کار و دسترسی به آن، نیاز به تنظیمات ابتدایی خاصی نیست و پیشتر به مجموعه‌ی سرویس‌های برنامه افزوده شده‌است. برای نمونه توسط آن می‌توان به Uri در حال پردازش، دسترسی یافت. اکنون که این Uri را داریم، با استفاده از متد HttpUtility.ParseQueryString می‌توان به مجموعه‌ی کوئری استرینگ‌های ارسالی، به صورت key/value دسترسی یافت و برای مثال آن‌ها را در روال رویدادگردان OnInitialized، دریافت و با انتساب آن‌ها به دو فیلد تعریف شده، سبب نمایش مقادیر دریافتی شد:



هدایت به یک کامپوننت دیگر با کد نویسی

فرض کنید می‌خواهیم دکمه‌ای را اضافه کنیم که با کلیک بر روی آن، ما را به کامپوننت LearnRouting هدایت می‌کند:
@page "/LearnAdvancedRouting"
@inject NavigationManager NavigationManager

@*<NavLink href="/learnrouting" class="btn btn-secondary">Back to Routing</NavLink>*@
@*<a href="/learnrouting" class="btn btn-secondary">Back to Routing</a>*@
<button class="btn btn-secondary" @onclick="BackToRouting">Back to Routing</button>

@code
{
    private void BackToRouting()
    {
        NavigationManager.NavigateTo("learnrouting");
    }
}
در اینجا روش‌های مختلف تعریف لینک به کامپوننتی دیگر را مشاهده می‌کنید. یا می‌توان از کامپوننت NavLink استفاده کرد و یا از یک anchor tag استاندارد، که href هر دوی آن‌ها به مسیریابی مقصد اشاره می‌کنند و یا می‌توان با استفاده از سرویس NavigationManager و متد NavigateTo آن مانند کدهای فوق، سبب هدایت کاربر به صفحه‌ای دیگر شد.


کدهای کامل این مطلب را از اینجا می‌توانید دریافت کنید: Blazor-5x-Part-10.zip