مطالب
شروع به کار با AngularJS 2.0 و TypeScript - قسمت نهم - مسیریابی
یک برنامه، از صفحات و Viewهای مختلفی تشکیل می‌شود و Routing یا مسیریابی، امکان ناوبری بین این Viewها را میسر می‌کند. یک برنامه‌ی AngularJS 2.0، یک برنامه‌ی تک صفحه‌ای وب است. به این معنا که تمام Viewهای برنامه، در یک صفحه نمایش داده می‌شوند؛ که معمولا همان index.html سایت است. هدف از سیستم مسیریابی، مدیریت نحوه‌ی نمایش و قرارگیری این Viewها، درون تک صفحه‌ی برنامه است.


برپایی تنظیمات اولیه‌ی سیستم مسیریابی در AngularJS 2.0

برای کار با سیستم مسیریابی AngularJS 2.0، ابتدا باید اسکریپت‌های آن به صفحه اضافه شوند. در ادامه المان پایه‌ای تعریف شده و سپس باید سرویس پروایدر مسیریابی را رجیستر کرد. جزئیات این موارد را در ادامه بررسی می‌کنیم:

الف) سرویس مسیریابی، جزئی از angular2/core نیست. به همین جهت مدخل اسکریپت متناظر با آن باید به صفحه‌ی اصلی سایت اضافه شود که این مورد، در قسمت اول بررسی پیشنیازهای نصب AngularJS 2.0 صورت گرفته‌است:
 <!-- Required for routing -->
<script src="~/node_modules/angular2/bundles/router.dev.js"></script>
این تعریف در فایل Views\Shared\_Layout.cshtml (و یا index.html) پروژه‌ی جاری موجود است.

ب) افزودن المان base به ابتدای صفحه:
 <!DOCTYPE html>
<html>
<head>
    <base href="/">
بلافاصله پس از تگ head، المان base اضافه می‌شود. این المان به سیستم مسیریابی اعلام می‌کند که چگونه آدرس‌های خود را تشکیل دهد. به صورت پیش فرض، AngularJS 2.0 از آدرس‌هایی با فرمت HTML5 استفاده می‌کند. در این حالت دیگر نیازی به ذکر # برای مشخص سازی مسیریابی‌های محلی نیست.
از آنجائیکه فایل index.html در ریشه‌ی سایت قرار گرفته‌است، مقدار آغازین href آن به / تنظیم شده‌است.

ج) شبیه به حالت ثبت پروایدر HTTP در قسمت قبل، برای ثبت پروایدر مسیریابی نیز به فایل App\app.component.ts مراجعه می‌کنیم:
//same as before ...
import { ROUTER_PROVIDERS } from 'angular2/router';
 
//same as before ... 

@Component({
//same as before …
    providers: [
        ProductService,
        HTTP_PROVIDERS,
        ROUTER_PROVIDERS
    ]
})
//same as before ...
در اینجا سرویس ROUTER_PROVIDERS به خاصیت providers اضافه شده‌است و همچنین import متناظر با آن نیز به ابتدای صفحه اضافه گردیده‌است.
علت ختم شدن نام این سرویس‌ها به PROVIDERS این است که این تعاریف، امکان استفاده‌ی از چندین سرویس زیر مجموعه‌ی آن‌ها را فراهم می‌کنند و صرفا یک سرویس نیستند.


ساخت کامپوننت نمایش جزئیات محصولات

در ادامه می‌خواهیم جزئیات هر محصول را با کلیک بر روی نام آن در لیست محصولات، در آدرسی دیگر به صورتی مجزا مشاهده کنیم. به همین منظور به پوشه‌ی products برنامه مراجعه کرده و دو فایل جدید product-detail.component.ts و product-detail.component.html را ایجاد کنید؛ با این محتوا:
الف) محتوای فایل product-detail.component.html
<div class='panel panel-primary'>
    <div class='panel-heading'>
        {{pageTitle}}
    </div>
</div>
ب) محتوای فایل product-detail.component.ts
import { Component } from 'angular2/core';

@Component({
    templateUrl: 'app/products/product-detail.component.html'
})
export class ProductDetailComponent  {
    pageTitle: string = 'Product Detail'; 
}
در اینجا یک کامپوننت جدید را ایجاد کرده‌ایم که در قالب آن، مقدار خاصیت pageTitle با روش interpolation در آن درج شده‌است. کلاس ProductDetailComponent، قالب خود را از طریق مقدار دهی آدرس آن در خاصیت templateUrl متادیتای خود، پیدا می‌کند.
اگر دقت کنید، این کامپوننت ویژه دارای خاصیت selector نیست. ذکر selector تنها زمانی اجباری است که بخواهیم این کامپوننت را داخل کامپوننتی دیگر قرار دهیم. در اینجا قصد داریم این کامپوننت را به صورت یک View جدید، توسط سیستم مسیریابی نمایش دهیم و نه به صورت جزئی از یک کامپوننت دیگر.


افزودن تنظیمات مسیریابی به برنامه

مسیریابی در AngularJS 2.0، مبتنی بر کامپوننت‌ها است. به همین جهت، ابتدای کار مسیریابی، مشخص سازی تعدادی از کامپوننت‌ها هستند که قرار است به عنوان مقصد سیستم راهبری (navigation) مورد استفاده قرار گیرند و به ازای هر کدام، یک مسیریابی و Route جدید را تعریف می‌کنیم. تعریف هر Route جدید شامل انتساب نامی به آن، تعیین مسیر مدنظر و مشخص سازی کامپوننت مرتبط است:
 { path: '/products', name: 'Products', component: ProductListComponent },
برای مثال جهت تعریف Route کامپوننت لیست محصولات، نام آن‌را Products، مسیر آن‌را products/ و در آخر کامپوننت آن‌را به نام کلاس متناظر با آن، تنظیم می‌کنیم.
این تنظیمات به عنوان یک متادیتای جدید دیگر به کلاس AppComponent، در فایل app.component.ts اضافه می‌شوند:
//same as before …
import { ROUTER_PROVIDERS, RouteConfig } from 'angular2/router';
 
//same as before …
 
@Component({
    //same as before …
})
@RouteConfig([
    { path: '/welcome', name: 'Welcome', component: WelcomeComponent, useAsDefault: true },
    { path: '/products', name: 'Products', component: ProductListComponent }
])
export class AppComponent {
    pageTitle: string = "DNT AngularJS 2.0 APP";
}
در اینجا decorator جدیدی به نام RouteConfig به کلاس AppComponent اضافه شده‌است و همچنین importهای متناظری نیز در ابتدای این فایل تعریف شده‌اند.
همانطور که ملاحظه می‌کنید، یک کلاس می‌تواند بیش از یک decorator داشته باشد.
()RouteConfig@ را به کامپوننتی الصاق می‌کنیم که قصد میزبانی مسیریابی را دارد (Host component). این مزین کننده، آرایه‌ای از اشیاء را قبول می‌کند و هر شیء آن دارای خواصی مانند مسیر، نام و کامپوننت است. باید دقت داشت که نام هر مسیریابی تعریف شده باید pascal case باشد. در غیراینصورت مسیریاب ممکن است این نام را با path اشتباه کند.  
همچنین امکان تعریف خاصیت دیگری به نام useAsDefault نیز در اینجا میسر است. از آن جهت تعریف مسیریابی پیش فرض سیستم، در اولین بار نمایش آن، استفاده می‌شود.

در اینجا نام کامپوننت، رشته‌ای ذکر نمی‌شود و دقیقا اشاره دارد به نام کلاس متناظر. بنابراین هر نام کلاسی که در اینجا اضافه می‌شود، باید به همراه import ماژول آن نیز در ابتدای فایل جاری باشد. به همین جهت اگر تنظیمات فوق را اضافه کنید، ذیل کلمه‌ی WelcomeComponent یک خط قرمز مبتنی بر عدم تعریف آن کشیده می‌شود. برای تعریف آن، پوشه‌ی جدیدی را به ریشه‌ی سایت به نام home اضافه کنید و به آن دو فایل ذیل را اضافه نمائید:
الف) محتوای فایل welcome.component.ts
import { Component } from 'angular2/core';
 
@Component({
    templateUrl: 'app/home/welcome.component.html'
})
export class WelcomeComponent {
    public pageTitle: string = "Welcome";
}
ب) محتوای فایل welcome.component.html
<div class="panel panel-primary">
    <div class="panel-heading">
        {{pageTitle}}
    </div>
    <div class="panel-body">
        <h3 class="text-center">Default page</h3>
    </div>
</div>
کار این کامپوننت، نمایش صفحه‌ی آغازین برنامه است؛ بر اساس تنظیم useAsDefault: true مسیریابی‌های تعریف شده‌.
پس از تعریف این کامپوننت، اکنون باید import ماژول آن‌را به ابتدای فایل app.component.ts اضافه کنیم، تا مشکل عدم شناسایی نام کلاس WelcomeComponent برطرف شود:
 import { WelcomeComponent } from './home/welcome.component';


فعال سازی مسیریابی‌های تعریف شده

روش‌های مختلفی برای دسترسی به اجزای یک برنامه وجود دارند؛ برای مثال کلیک بر روی یک لینک، دکمه و یا تصویر و سپس فعال سازی مسیریابی متناظر با آن. همچنین کاربر می‌تواند آدرس صفحه‌ای را مستقیما در نوار آدرس‌های مرورگر وارد کند. به علاوه امکان کلیک بر روی دکمه‌های back و forward مرورگر نیز همواره وجود دارند. تنظیمات مسیریابی‌های انجام شده، دو مورد آخر را به صورت خودکار مدیریت می‌کنند. در اینجا تنها باید مدیریت اولین حالت ذکر شده را با اتصال مسیریابی‌ها به اعمال کاربران، انجام داد.
به همین جهت منویی را به بالای صفحه‌ی برنامه اضافه می‌کنیم. برای این منظور، فایل app.component.ts را گشوده و خاصیت template کامپوننت AppComponent را به نحو ذیل تغییر می‌دهیم:
@Component({
    //same as before …
    template: `
     <div>
        <nav class='navbar navbar-default'>
            <div class='container-fluid'>
                <a class='navbar-brand'>{{pageTitle}}</a>
                <ul class='nav navbar-nav'>
                    <li><a [routerLink]="['Welcome']">Home</a></li>
                    <li><a [routerLink]="['Products']">Product List</a></li>
                </ul>
            </div>
        </nav>
        <div class='container'>
            <router-outlet></router-outlet>
        </div>
     </div>
    `,
    //same as before …
})
در اینجا یک navigation bar بوت استرپ 3، جهت تعریف منوی بالای صفحه اضافه شده‌است.
سپس جهت تعریف لینک‌های هر آیتم، از یک دایرکتیو توکار AngularJS 2.0 به نام routerLink استفاده می‌کنیم. هر routerLink به یکی از آیتم‌های تنظیم شده‌ی در RouteConfig بایند می‌شود. بنابراین نام‌هایی که در اینجا قید شده‌اند، دقیقا نام‌هایی هستند که در خاصیت name هر کدام از اشیاء تشکیل دهنده‌ی RouteConfig، تعریف و مقدار دهی گردید‌ه‌اند.
اکنون اگر کاربر بر روی یکی از لینک‌های Home و یا Product List کلیک کند، مسیریابی متناظر با آن فعال می‌شود (بر اساس این نام، در لیست عناصر RouteConfig جستجویی صورت گرفته و عنصر معادلی بازگشت داده می‌شود) و سپس View آن کامپوننت نمایش داده خواهد شد.
تا اینجا دایرکتیو جدید routerLink به قالب کامپوننت اضافه شد‌ه‌است؛ اما AngularJS 2.0 نمی‌داند که باید آن‌را از کجا دریافت کند. به همین جهت ابتدا import آن‌را (ROUTER_DIRECTIVES) به ابتدای ماژول جاری اضافه خواهیم کرد:
 import { ROUTER_PROVIDERS, RouteConfig, ROUTER_DIRECTIVES } from 'angular2/router';
و سپس خاصیت دایرکتیوهای کامپوننت ریشه‌ی سایت را نیز با آن مقدار دهی خواهیم کرد:
 directives: [ROUTER_DIRECTIVES],
علت جمع بود نام این دایرکتیو این است که routerLink استفاده شده، یکی از اعضای مجموعه‌ی دایرکتیوهای مسیریابی توکار AngularJS 2.0 است.

تا اینجا اگر دقت کرده باشید، کامپوننت نمایش لیست محصولات را از کامپوننت ریشه‌ی سایت حذف کرده‌ایم و بجای آن منوی بالای سایت را نمایش می‌دهیم که توسط آن می‌توان به صفحه‌ی آغازین و یا صفحه‌ی نمایش لیست محصولات، رسید. به همین جهت خاصیت directives دیگر شامل ذکر کلاس کامپوننت لیست محصولات نیست.

در انتهای قالب کامپوننت ریشه‌ی سایت، یک دایرکتیو جدید به نام router-outlet نیز تعریف شده‌است. وقتی یک کامپوننت فعال می‌شود، نیاز است View مرتبط با آن نیز نمایش داده شود. دایرکتیو router-outlet محل نمایش این View را مشخص می‌کند.

اکنون اگر برنامه را اجرا کنیم، به این شکل خواهیم رسید:


اگر دقت کنید، آدرس بالای صفحه، در اولین بار نمایش آن به http://localhost:2222/welcome تنظیم شده و این مقدار دهی بر اساس خاصیت useAsDefault تنظیمات مسیریابی سایت انجام شده‌است (نمایش welcome به عنوان صفحه‌ی اصلی و پیش فرض).
همچنین با کلیک بر روی لینک لیست محصولات، کامپوننت آن فعال شده و نمایش داده می‌شود. محل قرارگیری این کامپوننت‌ها، دقیقا در محل قرارگیری دایرکتیو router-outlet است.


ارسال پارامترها به سیستم مسیریابی

در ابتدا بحث، مقدمات کامپوننت نمایش جزئیات یک محصول انتخابی را تهیه کردیم. برای فعال سازی این کامپوننت و مسیریابی آن، نیاز است بتوان پارامتری را به سیستم مسیریابی ارسال کرد. برای مثال با انتخاب آدرس product/5، جزئیات محصول با ID مساوی 5 نمایش داده شود.
برای این منظور:
الف) اولین قدم، تعریف مسیریابی آن است. به همین جهت به فایل app.component.ts مراجعه و دو تغییر ذیل را به آن اعمال کنید:
//same as before …

import { ProductDetailComponent } from './products/product-detail.component';
 
@Component({
    //same as before …
})
@RouteConfig([
    //same as before …
    { path: '/product/:id', name: 'ProductDetail', component: ProductDetailComponent }
])
//same as before …
ابتدا مسیریابی جدیدی به نام ProductDetail اضافه شده‌است و سپس ماژول دربرگیرنده‌ی کلاس کامپوننت آن نیز import شده‌است.
تفاوت این مسیریابی با نمونه‌های قبلی در تعریف id:/ است. پس از ذکر :/، نام یک متغیر عنوان می‌شود و اگر نیاز به چندین متغیر بود، همین الگو را تکرار خواهیم کرد.

ب) سپس نحوه‌ی فعال سازی این مسیریابی را توسط تعریف لینکی جدید، معرفی می‌کنیم. بنابراین فایل قالب product-list.component.html را گشوده و سپس بجای نمایش عنوان محصول:
 <td>{{ product.productName }}</td>
لینک به جزئیات آن‌را نمایش می‌دهیم:
<td>
    <a [routerLink]="['ProductDetail', {id: product.productId}]">
        {{product.productName}}
    </a>
</td>
نحوه‌ی تعریف این لینک، با لینک‌هایی که در منوی بالای سایت اضافه کردیم، یکی است؛ با این تفاوت که اکنون پارامتر دومی را به قسمت یافتن نام این Route، جهت مشخص سازی روش مقدار دهی متغیر id، تعریف کرده‌ایم. در اینجا id هر لینک از productId بایند شده تامین می‌شود.
اکنون که از دایرکتیو جدید routerLink در این قالب استفاده شده‌است، نیاز است تعریف دایرکتیو آن‌را به متادیتای کلاس کامپوننت لیست محصولات نیز اضافه کنیم تا AngularJS 2.0 بداند آن‌را از کجا باید تامین کند:
import { Component, OnInit } from 'angular2/core';
import { ROUTER_DIRECTIVES } from 'angular2/router';
//same as before …
 
@Component({
    //same as before …
    directives: [StarComponent, ROUTER_DIRECTIVES]
})

در ادامه اگر برنامه را اجرا کنید، عنوان‌های محصولات، به آدرس نمایش جزئیات آن‌ها لینک شده‌اند:


ج) در آخر زمانیکه View نمایش جزئیات محصول فعال می‌شود، نیاز است این id را از url جاری دریافت کند. به همین جهت فایل product-detail.component.ts را گشوده و تغییرات ذیل را به آن اعمال کنید:
import { Component } from 'angular2/core';
import { RouteParams } from 'angular2/router';
 
@Component({
    templateUrl: 'app/products/product-detail.component.html'
})
export class ProductDetailComponent {
    pageTitle: string = 'Product Detail';
 
    constructor(private _routeParams: RouteParams) {
        let id = +this._routeParams.get('id');
        this.pageTitle += `: ${id}`;
    } 
}
با نحوه‌ی تزریق وابستگی‌ها در قسمت هفتم آشنا شدیم. در اینجا سرویس توکار RouteParams به سازنده‌ی کلاس تزریق شده‌‌است. با استفاده از آن می‌توان به id ارسالی از طریق url دسترسی یافت. در اینجا پارامتری که به متد get ارسال می‌شود، باید با نام پارامتری که در تنظیمات آغازین مسیریابی سیستم تعریف گردید، تطابق داشته باشد.
در این حالت، id دریافتی، به متغیر pageTitle اضافه شده و در قالب مربوطه به صورت خودکار نمایش داده می‌شود.

تا اینجا اگر برنامه را اجرا کنید، صفحه‌ی نمایش جزئیات یک محصول، با کلیک بر روی عناوین آن‌ها به صورت زیر نمایش داده می‌شود:



افزودن دکمه‌ی back با کدنویسی

اکنون برای بازگشت مجدد به لیست محصولات، می‌توان از دکمه‌ی back مرورگر استفاده کرد، اما امکان طراحی این دکمه در قالب‌ها نیز پیش بینی شده‌است.
برای این منظور قالب product-detail.component.html را به نحو ذیل بازنویسی می‌کنیم:
<div class='panel panel-primary'>
    <div class='panel-heading'>
        {{pageTitle}}
    </div>
    <div class='panel-footer'>
        <a class='btn btn-default' (click)='onBack()' style='width:80px'>
            <i class='glyphicon glyphicon-chevron-left'></i> Back
        </a>
    </div>    
</div>
در اینجا دکمه‌ی بازگشت به صفحه‌ی قبلی اضافه شده‌است که به متد onBack در کلاس مرتبط با این قالب، متصل است.

سپس کدهای product-detail.component.ts را به صورت ذیل تکمیل خواهیم کرد:
import { Component } from 'angular2/core';
import { RouteParams, Router } from 'angular2/router';
 
@Component({
    templateUrl: 'app/products/product-detail.component.html'
})
export class ProductDetailComponent {
    pageTitle: string = 'Product Detail';
 
    constructor(private _routeParams: RouteParams, private _router: Router) {
        let id = +this._routeParams.get('id');
        this.pageTitle += `: ${id}`;
    }
 
    onBack(): void {
        this._router.navigate(['Products']);
    }
}
در اینجا سرویس جدیدی به نام Router در سازنده‌ی کلاس تزریق شده‌است. این سرویس امکان فراخوانی متدهایی مانند navigate را جهت حرکت به مسیریابی مشخصی، میسر می‌کند. پارامتری که به این متد ارسال می‌شود، دقیقا معادل همان پارامتری است که به دایرکتیو routerLink ارسال می‌گردد و در اینجا صرفا نام یک مسیریابی مشخص شده‌است؛ بدون ذکر پارامتری.


رفع تداخل مسیریابی‌های ASP.NET MVC با مسیریابی‌های AngularJS 2.0

در طی بحث جاری عنوان شد که اگر کاربر مسیر http://localhost:2222/product/2 را جایی ثبت کرده یا bookmark کند، پس از فراخوانی مستقیم آن در نوار آدرس‌های مرورگر، بلافاصله به این آدرس هدایت خواهد شد. این مورد صحیح است اگر از index.html بجای بکارگیری ASP.NET MVC، جهت هاست برنامه استفاده شود. اگر چنین آدرسی را در یک برنامه‌ی ASP.NET MVC فراخوانی کنیم، ابتدا به دنبال کنترلری به نام product می‌گردد (ابتدا وارد موتور ASP.NET MVC می‌شود) و چون این کنترلر در سمت سرور تعریف نشده‌است، پیام 404 و یا یافت نشد را مشاهده خواهید کرد و فرصت به اجرای برنامه‌ی AngularJS نخواهد رسید.
برای حل این مشکل نیاز است یک route جدید را به نام catch all، در انتهای مسیریابی‌های فعلی اضافه کنید؛ تا سایر درخواست‌های رسیده را به صفحه‌ی نمایش برنامه‌ی AngularJS هدایت کند:
public class RouteConfig
{
    public static void RegisterRoutes(RouteCollection routes)
    {
        routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
 
        routes.MapRoute(
            name: "Default",
            url: "{controller}/{action}/{id}",
            defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional },
            constraints: new { controller = "Home" } // for catch all to work, Home|About|SomeName
        );
 
        // Route override to work with Angularjs and HTML5 routing
        routes.MapRoute(
            name: "NotFound",
            url: "{*catchall}",
            defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
        );
    }
}


کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: MVC5Angular2.part9.zip


خلاصه‌ی بحث

حین ایجاد کامپوننت‌ها باید به نحوه‌ی نمایش آن‌ها نیز فکر کرد. اگر کامپوننتی قرار است داخل یک کامپوننت دیگر نمایش یابد، باید دارای selector باشد. یک چنین کامپوننتی نیاز به تعریف مسیریابی ندارد. برای کامپوننت‌هایی که به عنوان یک View مستقل طراحی می‌شوند و قرار است در یک صفحه‌ی مجزا نمایش داده شوند، نیازی به تعریف selector نیست؛ اما باید برای آن‌ها مسیریابی‌های ویژه‌ای را تعریف کرد. همچنین نیاز است مدیریت اعمال کاربران را جهت فعال سازی آن‌ها نیز مدنظر داشت. برای استفاده از امکانات مسیریابی توکار AngularJS 2.0 نیاز است اسکریپت آن‌را به صفحه‌ی اصلی اضافه کرد. سپس باید المان base را جهت نحوه‌ی تشکیل آدرس‌های مسیریابی، به صفحه اضافه کرد. در ادامه کار ثبت ROUTER_PROVIDERS در بالاترین سطح سلسه مراتب کامپوننت‌های سایت انجام می‌شود. با استفاده از RouteConfig کار تنظیمات ابتدایی مسیریابی صورت خواهد گرفت. این decorator به کامپوننتی که قرار است کار میزبانی مسیریابی را انجام دهد، متصل می‌شود. پس از تعریف مسیریابی‌ها با ذکر یک نام منحصربفرد، مسیری که باید توسط کاربر وارد شود و نام کامپوننت مدنظر، با استفاده از دایرکتیو routerLink کار تعریف این آدرس‌ها، در رابط کاربری برنامه انجام می‌شود. این دایرکتیو جدید، جزئی از مجموعه‌ی ROUTER_DIRECTIVES است که باید به لیست دایرکتیوهای کامپوننت ریشه‌های سایت و هر کامپوننتی که از routeLink استفاده می‌کند، اضافه شود. برای بایند این دایرکتیو به مسیریابی‌های تعریف شده، سمت راست این اتصال باید به آرایه‌ای از مقادیر مقدار دهی شود که اولین عنصر آن، نام یکی از عناصر مسیریابی تعریف شده‌ی در RouteConfig است. از دومین عنصر آن برای مقدار دهی پارامترهای ارسالی به سیستم مسیریابی استفاده می‌شود. کار دایرکتیو router-outlet، مشخص سازی محل نمایش یک View است که عموما در قالب میزبان مسیریابی قرار می‌گیرد. برای تعیین پارامترهای مسیریابی، از الگوی paramName:/ استفاده می‌شود. برای دسترسی به این مقادیر در یک کامپوننت، می‌توان از سرویس RouteParams استفاده کرد. برای فعال سازی یک مسیریابی با کدنویسی، از سرویس Router و متد navigate آن کمک می‌گیریم.
مطالب
دو تنظیم ضروری VS.NET جهت کار با WPF و Silverlight

تنظیم اول: تغییر نحوه‌ی نمایش پیش فرض فایل‌های XAML

اگر فایل XAML شما اندکی حجیم شود نمایش آن در VS.NET کمی طولانی خواهد شد و حالت پیش فرض نمایش در VS.NET هم split view mode است (نمایش XAML و پیش نمایش آن با هم). این مورد هم پس از مدتی تبدیل به عذاب می‌شود. برای رفع آن می‌توان حالت پیش فرض نمایش یک فایل XAML را به XAML View تنها تغییر داد.
برای این منظور به منوی Tools ، گزینه‌ی Options و سپس قسمت تنظیمات Text editor مراجعه کنید. در اینجا در قسمت XAML ، گزینه‌ی Miscellaneous را انتخاب کرده و سپس "Always open documents in full XAML view" را تیک بزنید.



حتی ممکن است این مورد هم رضایت بخش نباشد. در این حالت می‌توان ویرایشگر پیش فرض را کلا تغییر داد. Design tab را در پایین صفحه از دست می‌دهیم اما هنوز intellisense کار می‌کند و اگر نیاز به designer بود فقط کافی است کلیک راست کرده و گزینه‌ی View designer را انتخاب کرد:
روی یک فایل XAML دلخواه کلیک راست کرده و گزینه‌ی Open with را انتخاب کنید. سپس "Source Code (Text) Editor" را انتخاب کرده و روی دکمه‌ی Set as Default کلیک کنید. تمام!
هر چند Blend این مشکلات را ندارد و با فایل‌های حجیم XAML به خوبی کاری می‌کند.


تنظیم دوم: تغییر نحوه‌ی نمایش مشکلات ناشی از Binding

عموما اگر مشکلاتی در حین عملیات Binding در WPF یا Silverlight وجود داشته باشند، خطاها در Debugger Output Window نمایش داده می‌شوند. حالت پیش فرض هم فقط روی Error تنظیم شده است به این معنا که warning ها را مشاهده نخواهید کرد. برای تغییر این مورد باید به صورت زیر عمل کرد:
به منوی Tools ، گزینه‌ی Options و سپس قسمت تنظیمات Debugging مراجعه کنید. گزینه‌ی Output Window -> WPF Trace Settings را انتخاب نمائید. سپس در اینجا قسمت WPF trace settings را یافته و مقدار پیش فرض Data binding را که به Error تنظیم شده است، به Warning تنظیم نمائید.



مطالب
قالبی برای ایجاد آزمون‌های NUnit مخصوص ReSharper

افزونه‌ی ReSharper به‌دلیل یکپارچه کردن امکان استفاده از NUnit در ویژوال استودیو، یکی از انتخاب‌های اول جهت انجام آزمایشات واحد در این محیط به شمار می‌رود.
اخیرا آقای Genisio چند قالب ایجاد آزمون‌های NUnit را مخصوص ReSharper ایجاد کرده‌اند، که در ادامه در مورد نحوه‌ی استفاده از آن‌ها توضیح داده خواهد شد.
پس از دریافت فایل‌ها، برای استفاده، به منوی ReSharper گزینه‌ی live templates مراجعه نمائید. سپس بر روی نوار ابزار صفحه‌ی باز شده، روی دکمه‌ی import کلیک نموده و فایل‌ها را معرفی کنید.
NewTestFileTemplate.xml از نوع file template است.
TestTemplates.xml از نوع live template می‌باشد.


اکنون مجددا به منوی اصلی ReSharper مراجعه کنید و مسیر زیر را طی نمائید:

ReSharper -> new from template -> more …




گزینه‌ی Test اضافه شده را انتخاب کرده و سپس قسمت Add to quicklist را نیز انتخاب نمائید.
به این صورت گزینه‌ی Test به این منو افزوده خواهد شد و هر بار که بر روی آن کلیک شود، یک کلاس حاضر و آماده مطابق قالب اصلی یک کلاس استاندارد NUnit برای شما ایجاد خواهد شد.
همچنین در این مجموعه یک سری live template نیز موجود است که کار آن‌ها فعال سازی intellisense ویژوال استودیو جهت ایجاد یک سری متدها به صورت خودکار است. برای مثال اگر کلمه‌ی test را تایپ کنید و سپس دکمه‌ی tab و یا enter را فشار دهید، بلافاصله بدنه‌ی خالی یک متد تست برای شما ایجاد خواهد شد.
سایر میان‌بر‌های در نظر گرفته شده، به شرح زیر هستند:
test – Create a new [Test] method
setup – Create a [SetUp] method
teardown – Create a new [TearDown] method
ise – Assert that condition is equal to value
ist – Assert that condition is true
isf – Assert that condition is false
isn – Assert that condition is null
isnn – Assert that condition is not null


نظرات مطالب
یکپارچه سازی Angular CLI و ASP.NET Core در VS 2017
در این حالت حالت توسعه برخی مواقع حین build پروژه ممکن است با خطای:
System.TimeoutException: The Angular CLI process did not start listening for requests within the timeout period of 50 seconds.
مواجه شوید که علتش طولانی شدن پروسه‌ی build هست.برای رفع این مشکل در نگارش جدید بسته‌ی نیوگت Microsoft.AspNetCore.SpaServices.Extensions میتوان این زمان پیش فرض  را حین توسعه تغییر داد.
 app.UseSpa(spa =>
            {
                spa.Options.SourcePath = "ClientApp";

                if (env.IsDevelopment())
                {
                    spa.Options.StartupTimeout = new TimeSpan(0, 0, 80);
                    spa.UseAngularCliServer(npmScript: "start");
                }
            });

نظرات مطالب
اعمال تزریق وابستگی‌ها به مثال رسمی ASP.NET Identity
- همان از حالت کامنت خارج کردن قسمت CreateUserIdentityAsync در ApplicationSignInManager کافی هست. علت اینجا است که متد SignInManager.PasswordSignInAsync در انتهای کارش، متد SignInAsync را فراخوانی می‌کند. این متد سبب به روز رسانی کوکی کاربر با Claims جدید او می‌شود.
- بنابراین هر نوع افزوده شدن Claims باید «پیش از» متد PasswordSignInAsync  باشد و یا دقیقا در همان متد userManager.GenerateUserIdentityAsync و چون در کلاس SignInManager فراخوانی نمی‌شد، در حین لاگین هم تنظیم نمی‌شد. علت تنظیم آن با ریفرش بعدی، وارد عمل شدن Security Stamp Validator است.
- فراخوانی متد signInManager.CreateUserIdentityAsync پس از لاگین هیچ تاثیری ندارد، چون وارد پروسه‌ی SignInAsync و به روز رسانی کوکی‌های کاربر نمی‌شود.
مطالب
Garbage Collector در #C - قسمت اول

Garbage Collector

Garbage Collection



فرض کنید متغیری را ایجاد کرده و به آن مقدار داده‌‎‎اید:
string message = "Hello World!";

آیا تابحال به این موضوع فکر کرده‌اید که طول عمر متغیر message تا چه زمانی است و چه زمانی باید از بین برود؟
چه زمانی باید توسط کامپایلر ( یا بهتر بگوییم ، Runtime ) طول عمر این متغیر به پایان برسد و از حافظه حذف شود؟

زبان‎‌های برنامه نویسی به دو دسته‌ی Managed و Unmanaged تقسیم میشوند:

  1. Unmanaged: در این دسته از زبان ها، وظیفه‌ی ایجاد اشیاء، تشخیص زمان درست برای از بین بردن و از بین بردن آنها، برعهده‌ی شماست. زبان‌های C و ++C جز این نوع زبان‌ها میباشند.

  2. Managed: در این دسته از زبان‌ها، ایجاد اشیاء مانند قبل بر عهده‌ی شماست، اما وظیفه تشخیص و از بین بردن آنها در زمان درست را Runtime برعهده میگیرد.
    در این نوع زبان‌‎ها ما دغدغه‌ی حذف متغیری را که در چندین خط بالاتر، آن را ایجاد کرده و به آن مقدار داده‎، به چندین متد آن را پاس داده و انواع تغییرات را روی آن انجام داده‎ایم، نداریم. زبان‌های #C و Java جزو این نوع زبان‌ها میباشند.

ایده کلی ایجاد زبان‌های Managed بر این است که شما درگیر مباحث مربوط به Memory Management نشوید و تمرکز اصلیتان روی Business باشد.

اکثر پروژه‌ها به اندازه کافی پیچیدگی‌های Business ای دارند و ترکیب کردن این نوع پیچیدگی‌ها با پیچیدگی‌های Low Level و Technical ای همچون مباحث Memory Management، در اکثر اوقات باعث میشود که نگهداری از پروژه کاری بسیار دشوار شده و به تدریج مانند بسیاری از پروژه‌های دیگر، نام Legacy را با خود به یدک بکشد.

این بدان معنا نیست که پروژه‌هایی که با زبان هایی همچون C و ++C نوشته میشوند، از همان روز اول Legacy بوده و قابل نگهداری نیستند، بلکه بدین معناست که نگهداری کد چنین زبان‌هایی نسبت به زبان‌های Managed، به مراتب مشکل‌تر است و همچنین قابل ذکر است که قابلیت نگهداری یک پروژه به مباحث بسیار زیاد دیگری نیز بستگی دارد.

Legacy Code



نمونه ای از ترکیب این نوع پیچیدگی‌ها را با مثالی بسیار ساده در دو زبان C و #C بررسی میکنیم.

برنامه‌ای داریم که یک متغییر عددی از نوع int را ایجاد میکند. عدد 25 را بعنوان مقدار به آن میدهد و سپس این متغیر به یک متد پاس داده میشود تا مقدارش چاپ شود.

کد این برنامه در زبان C :
#include <stdio.h>
#include <stdlib.h>

void printReport(int* data)
{
    printf("Report: %d", *data);
}

int main(void)
{
    int *myNumber;
    myNumber = (int*)malloc(sizeof(int));
    if (myNumber == 0)
    {
        printf("ERROR: Out of memory");
        return 1;
    }
    
    *myNumber = 25;
    printReport(myNumber);
    
    free(myNumber);
    myNumber = NULL;
    
    return 0;
}

"هدف" و Business اصلی این برنامه، چاپ و یک گزارش ساده بود، اما مسائل بسیار بیشتری در این مثال دخیل شده اند:

1- Allocate و دریافت فضای مورد نیاز برای یک عدد ( int ) از Memory ، توسط تابع malloc
2- Cast کردن مقدار برگشت داده شده ( *void ) به type مدنظرمان یعنی *int
3- نگهداری آدرس متغییر allocate شده داخل یک pointer
4- بررسی موفقیت آمیز بودن عمل allocation روی memory ( اگر حافظه فضای کافی نداشته باشد، عدد 0 بعنوان آدرس و به معنای عدم موفقیت بازگردانده میشود)
5- مقداردهی متغیر allocate شده با عدد 25
6- صدا زدن و پاس دادن pointer متغییر myNumber به تابع printReport
7- خالی کردن مقدار allocate شده متغییر myNumber توسط تابع free در زمانی که دیگر به آن در برنامه نیازی نیست.
8- مقداردهی NULL به myNumber ( جهت جلوگیری از مشکلات Dangling Pointers )


چند مرحله از این مراحل ذکر شده، واقعا نیاز Business ای برنامه ما بود؟ این مثال، بسیار ساده و غیر واقعی بود؛ اما تصور کنید با این روش، یک برنامه بزرگ با Business Rule‌ها و پیچیدگی‌های خودش، چه حجمی از کد و پیچیدگی را خواهد داشت.


کد این برنامه در زبان #C :
using System;

public class Program
{
    public static void Main()
    {
        int myNumber = 25;
        PrintReport(myNumber);
    }
    
    private static void PrintReport(int number)
    {
        Console.WriteLine($"Report: {number}");
    }
}

همانطور که میبینید در اینجا تمرکزمان روی هدف اصلی و Business است و درگیر پیچیدگی مباحث جانبی نظیر Manual Memory Management نشده‌ایم و Runtime زبان #C یعنی CoreCLR وظیفه Memory Management را در پشت صحنه برعهده گرفته است.



تفاوت بین زبان‌های Managed و Unmanaged را میتوانیم به رانندگی با ماشین دنده‌ای و اتومات تشبیه کنیم.

اکثر اوقات هدف اصلی رانندگی، رفتن از یک مبدا به یک مقصد است. با استفاده از یک ماشین دنده‌ای، علاوه بر هدف اصلی یعنی رسیدن به یک مقصد، ذهن ما درگیر تعویض دنده در سرعت‎ مناسب در طول مسیر میشود و اینکار را ممکن است بیش از صدها یا هزاران بار انجام دهیم. در این روش طبیعتا ما کنترل بیشتری داریم و در بعضی مواقع بسیار بهتر به نسبت یک سیستم خودکار عمل میکنیم، اما از هدف اصلی خود یعنی رفتن از نقطه A به B دور شده‌ایم.

در سوی دیگر، با استفاده از یک ماشین اتومات، تمام تمرکز ما روی هدف اصلیمان یعنی رسیدن از یک مبدا به یک مقصد است. درگیر عوض کردن چندین باره دنده در طول یک مسیر نیستیم و این وظیفه را یک Engine خارجی بر عهده گرفته است. هرچند که این روش، روش راحتتری نسبت به روش دستی و Manual است اما طبیعتا کنترل شما در این روش نسبت به روش قبل، کمتر است.

در زبان‎های Managed و Unmanaged هم دقیقا چنین تفاوت هایی وجود دارد.


در زبان‏‎های Unmanaged، شما کنترل کاملی را روی طول عمر اشیا و مدیریت حافظه دارید و همه چیز برعهده شماست؛ اما علاوه بر هدف اصلیتان، درگیر مباحث جانبی دیگری نیز شده‌اید. اکثر اوقات قدرت زبان‏‎های Unmanaged را در Game Engine‌ها و Real-Time Processing System‌ها میتوانید ببینید که در آنها مدیریت حافظه بصورت دستی انجام میشود و برنامه نویس‎های آن سیستم، تعیین کننده اصلی این هستند که طول عمر اشیاء تا چه زمانی باشد و چه زمانی از بین بروند که باعث اختلال یا کندی یک سیستم حتی برای چند میلی ثانیه نشوند.

در زبان‌های Managed، اکثر اوقات حتی نیازی نیست که شما درگیر مباحث جانبی مدیریت حافظه شوید و تمام کار را Runtime شما بصورت خودکار انجام میدهد. اما گاهی اوقات لازم است که قسمت‌های حساس برنامه ( اصطلاحا Hot-Path‌ها ) خود را پیدا کنید، از این قسمت‌ها Benchmark گرفته و مطمئن شوید که با حجم تعداد بالای درخواست و بار، به خوبی عمل میکند و همچنین قسمتی از برنامه، نشستی حافظه ( اصطلاحا Memory Leak ) ندارد. همانطور که گفتیم، گاهی اوقات یک انسان ( سیستم دستی ) بهتر از یک سیستم خودکار میتواند تصمیم بگیرد که در یک لحظه چه اتفاقی داخل برنامه رخ دهد.


در این سری مقالات قصد داریم وارد مبحث Memory Management در #C شده، با Garbage Collector آشنا و دیدی کلی از نحوه‌ی انجام کار آن داشته باشیم.

اشتراک‌ها
از شرکت‌ها و محیط‌های‌ کاری بیشتر بدونید

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

از شرکت‌ها و محیط‌های‌ کاری بیشتر بدونید
اشتراک‌ها
انواع خطاهای ممکن در بستر Http

خطاهایی که با اعداد زیر شروع می‌شوند:

1: هدف آن اطلاع رسانی است.

2: پیام هایی که حاکی از موفقیت در عملیات است با این کد شروع می‌شوند.

3: جهت هدایت مجدد به درخواست دیگر به کار می‌روند.

4: خطاهای سمت کلاینت را مشخص می‌کند.

5: خطاهای سمت سرو را مشخص می‌کند.

...

انواع خطاهای ممکن در بستر Http
نظرات مطالب
غنی سازی کامپایلر C# 9.0 با افزونه‌ها
پروژه‌ی «Meziantou.DotNet.CodingStandard» کار تولید خودکار لیست بررسی کننده‌های یک آنالیزر و تبدیل آن‌ها به فایل‌های editorconfig. را انجام می‌دهد که در آن‌ها  این بررسی کننده‌ها به حالت warning تنظیم شده‌اند. علت اینجا است که ممکن است خود آنالیزر، این‌ها را به صورت پیش‌فرض در حالت توصیه، تنظیم کرده باشد. هدف، سخت‌گیری بیشتر است.
نظرات مطالب
ارتقاء به ASP.NET Core 1.0 - قسمت 21 - بررسی تغییرات Bundling و Minification
- هدف اصلی از bundling این هست که «تمام فایل‌ها» مدخلی در صفحه نداشته باشند و فقط یک فایل معرف تمام آن‌ها ارائه شود؛ مانند کاری که در اینجا شده و گرنه ضرورتی به استفاده از این ابزارها نیست.
+ در تنظیمات آن حالت‌های مختلف caching سمت سرور مانند enableMemoryCache وجود دارد تا هربار و با هر درخواستی کار bundling و سایر جزئیات را تکرار نکند.