مطالب
مسیریابی در Angular - قسمت ششم - گروه بندی مسیریابی‌ها
همانطور که در قسمت قبل مشاهده کردیم، از تعریف Child Routes برای میسر ساختن نمایش قالب‌های کامپوننت‌ها، در درون سایر قالب‌های کامپوننت‌ها، استفاده می‌شود. برای نمونه قالب‌های برگه‌های یک فرم ویرایش اطلاعات را با تعریف یک router-outlet دیگر، در درون قالب والد آن‌ها نمایش دادیم. اما شاید بخواهیم کار گروه بندی مسیریابی‌ها را بدون افزودن یک router-outlet دیگر انجام دهیم. برای این منظور می‌توان مسیریابی‌های کامپوننت‌های نمایش لیست محصولات، جزئیات یک محصول و ویرایش یک محصول را ذیل یک والد که هیچ کامپوننتی ندارد، گروه بندی کرد. به همین جهت به router-outlet اضافه‌تری نیاز ندارد و به آن component-less routes نیز گفته می‌شود.


علت نیاز به گروه بندی مسیریابی‌ها در ذیل یک مسیریابی بدون کامپوننت

علت وجود امکان گروه بندی مسیریابی‌ها، در ذیل یک مسیریابی بدون کامپوننت به شرح زیر هستند:
 - امکان مدیریت و ساماندهی ساده‌تر مسیریابی‌ها با افزایش حجم برنامه
 - امکان به اشتراک گذاری Route Resolvers و محافظت کننده‌های از مسیرها
 - ممکن ساختن امکان lazy loading آن گروه


گروه بندی مسیریابی‌ها

در حال حاضر، مسیریابی ماژول محصولات مثال این سری، یک چنین تعاریفی را پیدا کرده‌است:
const routes: Routes = [
  { path: 'products', component: ProductListComponent },
  {
    path: 'products/:id', component: ProductDetailComponent,
    resolve: { product: ProductResolverService }
  },
  {
    path: 'products/:id/edit', component: ProductEditComponent,
    resolve: { product: ProductResolverService },
    children: [   ]
  }
];
در اینجا می‌توان دو مسیریابی نمایش جزئیات یک محصول و ویرایش و افزودن یک محصول را تبدیل به فرزندان مسیریابی نمایش لیست محصولات کرد. از آنجائیکه Child Routes، سبب توسعه و بسط مسیریابی والد خود می‌شوند، نیاز است مسیرهای مطلق آن‌ها را تبدیل به مسیرهایی نسبی کنیم:
const routes: Routes = [
  {
    path: 'products',
    children: [
      {
        path: '',
        component: ProductListComponent
      },
      {
        path: ':id',
        component: ProductDetailComponent,
        resolve: { product: ProductResolverService }
      },
      {
        path: ':id/edit',
        component: ProductEditComponent,
        resolve: { product: ProductResolverService },
        children: [
          { path: '', redirectTo: 'info', pathMatch: 'full' },
          { path: 'info', component: ProductEditInfoComponent },
          { path: 'tags', component: ProductEditTagsComponent }
        ]
      }
    ]
  }
];
در اینجا کدهای کامل این تغییرات را جهت تعریف یک component-less route مشاهده می‌کنید.
 - ابتدا دو مسیریابی نمایش جزئیات و ویرایش یک محصول، تبدیل به یک گروه، به صورت فرزندان مسیریابی products با تعریف خاصیت children آن شده‌اند.
 - سپس pathهای آن‌ها ویرایش شده و با حذف /product از ابتدای آن‌ها، حالت نسبی را پیدا کرده‌اند.
 - مسیریابی products که والد این مسیریابی‌های فرزند است نیز بدون کامپوننت تعریف شده‌است.
 - کامپوننت مسیریابی products، به عنوان مدیریت کننده‌ی مسیر پیش فرض این فرزندان، تعریف شده‌است.
 
Child routes در درون router-outlet تعریف شده‌ی درون قالب والد آن‌ها نمایش داده می‌شوند (مانند برگه‌های edit info و edit tags قسمت قبل). با توجه به اینکه اکنون دو مسیریابی دیگر، به صورت فرزندان مسیریابی صفحه‌ی نمایش لیست محصولات تعریف شده‌اند، به همین جهت باید یک router-outlet جدید را در درون قالب کامپوننت نمایش لیست محصولات، تعریف کرد. اما نمی‌خواهیم نمایش جزئیات یک محصول و یا صفحه‌ی ویرایش آن‌ها، در همان صفحه‌ی نمایش لیست محصولات ظاهر شوند. برای رفع این مشکل است که نیاز به تعریف یک مسیریابی بدون کامپوننت خواهیم داشت. به همین جهت در تعاریف فوق، تعریف component: ProductListComponent به یکی از مداخل آرایه‌ی children منتقل شده‌است (بجای تعریف آن همانند قبل ذیل مسیریابی products) که دارای path خالی است (یا همان مسیر پیش فرض که در اینجا به products اشاره می‌کند).
در این حالت چون مسیریابی والد، به همراه یک کامپوننت تعریف نشده‌است، مسیریاب، سعی در نمایش فرزندان آن در router-outlet والد نمی‌کند. بجای آن، فرزندان، در یک router-outlet قرار گرفته‌ی در یک سطح بالاتر، نمایش داده می‌شوند که دقیقا همان router-outlet تعریف شده‌ی در فایل قالب src\app\app.component.html است.


کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: angular-routing-lab-05.zip
برای اجرای آن فرض بر این است که پیشتر Angular CLI را نصب کرده‌اید. سپس از طریق خط فرمان به ریشه‌ی پروژه وارد شده و دستور npm install را صادر کنید تا وابستگی‌های آن دریافت و نصب شوند. در آخر با اجرای دستور ng s -o برنامه ساخته شده و در مرورگر پیش فرض سیستم نمایش داده خواهد شد.
مطالب دوره‌ها
نگاهی به اجزای سیستم راهبری بوت استرپ 3
در مطلب «نگاهی به اجزای تعاملی Twitter Bootstrap» مباحث سیستم راهبری بوت استرپ 2 بررسی شدند. در ادامه قصد داریم، نکات جدیدی را در اینباره خصوصا جهت ارتقاء به بوت استرپ 3، بررسی کنیم.


تعیین موقعیت کاربر در صفحه به کمک breadcrumbs در Bootstrap

در مورد قسمت breadcrumb، مطالب مانند قبل و پیشنیاز ذکر شده است. با این تفاوت که بهتر است بجای ul از ol استفاده شود؛ چون ترتیب این عناصر مهم است. ol به معنای ordered list می‌باشد:
            <ol class="breadcrumb">
                <li><a href="#">خانه</a></li>
                <li><a href="#">خدمات</a></li>
                <li class="active">محصولات</li>
            </ol>


یک سؤال: اگر نخواهیم این خطوط مورب ظاهر شوند و برای مثال علاقمند باشیم تا از گلیف آیکن‌های معرفی شده در قسمت قبل استفاده کنیم، چه باید کرد؟
برای این منظور نیاز است با نحوه رندر خطوط مورب در بوت استرپ آشنا شویم. بنابراین فایل bootstrap-rtl.css را گشوده و چند سطر ذیل را جستجو کنید:
.breadcrumb > li + li:before {
  content: "/\00a0";
  padding: 0 5px;
  color: #cccccc;
}
همانطور که ملاحظه می‌کنید، تفسیر عبارت ذکر شده در قسمت content سبب نمایش این خط مورب است. برای حذف آن، به فایل custom.css پروژه مراجعه و تعاریف ذیل را اضافه خواهیم کرد (این فایل همانطور که در قسمت اول ذکر شد، باید پس از ذکر لینک فایل CSS اصلی بوت استرپ، تعریف شود):
.breadcrumb > li + li:before {
  content: none;
}
به این ترتیب خطوط مورب breadcrumb حذف می‌شوند. اکنون برای افزودن گلیف آیکن‌ها به صورت زیر می‌توان عمل کرد:
            <ol class="breadcrumb">
                <li><a href="#">خانه</a> <span class="glyphicon glyphicon-circle-arrow-left"></span></li>
                <li><a href="#">خدمات</a> <span class="glyphicon glyphicon-circle-arrow-left"></span></li>
                <li class="active">محصولات</li>
            </ol>


اگر از رنگ گلیف آیکن‌‌های نمایش داده شده راضی نیستید، آن‌ها را نیز می‌توانید در فایل CSS سفارشی خود تغییر دهید. برای مثال:
.glyphicon {
color: #cdae51;
}



تعریف برگه‌ها در Twitter Bootstrap
در مورد تعریف برگه‌ها، بوت استرپ 3 با نگارش 2 آن، تفاوتی ندارد و تمام نکات مطلب «نگاهی به اجزای تعاملی Twitter Bootstrap» در اینجا نیز صادق هستند. یک ul باید تعریف شود و سپس برای نمونه کلاس‌های nav nav-tabs را به آن‌ها اضافه خواهیم کرد تا به شکل tab به نظر برسند. برگه فعال نیز با کلاس active مشخص می‌شود.
یک نکته جدید: در بوت استرپ 3 می‌توان یک برگه را کاملا در عرض صفحه کشید و امتداد داد:
                <ul class="nav nav-pills nav-justified">
                    <li><a href="#">Home</a></li>
                    <li><a href="#">About</a></li>
                    <li class="active"><a href="#">Services</a></li>
                    <li><a href="#">Photo Gallery</a></li>
                    <li><a href="#">Contact</a></li>
                </ul>
با اضافه کردن nav-justified، بجای شکل زیر


به تصویر ذیل خواهیم رسید که در آن tab، در امتداد صفحه کشیده شده است:




تعریف navbar در بوت استرپ 3

اصول کلی navbar بوت استرپ 3 همانند بوت استرپ 2 است؛ با چند تفاوت کوچک:
- کلاس btn-navbar بوت استرپ 2 به کلاس navbar-btn در بوت استرپ 3 تغییر نام یافته است.
- کلاس navbar-inner بوت استرپ 2 کلا حذف شده است.
- کلاس‌های nav-list به کلاس‌های list-group تغییر نام یافته‌اند.
- کلاس brand با navbar-brand جایگزین شده است.
- کلاس‌های navbar-brand و navbar-toggle باید داخل المانی با کلاس navbar-header محصور شوند.
- کلاس nav باید به همراه navbar-nav باشد.
- کلاس‌های جدید navbar-default  navbar-text  navbar-btn  navbar-header اضافه شده‌اند.

یک مثال:
    <div class="container">
        <h4 class="alert alert-info">
            nav</h4>
        <div class="row">
            <nav class="navbar navbar-default navbar-inverse navbar-fixed-top" role="navigation">
                <div class="navbar-header">
                    <button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#collapse">
                        <span class="sr-only">Toggle navigation</span> <span class="icon-bar"></span><span
                            class="icon-bar"></span><span class="icon-bar"></span>
                    </button>
                </div>
                <div class="collapse navbar-collapse" id="collapse">
                    <ul class="nav navbar-nav">
                        <li><a href="#">Home</a></li>
                        <li><a href="#">About</a></li>
                        <li class="active"><a href="#">Services</a></li>
                        <li><a href="#">Photo Gallery</a></li>
                        <li><a href="#">Contact</a></li>
                    </ul>
                </div>
            </nav>
        </div>
        <!-- end row -->
    </div>
    <!-- /container -->
به همراه CSS سفارشی:
 body { padding-top: 70px; }






توضیحات:
- CSS سفارشی، سبب خواهد شد تا تگ h4 بالای صفحه، زیر navbar مخفی نشده و قابل مشاهده باشد.
- کار با المان nav دارای کلاس navbar navbar-default شروع می‌شود. همچنین role=navigation نیز به این المان اضافه شده‌است. این مورد  کمکی خواهد بود به افرادی با قدرت بینایی کم که از screen readers استفاده می‌کنند.
- داخل navbar-default یک div دیگر با کلاس navbar-header اضافه شده است. این مورد قابلیت‌های واکنشگرای navbar را فراهم می‌سازد. به این ترتیب زمانیکه عرض صفحه کمتر می‌شود، مانند تصویر دوم، یک دکمه ویژه نمایش داده خواهد شد.
- هرجایی در بوت استرپ ویژگی‌های data- را ملاحظه کردید، یعنی قرار است اطلاعاتی در اختیار اجزای جاوا اسکریپتی آن قرار گیرند. برای نمونه data-target به یک div با آی دی مساوی collapse اشاره می‌کند. به این معنا که اگر در زمان کم بودن عرض صفحه، دکمه ویژه واکنشگرای navbar ظاهر شد، با کلیک بر روی آن دکمه، div یاد شده را نمایش بده.
- span تعریف شده با کلاس sr-only به معنای اطلاعاتی است که صرفا جهت screen readers تدارک دیده شده‌اند.


فایل‌های نهایی این قسمت را از اینجا نیز می‌توانید دریافت کنید:
bs3-sample03.zip
 
مطالب
طراحی یک گرید با Angular و ASP.NET Core - قسمت دوم - پیاده سازی سمت کلاینت
در قسمت قبل، کار پیاده سازی سمت سرور نمایش اطلاعات یک گرید، به پایان رسید. در این قسمت می‌خواهیم از سمت کلاینت، اطلاعات صفحه بندی و مرتب سازی را به سمت سرور ارسال کرده و همچنین نتیجه‌ی دریافتی از سرور را نمایش دهیم.



پیشنیازهای نمایش اطلاعات گرید به همراه صفحه بندی اطلاعات

در مطلب «Angular CLI - قسمت ششم - استفاده از کتابخانه‌های ثالث» نحوه‌ی نصب و معرفی کتابخانه‌ی ngx-bootstrap را بررسی کردیم. دقیقا همان مراحل، در اینجا نیز باید طی شوند و از این مجموعه تنها به کامپوننت Pagination آن نیاز داریم. همان قسمت ذیل گرید تصویر فوق که شماره صفحات را جهت انتخاب، نمایش داده‌است.
بنابراین ابتدا فرض بر این است که دو بسته‌ی بوت استرپ و ngx-bootstrap را نصب کرده‌اید:
> npm install bootstrap --save
> npm install ngx-bootstrap --save
در فایل angular-cli.json. شیوه‌نامه‌ی بوت استرپ را نیز افزوده‌اید:
  "apps": [
    {
      "styles": [
    "../node_modules/bootstrap/dist/css/bootstrap.min.css",
        "styles.css"
      ],
پس از آن باید به‌خاطر داشت که کامپوننت نمایش صفحه بندی این مجموعه PaginationModule نام دارد و باید در نزدیک‌ترین ماژول مورد نیاز، ثبت و معرفی شود:
import { PaginationModule } from "ngx-bootstrap";

@NgModule({
  imports: [
    PaginationModule.forRoot()
  ]
برای نمونه در این مثال، ماژولی به نام simple-grid.module.ts دربرگیرنده‌ی گرید مطلب جاری است و به صورت ذیل به برنامه اضافه شده‌است:
 >ng g m SimpleGrid -m app.module --routing
بنابراین تعریف PaginationModule باید به قسمت imports این ماژول اضافه شود و تعریف آن در app.module.ts تاثیری بر روی این قسمت نخواهد داشت.

کامپوننتی هم که مثال جاری را نمایش می‌دهد به صورت ذیل به ماژول SimpleGrid فوق اضافه شده‌است:
 >ng g c SimpleGrid/products-list


تهیه معادل‌های قراردادهای سمت سرور در سمت Angular

در قسمت قبل، تعدادی قرارداد مانند پارامترهای دریافتی از سمت کلاینت و ساختار اطلاعات ارسالی به سمت کلاینت را تعریف کردیم. اکنون جهت کار strongly typed با آن‌ها در سمت یک برنامه‌ی تایپ اسکریپتی Angular، کلاس‌های معادل آن‌ها را تهیه می‌کنیم.

ساختار شیء محصول دریافتی از سمت سرور
 >ng g cl SimpleGrid/app-product
با این محتوا
export class AppProduct {
  constructor(
    public productId: number,
    public productName: string,
    public price: number,
    public isAvailable: boolean
  ) {}
}
که در اینجا هر کدام از خواص ذکر شده، معادل camel case نمونه‌ی سمت سرور خود هستند (چون JSON.NET در ASP.NET Core، به صورت پیش فرض یک چنین خروجی را تولید می‌کند).

ساختار معادل پارامترهای صفحه بندی و مرتب سازی ارسالی به سمت سرور
 >ng g cl SimpleGrid/PagedQueryModel
با این محتوا
export class PagedQueryModel {
  constructor(
    public sortBy: string,
    public isAscending: boolean,
    public page: number,
    public pageSize: number
  ) {}
}
در اینجا همان ساختار IPagedQueryModel سمت سرور را مشاهده می‌کنید. از آن جهت مشخص سازی جزئیات صفحه بندی و نحوه‌ی مرتب سازی اطلاعات، استفاده می‌شود.

ساختار معادل اطلاعات صفحه بندی شده‌ی دریافتی از سمت سرور
 >ng g cl SimpleGrid/PagedQueryResult
با این محتوا
export class PagedQueryResult<T> {
  constructor(public totalItems: number, public items: T[]) {}
}
این ساختار جنریک نیز دقیقا معادل همان PagedQueryResult سمت سرور است و حاوی تعداد کل ردیف‌های یک کوئری و تنها قسمتی از اطلاعات صفحه بندی شده‌ی آن می‌باشد.

ساختار ستون‌های گرید نمایشی
 >ng g cl SimpleGrid/GridColumn
با این محتوا
export class GridColumn {
  constructor(
    public title: string,
    public propertyName: string,
    public isSortable: boolean
  ) {}
}
هر ستون نمایش داده شده، دارای یک برچسب، خاصیتی مشخص در سمت سرور و بیانگر قابلیت مرتب سازی آن می‌باشد. اگر isSortable به true تنظیم شود، با کلیک بر روی سرستون‌ها می‌توان اطلاعات را بر اساس آن ستون، مرتب سازی کرد.


تهیه سرویس ارسال اطلاعات صفحه بندی به سرور و دریافت اطلاعات از آن

پس از تدارک این مقدمات، اکنون کار تعریف سرویسی که این اطلاعات را به سمت سرور ارسال می‌کند و نتیجه را باز می‌گرداند، به صورت ذیل خواهد بود:
 >ng g s SimpleGrid/products-list -m simple-grid.module
این دستور سبب ایجاد کلاس ProductsListService شده و همچنین قسمت providers ماژول simple-grid را نیز بر این اساس به روز رسانی می‌کند.
پیش از تکمیل این سرویس، نیاز است متدی را جهت تبدیل یک شیء، به معادل کوئری استرینگ آن تهیه کنیم:
  toQueryString(obj: any): string {
    const parts = [];
    for (const key in obj) {
      if (obj.hasOwnProperty(key)) {
        const value = obj[key];
        if (value !== null && value !== undefined) {
          parts.push(encodeURIComponent(key) + "=" + encodeURIComponent(value));
        }
      }
    }
    return parts.join("&");
  }
در قسمت قبل امضای متد GetPagedProducts دارای ویژگی HttpGet است. بنابراین، نیاز است اطلاعات را به صورت کوئری استرینگ از سمت کلاینت دریافت کند و متد toQueryString فوق به صورت خودکار بر روی تمام خواص یک شیء دلخواه حرکت کرده و آن‌ها را تبدیل به یک رشته‌ی حاوی کوئری استرینگ‌ها می‌کند.
[HttpGet("[action]")]
public PagedQueryResult<Product> GetPagedProducts(ProductQueryViewModel queryModel)
برای نمونه متد toQueryString فوق است که سبب ارسال یک چنین درخواستی به سمت سرور می‌شود:
 http://localhost:5000/api/Product/GetPagedProducts?sortBy=productId&isAscending=true&page=2&pageSize=7

پس از این تعریف، سرویس ProductsListService  به صورت ذیل تکمیل خواهد شد:
@Injectable()
export class ProductsListService {
  private baseUrl = "api/Product";

  constructor(private http: Http) {}

  getPagedProductsList(
    queryModel: PagedQueryModel
  ): Observable<PagedQueryResult<AppProduct>> {
    return this.http
      .get(`${this.baseUrl}/GetPagedProducts?${this.toQueryString(queryModel)}`)
      .map(res => {
        const result = res.json();
        return new PagedQueryResult<AppProduct>(
          result.totalItems,
          result.items
        );
      });
  }
در اینجا از متد toQueryString، جهت تکمیل متد get ارسالی به سمت سرور استفاده شده‌است تا پارامترها را به صورت کوئری استرینگ‌ها تبدیل کرده و ارسال کند.
سپس در متد map آن، res.json دقیقا همان ساختار PagedQueryResult سمت سرور را به همراه دارد. اینجا است که فرصت خواهیم داشت نمونه‌ی سمت کلاینت آن‌را که در ابتدای بحث تهیه کردیم، وهله سازی کرده و بازگشت دهیم (نگاشت فیلدهای دریافتی از سمت سرور به سمت کلاینت).


تکمیل کامپوننت نمایش گرید

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


  <table class="table table-striped table-hover table-bordered table-condensed">
    <thead>
      <tr>
        <th class="text-center" style="width:3%">#</th>
        <th *ngFor="let column of columns" class="text-center">
          <div *ngIf="column.isSortable" (click)="sortBy(column.propertyName)" style="cursor: pointer">
            {{ column.title }}
            <i *ngIf="queryModel.sortBy === column.propertyName" class="glyphicon"
              [class.glyphicon-sort-by-order]="queryModel.isAscending" [class.glyphicon-sort-by-order-alt]="!queryModel.isAscending"></i>
          </div>
          <div *ngIf="!column.isSortable" style="cursor: pointer">
            {{ column.title }}
          </div>
        </th>
      </tr>
    </thead>
در اینجا ابتدا بررسی می‌شود که آیا یک ستون قابلیت مرتب سازی را دارد، یا خیر؟ اگر اینطور است، در کنار آن یک گلیف آیکن مرتب سازی درج می‌شود. اگر خیر، صرفا متن عنوان آن نمایش داده خواهد شد. می‌شد تمام این موارد را به ازای هر ستون به صورت مجزایی ارائه داد، اما در این حالت به کدهای تکراری زیادی می‌رسیدیم. به همین جهت از یک حلقه بر روی تعریف ستون‌های این گرید استفاده شده‌است. آرایه‌ی این ستون‌ها نیز به صورت ذیل تعریف می‌شود:
export class ProductsListComponent implements OnInit {
  columns: GridColumn[] = [
    new GridColumn("Id", "productId", true),
    new GridColumn("Name", "productName", true),
    new GridColumn("Price", "price", true),
    new GridColumn("Available", "isAvailable", true)
  ];

همچنین در کدهای قالب این کامپوننت، مدیریت کلیک بر روی یک سر ستون را نیز مشاهده می‌کنید:
export class ProductsListComponent implements OnInit {
  itemsPerPage = 7;
  queryModel = new PagedQueryModel("productId", true, 1, this.itemsPerPage);

  sortBy(columnName) {
    if (this.queryModel.sortBy === columnName) {
      this.queryModel.isAscending = !this.queryModel.isAscending;
    } else {
      this.queryModel.sortBy = columnName;
      this.queryModel.isAscending = true;
    }
    this.getPagedProductsList();
  }
}
در این‌حالت اگر ستونی که بر روی آن کلیک شده، پیشتر مرتب سازی شده‌است، صرفا خاصیت صعودی بودن آن برعکس خواهد شد. در غیراینصورت، نام خاصیت درخواستی مرتب سازی و جهت آن نیز مشخص می‌شود. سپس مجددا این گرید توسط متد getPagedProductsList رندر خواهد شد.

کار رندر بدنه‌ی اصلی گرید توسط همین چند سطر در قالب آن مدیریت می‌شود:
    <tbody>
      <tr *ngFor="let item of queryResult.items; let i = index">
        <td class="text-center">{{ itemsPerPage * (currentPage - 1) + i + 1 }}</td>
        <td class="text-center">{{ item.productId }}</td>
        <td class="text-center">{{ item.productName }}</td>
        <td class="text-center">{{ item.price | number:'.0' }}</td>
        <td class="text-center">
          <input id="item-{{ item.productId }}" type="checkbox" [checked]="item.isAvailable"
            disabled="disabled" />
        </td>
      </tr>
    </tbody>
  </table>
اولین ستون آن، اندکی ابتکاری است. در اینجا شماره ردیف‌های خودکاری در هر صفحه درج خواهند شد. این شماره ردیف نیز جزو ستون‌های منبع داده‌ی فرضی برنامه نیست. به همین جهت برای درج آن، توسط let i = index در ngFor، به شماره ایندکس ردیف جاری دسترسی پیدا می‌کنیم. سپس توسط محاسباتی بر اساس تعداد ردیف‌های هر صفحه و شماره‌ی صفحه‌ی جاری، می‌توان شماره ردیف فعلی را محاسبه کرد.

در اینجا حلقه‌ای بر روی queryResult.items تشکیل شده‌است. این منبع داده به صورت ذیل در کامپوننت متناظر مقدار دهی می‌شود:
export class ProductsListComponent implements OnInit {
  itemsPerPage = 7;
  currentPage: number;
  numberOfPages: number;
  isLoading = false;
  queryModel = new PagedQueryModel("productId", true, 1, this.itemsPerPage);
  queryResult = new PagedQueryResult<AppProduct>(0, []);

  constructor(private productsListService: ProductsListService) {}

  ngOnInit() {
    this.getPagedProductsList();
  }

  private getPagedProductsList() {
    this.isLoading = true;
    this.productsListService
      .getPagedProductsList(this.queryModel)
      .subscribe(result => {
        this.queryResult = result;
        this.isLoading = false;
      });
  }
}
ابتدا سرویس ProductsListService را که در ابتدای بحث تکمیل شد، به سازنده‌ی این کامپوننت تزریق می‌کنیم. به کمک آن می‌توان در متد getPagedProductsList، ابتدا queryModel جاری را که شامل اطلاعات مرتب سازی و صفحه بندی است، به سرور ارسال کرده و سپس نتیجه‌ی نهایی را به queryResult انتساب دهیم. به این ترتیب تعداد کل رکوردها و همچنین آیتم‌های صفحه‌ی جاری دریافت می‌شوند. اکنون حلقه‌ی ngFor نمایش بدنه‌ی گرید، کار تکمیل صفحه‌ی جاری را انجام خواهد داد.

قسمت آخر کار، افزودن کامپوننت نمایش شماره صفحات است:


  <div align="center">
    <pagination [maxSize]="8" [boundaryLinks]="true" [totalItems]="queryResult.totalItems"
      [rotate]="false" previousText="&lsaquo;" nextText="&rsaquo;" firstText="&laquo;"
      lastText="&raquo;" (numPages)="numberOfPages = $event" [(ngModel)]="currentPage"
      (pageChanged)="onPageChange($event)"></pagination>
  </div>
  <pre class="card card-block card-header">Page: {{currentPage}} / {{numberOfPages}}</pre>
در اینجا از کامپوننت pagination مجموعه‌ی ngx-bootstarp استفاده شده‌است و یک سری از خواص مستند شده‌ی آن‌، مقدار دهی شده‌اند؛ مانند متن‌های صفحه‌ی بعد و قبل و امثال آن. مدیریت کلیک بر روی شماره‌های آن، در کامپوننت جاری به صورت ذیل است:
export class ProductsListComponent implements OnInit {
  itemsPerPage = 7;
  currentPage: number;
  numberOfPages: number;

  onPageChange(event: any) {
    this.queryModel.page = event.page;
    this.getPagedProductsList();
  }
}
علت تعریف دو خاصیت اضافه‌ی currentPage و numberOfPages، استفاده‌ی از آن‌ها در قسمت ذیل این شماره‌ها (خارج از کامپوننت نمایش شماره صفحات) جهت نمایش page 1/x است.
هر زمانیکه کاربر بر روی شما‌ره‌ای کلیک می‌کند، رخ‌داد onPageChange فراخوانی شده و در این‌حالت تنها کافی است شماره صفحه‌ی درخواستی queryModel جاری را به روزرسانی کرده و سپس آن‌را در اختیار متد getPagedProductsList جهت دریافت اطلاعات این صفحه‌ی درخواستی قرار دهیم.


کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید.
مطالب
تنظیمات CORS در ASP.NET Core
برنامه‌های امروزی، ممکن است به چندین Web API مستقل، تبدیل شده و سپس برنامه‌هایی (Front-ends) جدای از آن‌ها برای کار با آن‌ها ایجاد شوند. بنابراین این وظیفه‌ی برنامه‌‌های Web API است که مطمئن شوند کلاینت‌ها قادر به تعامل با آن‌ها هستند. CORS استانداردی است که یک چنین امکانی را مهیا می‌کند.



CORS چیست؟

CORS و یا cross origin resource sharing، یک مکانیزم امنیتی است که در تمام مرورگرهای جدید جهت جلوگیری از ارسال اطلاعات یک وب سایت، به وب سایتی دیگر، درنظر گرفته شده‌است. درخواست دسترسی به یک منبع (Resource) مانند تصویر، داده و غیره، خارج از سرآغاز آن، یک درخواست cross-origin نامیده می‌شود. برای مدیریت یک چنین درخواست‌هایی، استانداردی به نام CORS طراحی شده‌است. می‌توان به آن مانند نگهبانی یک ساختمان نگاه کرد که تا مجوز خاصی به آن‌ها ارائه نشود، امکان دسترسی به منابع ساختمان را صادر نخواهند کرد.


Origin چیست؟

سرآغاز یک درخواست از سه قسمت تشکیل می‌شود:
- Protocol/Scheme مانند HTTP/HTTPS
- Host مانند نام دومین
- شماره پورت مانند 443 برای پروتکل HTTPS  یا پورت 80 برای HTTP که عموما هر دو مورد به علت پیش‌فرض بودن، ذکر نمی‌شوند

بنابراین URLای مانند https://www.dntips.ir یک Origin را مشخص می‌کند. در اینجا به تمام منابعی که از این سرآغاز شروع می‌شوند و سه قسمت یاد شده‌ی آن‌ها یکی است، same-origin گفته می‌شود.
در این حالت اگر منابعی به URLهایی مانند https://www.dntips.ir (پروتکل متفاوت) و یا https://github.com (با host متفاوت) اشاره کنند، به آن‌ها Different-Origin گفته خواهد شد.


سیاست امنیتی Same-Origin چیست؟

سیاست امنیتی Same-Origin که به صورت توکار در تمام مرورگرهای امروزی تعبیه شده‌است، مانع تعامل سرآغازهایی متفاوت، جهت جلوگیری از حملات امنیتی مانند Cross-Site Request Forgery یا CSRF می‌شود. این سیاست امنیتی بسیار محدود کننده‌است و برای مثال مانع این می‌شود که بتوان توسط کدهای JavaScript ای، درخواستی را به سرآغاز دیگری ارسال کرد؛ حتی اگر بدانیم درخواست رسیده از منبعی مورد اطمینان صادر شده‌است. برای مثال اگر سایت جاری یک درخواست Ajax ای را به https://anotherwebsite.com/api/users ارسال کند، چون قسمت host مربوط به origin آن‌ها یکی نیست، این عملیات توسط مرورگر غیرمجاز شمرده شده و مسدود می‌شود.


چگونه می‌توان از تنظیمات CORS، برای رفع محدودیت‌های سیاست‌های دسترسی Same-Origin استفاده کرد؟

استاندارد CORS تعدادی header ویژه را جهت تبادل اطلاعات بین سرور و مرورگر مشخص کرده‌است تا توسط آن‌ها بتوان منابع را بین سرآغازهای متفاوتی به اشتراک گذاشت. در این حالت ابتدا کلاینت درخواستی را به سمت سرور ارسال می‌کند. سپس سرور پاسخی را به همراه هدرهای ویژه‌ای که مشخص می‌کنند به چه منابعی و چگونه می‌توان دسترسی یافت، به سمت کلاینت ارسال خواهد کرد. اکثر این هدرها با Access-Control-Allow شروع می‌شوند:
• Access-Control-Allow-Origin
در آن لیست سرآغازهایی را که سرور مایل است منابع خود را با آن‌ها به اشتراک بگذارد، مشخص می‌شوند. در حالت توسعه‌ی برنامه می‌توان از مقدار * نیز برای آن استفاده کرد تا هر سرآغازی بتواند به منابع سرور دسترسی پیدا کند. اما از استفاده‌ی این مقدار سراسری و کلی، در حالت انتشار برنامه خودداری کنید.
• Access-Control-Allow-Headers
• Access-Control-Allow-Methods
• Access-Control-Allow-Credentials


درخواست‌های ویژه‌ی Pre-Flight

در بسیاری از موارد، مرورگر زمانیکه تشخیص می‌دهد درخواست صادر شده‌ی از طرف برنامه، قرار است به یک Origin دیگر ارسال شود، خودش یک درخواست ویژه را به نام Pre-Flight و از نوع OPTIONS به سمت سرور ارسال می‌کند. علت آن این است که سرورهای قدیمی، مفهوم CORS را درک نمی‌کنند و در برابر درخواست‌های ویژه‌ای مانند Delete که از سرور جاری صادر نشده باشند، مقاومت می‌کنند (صرفا درخواست‌های Same-Origin را پردازش می‌کنند). به همین جهت است که اگر به برگه‌ی network ابزارهای توسعه دهندگان مرورگر خود دقت کنید، درخواست‌های cross-origin به نظر دوبار ارسال شده‌اند. بار اول درخواستی که method آن، options است و در خواست دوم که درخواست اصلی است و برای مثال method آن put است. این درخواست اول، Pre-Flight Request نام دارد. هدف آن این است که بررسی کند آیا سرور مقصد، استاندارد CORS را متوجه می‌شود یا خیر و آیا اینقدر قدیمی است که صرفا درخواست‌های Same-Origin را پردازش می‌کند و مابقی را مسدود خواهد کرد. اگر سرور درخواست Pre-Flight را درک نکند، مرورگر درخواست اصلی را صادر نخواهد کرد.



البته درخواست‌های pre-flight بیشتر در حالت‌های زیر توسط مرورگر صادر می‌شوند:
- اگر متد اجرایی مدنظر، متدی بجز GET/POST/HEAD باشد.
- اگر content type درخواست‌های از نوع POST، چیزی بجز application/x-www-form-urlencoded, multipart/form-data و یا text/plain باشند.
- اگر درخواست، به همراه یک هدر سفارشی باشد نیز سبب صدور یک pre-flight request خواهد شد.

بنابراین در برنامه‌های SPA خود که اطلاعات را با فرمت JSON به سمت یک Web API ارسال می‌کنند (و Origin آن‌ها یکی نیست)، حتما شاهد درخواست‌های Pre-Flight نیز خواهید بود.


تنظیمات CORS در ASP.NET Core

اکنون که با مفهوم CORS آشنا شدیم، در ادامه به نحوه‌ی انجام تنظیمات آن در برنامه‌های ASP.NET Core خواهیم پرداخت.
تنظیمات ابتدایی CORS در فایل Startup.cs و متد ConfigureServices آن انجام می‌شوند:
public void ConfigureServices(IServiceCollection services) 
{ 
    // Add service and create Policy with options 
    services.AddCors(options => 
    { 
        options.AddPolicy("CorsPolicy", 
            builder => builder.AllowAnyOrigin() 
            .AllowAnyMethod() 
            .AllowAnyHeader() 
            .AllowCredentials() ); 
    }); 
     
    // Make sure MVC is added AFTER CORS 
    services.AddMvc(); 
}
ابتدا توسط متد AddCors، سرویس‌های مرتبط، به سیستم تزریق وابستگی‌های ASP.NET Core اضافه خواهند شد و سپس در قسمت تنظیمات آن می‌توان همان هدرهای Access-Control-Allow را که پیشتر در مورد آن‌ها بحث کردیم، تعریف و مقدار دهی کرد. برای مثال تنظیم AllowAnyOrigin، همان استفاده‌ی از مقدار * در هدر Access-Control-Allow-Origin تولیدی است. باید دقت داشت که تنظیمات این سرویس‌ها باید پیش از افزودن سرویس‌های MVC انجام شوند.
در اینجا CorsPolicy که به عنوان پارامتر مشخص شده‌است، نام این سیاست دسترسی سفارشی است و در قسمت‌های مختلف برنامه می‌توان ارجاعاتی را به این نام تعریف کرد.

و برای تعریف سرآغازی خاص و یا متدی مشخص، می‌توان به صورت زیر عمل کرد (ذکر صریح WithOrigins برای حالت توزیع برنامه مناسب است و از دیدگاه امنیتی، استفاده‌ی از AllowAnyOrigin کار صحیحی نیست ):
services.AddCors(options =>
{
    options.AddPolicy("AllowOrigin",
            builder => builder.WithOrigins("http://localhost:55294")
                              .WithMethods("GET", "POST", "HEAD"));
});

پس از تنظیم سیاست دسترسی مدنظر، اکنون نوبت به اعمال آن است. اینکار در متد Configure فایل Startup.cs انجام می‌شود:
public void Configure(IApplicationBuilder app) 
{ 
    // ... 
 
    app.UseCors("CorsPolicy"); 
     
    // ... 
     
    app.UseMvc(routes => 
    { 
        routes.MapRoute( 
            name: "default", 
            template: "{controller=Home}/{action=Index}/{id?}"); 
    }); 
}
در اینجا متد UseCors که میان‌افزار CORS را فعال می‌کند، باید پیش‌از میان‌افزار Mvc تعریف شود تا بتواند هدرها را اعمال کند. همچنین نامی که در اینجا به عنوان پارامتر آن استفاده شده، دقیقا به همان نامی که برای تنظیمات سیاست دسترسی درنظر گرفته شد، اشاره می‌کند.
متد app.UseCors، یک متد سراسری است و کل سیستم را داخل این سیاست دسترسی CORS قرار می‌دهد. اگر می‌خواهید صرفا به کنترلر خاصی این تنظیمات اعمال شوند، می‌توان از فیلتر EnableCors با ذکر نام سیاست دسترسی تعریف شده، استفاده کرد (در این حالت ذکر services.AddCors ضروری است و از ذکر app.UseCors صرفنظر می‌شود):
[EnableCors("CorsPolicy")]
public class MyApiController : Controller

و یا اگر می‌خواهید از app.UseCors سراسری استفاده کنید، اما آن‌را برای کنترلر و یا حتی اکشن متد خاصی غیرفعال نمائید، می‌توان از فیلتر [DisableCors] استفاده کرد.

چند نکته:
- به نام رشته‌ای سیاست دسترسی تعریف شده، دقت داشته باشید. اگر این نام را درست ذکر نکنید، هدری تولید نخواهد شد.
- میان‌افزار CORS، برای درخواست‌های same-origin نیز هدری را تولید نمی‌کند.


امکان تعریف سیاست‌های دسترسی بدون نام نیز وجود دارد

در هر دو روش فوق که یکی بر اساس app.UseCors سراسری و دیگری بر اساس فیلتر EnableCors اختصاصی کار می‌کنند، ذکر نام سیاست دسترسی options.AddPolicy ضروری است. اگر علاقه‌ای به ذکر این نام ندارید، می‌توان به طور کامل از تنظیم services.AddCors صرفنظر کرد (قسمت پارامتر و تنظیمات آن‌را ذکر نکرد) و متد app.UseCors را به صورت زیر تنظیم نمود که قابلیت داشتن تنظیمات خاص خود را نیز دارا است:
public void ConfigureServices(IServiceCollection services)
{
      services.AddCors();
      services.AddMvc();
}

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
 
    app.UseCors(policyBuilder =>
    {
        string[] origins = new string[] { "http://localhost:2000", "http://localhost:2001" };
 
        policyBuilder
            .AllowAnyHeader()
            .AllowAnyMethod()
            .WithOrigins(origins);
    });
 
    app.UseMvc();
}

روش دیگر انجام اینکار، تعریف یک DefaultPolicy است:
public void ConfigureServices(IServiceCollection services)
{
    services.AddCors(options =>
    {
        options.AddDefaultPolicy(
            builder =>
            {
            builder
                .AllowAnyOrigin()
                .AllowAnyMethod()
                .AllowAnyHeader();
            });
          options.AddPolicy("MyCORSPolicy",
            builder =>
            {
                builder.WithOrigins("http://localhost:49373")
                                    .AllowAnyHeader()
                                    .AllowAnyMethod();
            });
     });
    services.AddMvc().AddNewtonsoftJson();
}
در اینجا در متد AddCors می‌توان چندین AddPolicy نامدار را داشت، به همراه یک AddDefaultPolicy بدون نام. استفاده‌ی از سیاست نام‌دار تعریف شده، یا توسط متد سراسری app.UseCors("MyCORSPolicy") انجام می‌شود و یا توسط فیلتر [EnableCors("MyCORSPolicy")]. اما اگر فیلتر [EnableCors] را بدون ذکر نامی قید کنیم، از همان تنظیمات AddDefaultPolicy استفاده خواهد کرد.


خطاهای متداول حین کار با CORS

خطای کار با SignalR و ارسال اطلاعات اعتبارسنجی کاربر

Access to XMLHttpRequest at 'http://localhost:22135/chat/negotiate?jwt=<removed the jwt key>' 
from origin 'http://localhost:53150' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: 
The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include'. 
The credentials mode of requests initiated by the XMLHttpRequest is controlled by the withCredentials attribute.
برای رفع این مشکل کافی است متد AllowCredentials نیز قید شود:
services.AddCors(options =>
{
     options.AddPolicy("AllowAllOrigins",
         builder =>
          {
              builder
                 .AllowAnyOrigin()
                 .AllowAnyHeader()
                 .AllowAnyMethod()
                 .AllowCredentials();
          });
    });

تمام اعتبارسنجی‌ها و اطلاعات کوکی‌ها در سمت سرور قابل دسترسی نیستند

CORS به صورت پیش‌فرض اطلاعات کوکی‌ها را به دومینی دیگر ارسال نمی‌کند. در این حالت اگر حتما نیاز به انجام اینکار است، باید در درخواست Ajax ای ارسالی، خاصیت withCredentials به true تنظیم شود. همچنین سمت سرور نیز هدر Access-Control-Allow-Credentials را تنظیم کند (همان متد AllowCredentials فوق). در اینجا Credentials به کوکی‌ها، هدرهای اعتبارسنجی (مانند هدرهای JWT) کاربران و یا مجوزهای TLS کلاینت‌ها اشاره می‌کند.
var xhr = new XMLHttpRequest();
xhr.open('get', 'http://www.example.com/api/test');
xhr.withCredentials = true;

//In jQuery:
$.ajax({
  type: 'get',
  url: 'http://www.example.com/home',
  xhrFields: {
    withCredentials: true
}

//ES6 fetch API:
fetch(
   'http://remote-url.com/users',
   {
     'PUT',
     headers: {
          'Accept': 'application/json',
          'Content-Type': 'application/json'
     },
     mode: 'cors',
     credentials: 'include',
     body: JSON.stringify(body)
   }
)
.then((response) => response.json())
.then((response) => console.log(response))
.catch((error) => console.error(error));

CORS حساس به حروف کوچک و بزرگ است

روش دیگر تنظیم فیلتر EnableCors را در اینجا مشاهده می‌کنید:
[EnableCors(origins: "http://MyWebsite.com", headers: "*", methods: "*")]
//is not the same as this:
[EnableCors(origins:"http://mywebsite.com", headers: "*", methods: "*")]
اگر بجای * دقیقا origins را مشخص کردید، بخاطر داشته باشید که این تنظیم به کوچکی و بزرگی حروف حساس است و در صورت اشتباهی (مانند ذکر MyWebSite بجای mywebsite متداول با حروف کوچک)، درخواست‌های رسیده مسدود خواهند شد. همچنین همانطور که ذکر شد، بین HTTP و HTTPS نیز تفاوت وجود دارد و origin این‌ها یکی درنظر گرفته نمی‌شود و حتی اگر تمام حروف را هم کوچک وارد کرده باشید، باز هم تنظیمات جداگانه‌ای را نیاز خواهند داشت.


در حالت بروز خطای مدیریت نشده‌ای در سمت سرور، ASP.NET Core هدرهای CORS را ارسال نمی‌کند
اطلاعات بیشتر 
مطالب
استفاده از Full text search توسط Entity Framework
پیشنیاز مطلب:
پشتیبانی از Full Text Search در SQL Server

Full Text Search یا به اختصار FTS یکی از قابلیت‌های SQL Server جهت جستجوی پیشرفته در متون میباشد. این قابلیت تا کنون در EF 6.1.1 ایجاد نشده است.
در ادامه پیاده سازی از FTS در EF را مشاهده مینمایید.
جهت ایجاد قابلیت FTS از متد Contains در Linq استفاده شده است.
ابتدا متد‌های الحاقی جهت اعمال دستورات FREETEXT و CONTAINS اضافه میشود.سپس دستورات تولیدی EF را قبل از اجرا بر روی بانک اطلاعاتی توسط امکان Command Interception به دستورات FTS تغییر میدهیم.
همانطور که میدانید دستور Contains در Linq توسط EF به دستور LIKE تبدیل میشود. به جهت اینکه ممکن است بخواهیم از دستور LIKE نیز استفاده کنیم یک پیشوند به مقادیری که میخواهیم به دستورات FTS تبدیل شوند اضافه مینماییم.
جهت استفاده از Fts در EF کلاس‌های زیر را ایجاد نمایید.
 
کلاس FullTextPrefixes :
/// <summary>
    /// 
    /// </summary>
    public static class FullTextPrefixes
    {
        /// <summary>
        /// 
        /// </summary>
        public const string ContainsPrefix = "-CONTAINS-";

        /// <summary>
        /// 
        /// </summary>
        public const string FreetextPrefix = "-FREETEXT-";

        /// <summary>
        /// 
        /// </summary>
        /// <param name="searchTerm"></param>
        /// <returns></returns>
        public static string Contains(string searchTerm)
        {
            return string.Format("({0}{1})", ContainsPrefix, searchTerm);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="searchTerm"></param>
        /// <returns></returns>
        public static string Freetext(string searchTerm)
        {
            return string.Format("({0}{1})", FreetextPrefix, searchTerm);
        }

    }

کلاس جاری جهت علامت گذاری دستورات FTS میباشد و توسط متد‌های الحاقی و کلاس FtsInterceptor استفاده میگردد.

کلاس FullTextSearchExtensions :
public static class FullTextSearchExtensions
    {

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="TEntity"></typeparam>
        /// <param name="source"></param>
        /// <param name="expression"></param>
        /// <param name="searchTerm"></param>
        /// <returns></returns>
        public static IQueryable<TEntity> FreeTextSearch<TEntity>(this IQueryable<TEntity> source, Expression<Func<TEntity, object>> expression, string searchTerm) where TEntity : class
        {
            return FreeTextSearchImp(source, expression, FullTextPrefixes.Freetext(searchTerm));
        }

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="TEntity"></typeparam>
        /// <param name="source"></param>
        /// <param name="expression"></param>
        /// <param name="searchTerm"></param>
        /// <returns></returns>
        public static IQueryable<TEntity> ContainsSearch<TEntity>(this IQueryable<TEntity> source, Expression<Func<TEntity, object>> expression, string searchTerm) where TEntity : class
        {
            return FreeTextSearchImp(source, expression, FullTextPrefixes.Contains(searchTerm));
        }

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="TEntity"></typeparam>
        /// <param name="source"></param>
        /// <param name="expression"></param>
        /// <param name="searchTerm"></param>
        /// <returns></returns>
        private static IQueryable<TEntity> FreeTextSearchImp<TEntity>(this IQueryable<TEntity> source, Expression<Func<TEntity, object>> expression, string searchTerm)
        {
            if (String.IsNullOrEmpty(searchTerm))
            {
                return source;
            }

            // The below represents the following lamda:
            // source.Where(x => x.[property].Contains(searchTerm))

            //Create expression to represent x.[property].Contains(searchTerm)
            //var searchTermExpression = Expression.Constant(searchTerm);
            var searchTermExpression = Expression.Property(Expression.Constant(new { Value = searchTerm }), "Value");
            var checkContainsExpression = Expression.Call(expression.Body, typeof(string).GetMethod("Contains"), searchTermExpression);

            //Join not null and contains expressions

            var methodCallExpression = Expression.Call(typeof(Queryable),
                                                       "Where",
                                                       new[] { source.ElementType },
                                                       source.Expression,
                                                       Expression.Lambda<Func<TEntity, bool>>(checkContainsExpression, expression.Parameters));

            return source.Provider.CreateQuery<TEntity>(methodCallExpression);
        }

در این کلاس متدهای الحاقی جهت اعمال قابلیت Fts ایجاد شده است.
متد FreeTextSearch جهت استفاده از دستور FREETEXT  استفاده میشود.در این متد پیشوند -FREETEXT- جهت علامت گذاری این دستور به ابتدای مقدار جستجو اضافه میشود. این متد دارای دو پارامتر میباشد ، اولی ستونی که میخواهیم بر روی آن جستجوی FTS انجام دهیم و دومی عبارت جستجو.
متد ContainsSearch جهت استفاده از دستور CONTAINS استفاده میشود.در این متد پیشوند -CONTAINS- جهت علامت گذاری این دستور به ابتدای مقدار جستجو اضافه میشود. این متد دارای دو پارامتر میباشد ، اولی ستونی که میخواهیم بر روی آن جستجوی FTS انجام دهیم و دومی عبارت جستجو. 
متد FreeTextSearchImp جهت ایجاد دستور Contains در Linq میباشد.
 
کلاس FtsInterceptor :
 public class FtsInterceptor : IDbCommandInterceptor
    {

        /// <summary>
        /// 
        /// </summary>
        /// <param name="command"></param>
        /// <param name="interceptionContext"></param>
        public void NonQueryExecuting(DbCommand command, DbCommandInterceptionContext<int> interceptionContext)
        {
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="command"></param>
        /// <param name="interceptionContext"></param>
        public void NonQueryExecuted(DbCommand command, DbCommandInterceptionContext<int> interceptionContext)
        {
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="command"></param>
        /// <param name="interceptionContext"></param>
        public void ReaderExecuting(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext)
        {
            RewriteFullTextQuery(command);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="command"></param>
        /// <param name="interceptionContext"></param>
        public void ReaderExecuted(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext)
        {
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="command"></param>
        /// <param name="interceptionContext"></param>
        public void ScalarExecuting(DbCommand command, DbCommandInterceptionContext<object> interceptionContext)
        {
            RewriteFullTextQuery(command);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="command"></param>
        /// <param name="interceptionContext"></param>
        public void ScalarExecuted(DbCommand command, DbCommandInterceptionContext<object> interceptionContext)
        {
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="cmd"></param>
        public static void RewriteFullTextQuery(DbCommand cmd)
        {
            var text = cmd.CommandText;
            for (var i = 0; i < cmd.Parameters.Count; i++)
            {
                var parameter = cmd.Parameters[i];
                if (
                    !parameter.DbType.In(DbType.String, DbType.AnsiString, DbType.StringFixedLength,
                        DbType.AnsiStringFixedLength)) continue;
                if (parameter.Value == DBNull.Value)
                    continue;
                var value = (string)parameter.Value;
                if (value.IndexOf(FullTextPrefixes.ContainsPrefix, StringComparison.Ordinal) >= 0)
                {
                    parameter.Size = 4096;
                    parameter.DbType = DbType.AnsiStringFixedLength;
                    value = value.Replace(FullTextPrefixes.ContainsPrefix, ""); // remove prefix we added n linq query
                    value = value.Substring(1, value.Length - 2); // remove %% escaping by linq translator from string.Contains to sql LIKE
                    parameter.Value = value;
                    cmd.CommandText = Regex.Replace(text,
                        string.Format(
                            @"\[(\w*)\].\[(\w*)\]\s*LIKE\s*@{0}\s?(?:ESCAPE N?'~')", parameter.ParameterName),
                        string.Format(@"CONTAINS([$1].[$2], @{0})", parameter.ParameterName));
                    if (text == cmd.CommandText)
                        throw new Exception("FTS was not replaced on: " + text);
                    text = cmd.CommandText;
                }
                else if (value.IndexOf(FullTextPrefixes.FreetextPrefix, StringComparison.Ordinal) >= 0)
                {
                    parameter.Size = 4096;
                    parameter.DbType = DbType.AnsiStringFixedLength;
                    value = value.Replace(FullTextPrefixes.FreetextPrefix, ""); // remove prefix we added n linq query
                    value = value.Substring(1, value.Length - 2); // remove %% escaping by linq translator from string.Contains to sql LIKE
                    parameter.Value = value;
                    cmd.CommandText = Regex.Replace(text,
                        string.Format(
                            @"\[(\w*)\].\[(\w*)\]\s*LIKE\s*@{0}\s?(?:ESCAPE N?'~')", parameter.ParameterName),
                        string.Format(@"FREETEXT([$1].[$2], @{0})", parameter.ParameterName));
                    if (text == cmd.CommandText)
                        throw new Exception("FTS was not replaced on: " + text);
                    text = cmd.CommandText;
                }
            }
        }

    }

در این کلاس دستوراتی را که توسط متد‌های الحاقی جهت امکان Fts علامت گذاری شده بودند را یافته و دستور LIKE را با دستورات  CONTAINSو FREETEXT جایگزین میکنیم.

در ادامه برنامه ای جهت استفاده از این امکان به همراه دستورات تولیدی آنرا مشاهده مینمایید.
public class Note
    {
        /// <summary>
        /// 
        /// </summary>
        public int Id { get; set; }

        /// <summary>
        /// 
        /// </summary>
        public string NoteText { get; set; }
    }

  public class FtsSampleContext : DbContext
    {
        /// <summary>
        /// 
        /// </summary>
        public DbSet<Note> Notes { get; set; }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="modelBuilder"></param>
        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            modelBuilder.Configurations.Add(new NoteMap());
        }
    }

/// <summary>
    /// 
    /// </summary>
    class Program
    {
        /// <summary>
        /// 
        /// </summary>
        /// <param name="args"></param>
        static void Main(string[] args)
        {
            DbInterception.Add(new FtsInterceptor());
            const string searchTerm = "john";
            using (var db = new FtsSampleContext())
            {
                var result1 = db.Notes.FreeTextSearch(a => a.NoteText, searchTerm).ToList();
                //SQL Server Profiler result ===>>>
                //exec sp_executesql N'SELECT 
                //    [Extent1].[Id] AS [Id], 
                //    [Extent1].[NoteText] AS [NoteText]
                //    FROM [dbo].[Notes] AS [Extent1]
                //    WHERE FREETEXT([Extent1].[NoteText], @p__linq__0)',N'@p__linq__0 
                //char(4096)',@p__linq__0='(john)'
                var result2 = db.Notes.ContainsSearch(a => a.NoteText, searchTerm).ToList();
                //SQL Server Profiler result ===>>>
                //exec sp_executesql N'SELECT 
                //    [Extent1].[Id] AS [Id], 
                //    [Extent1].[NoteText] AS [NoteText]
                //    FROM [dbo].[Notes] AS [Extent1]
                //    WHERE CONTAINS([Extent1].[NoteText], @p__linq__0)',N'@p__linq__0 
                //char(4096)',@p__linq__0='(john)'
            }
            Console.ReadKey();
        }
    }

ابتدا کلاس FtsInterceptor را به EF معرفی مینماییم. سپس از دو متد الحاقی مذکور استفاده مینماییم. خروجی هر دو متد توسط SQL Server Profiler در زیر هر متد مشاهده مینمایید.
تمامی کدها و مثال مربوطه در آدرس https://effts.codeplex.com قرار گرفته است.
منبع:
Full Text Search in Entity Framework 6
اشتراک‌ها
چطور می‌توان اینترنت را از کار انداخت؟!

شخصی به نام Azer Koçulu به علت فشار یک شرکت که عنوان کرده بوده نام پروژه‌ی او با نام شرکت آن‌ها یکی است، تمام ماژول‌های خودش را از npm یا node package manager حذف کرده‌است. همین مساله سبب از کار افتادن تمام بسته‌های دیگری شده‌است که به بسته‌های او وابسته بوده‌اند.

چطور می‌توان اینترنت را از کار انداخت؟!
نظرات مطالب
شروع به کار با AngularJS 2.0 و TypeScript - قسمت اول - نصب پیشنیازها
- این خطا نیست، warning هست و صرفا به این معنا است که وابستگی Mac یاد شده در ویندوز وجود ندارد و مهم نیست (skip شده، صرفنظر شده).
- در کل یکبار node را به طور کامل از سیستم حذف کنید. آخرین نگارش این‌ها را دریافت و مجددا نصب کنید. همچنین نکته‌ی جایگزین کردن npm پیش فرض ویژوال استودیو را که در متن عنوان شد، فراموش نکنید.
نظرات مطالب
شروع به کار با AngularJS 2.0 و TypeScript - قسمت دوازدهم - توزیع برنامه
یک نکته‌ی تکمیلی

روش توصیه‌ی شده‌ی توسط تیم AngularJS 2.0، جهت توزیع برنامه‌ها به این صورت است:
 1) ابتدا برنامه‌ی جدید angular-cli را نصب کنید: (بدیهی است پیش از آن باید وابستگی‌های Nodejs بر روی سیستم شما نصب باشند)
npm install -g angular-cli
 2) با دستور ذیل، یک برنامه‌ی جدید AngularJS 2.0 را به همراه تمام وابستگی‌های آن، می‌توانید آغاز کنید:
ng new AngularCLIDemoApp
ابتدا به پوشه‌ی مدنظر وارد شده و سپس در خط فرمان، دستور فوق را وارد کنید. به این ترتیب ساختار یک برنامه‌ی جدید AngularJS 2.0 را خواهید داشت.
 3) اکنون اگر دستور ذیل را صادر کنید:
 ng build -prod
به صورت خودکار، کار بسته بندی و توزیع نهایی برنامه، در پوشه‌ای به نام dist، انجام خواهد شد.

دو مطلب تکمیلی
Angular CLI is here for AngularJS 2  
Angular 2 CLI – Build Angular 2 apps using Command Line Interface  
مطالب
ایجاد جداول بهینه سازی شده برای حافظه در SQL Server 2014
پس از نگاهی به مفاهیم مقدماتی OLTP درون حافظه‌ای در SQL Server 2014، در ادامه به نحوه‌ی انجام تنظیمات خاص جداول بهینه سازی شده برای حافظه خواهیم پرداخت.


ایجاد یک بانک اطلاعاتی با پشتیبانی از جداول بهینه سازی شده برای حافظه

برای ایجاد جداول بهینه سازی شده برای حافظه، ابتدا نیاز است تا تنظیمات خاصی را به بانک اطلاعاتی آن اعمال کنیم. برای اینکار می‌توان یک بانک اطلاعاتی جدید را به همراه یک filestream filegroup ایجاد کرد که جهت جداول بهینه سازی شده برای حافظه، ضروری است؛ یا اینکه با تغییر یک بانک اطلاعاتی موجود و افزودن filegroup یاد شده نیز می‌توان به این مقصود رسید.
در اینگونه جداول خاص، اطلاعات در حافظه‌ی سیستم ذخیره می‌شوند و برخلاف جداول مبتنی بر دیسک سخت، صفحات اطلاعات وجود نداشته و نیازی نیست تا به کش بافر وارد شوند. برای مقاصد ذخیره سازی نهایی اطلاعات جداول بهینه سازی شده برای حافظه، موتور OLTP درون حافظه‌ای آن، فایل‌های خاصی را به نام checkpoint در یک filestream filegroup ایجاد می‌کند که از آن‌ها جهت ردیابی اطلاعات استفاده خواهد کرد و نحوی ذخیره سازی اطلاعات در آن‌ها از شیوه‌ی با کارآیی بالایی به نام append only mode پیروی می‌کند.
با توجه به متفاوت بودن نحوه‌ی ذخیره سازی نهایی اطلاعات اینگونه جداول و دسترسی به آن‌ها از طریق استریم‌ها، توصیه شده‌است که filestream filegroup‌های تهیه شده را در یک SSD یا Solid State Drive قرار دهید.

پس از اینکه بانک اطلاعاتی خود را به روش‌های معمول ایجاد کردید، به برگه‌ی خواص آن در management studio مراجعه کنید. سپس صفحه‌ی file groups را در آن انتخاب کرده و در پایین برگه‌ی آن، در قسمت جدید memory optimized data، بر روی دکمه‌ی Add کلیک کنید. سپس نام دلخواهی را وارد نمائید.


پس از ایجاد یک گروه فایل جدید، به صفحه‌ی files خواص بانک اطلاعاتی مراجعه کرده و بر روی دکمه‌ی Add کلیک کنید. سپس File type این ردیف اضافه شده را از نوع file stream data و file group آن‌را همان گروه فایلی که پیشتر ایجاد کردیم، تنظیم کنید. در ادامه logical name دلخواهی را وارد کرده و در آخر بر روی دکمه‌ی Ok کلیک کنید تا تنظیمات مورد نیاز جهت تعاریف جدول بهینه سازی شده برای حافظه به پایان برسد.


این مراحل را توسط دو دستور T-SQL ذیل نیز می‌توان سریعتر انجام داد:
USE [master]
GO
ALTER DATABASE [testdb2] 
      ADD FILEGROUP [InMemory_InMemory] CONTAINS MEMORY_OPTIMIZED_DATA 
GO
ALTER DATABASE [testdb2] 
      ADD FILE ( NAME = N'InMemory_InMemory', FILENAME = N'D:\SQL_Data\MSSQL11.MSSQLSERVER\MSSQL\DATA\InMemory_InMemory' ) 
      TO FILEGROUP [InMemory_InMemory]
GO

ساختار گروه فایل بهینه سازی شده برای حافظه

گروه فایل بهینه سازی شده برای حافظه، دارای چندین دربرگیرنده است که هر کدام چندین فایل را در خود جای خواهند داد:
- Root File که در برگیرنده‌ی متادیتای اطلاعات است.
- Data File که شامل ردیف‌های اطلاعات ثبت شده در جداول بهینه سازی شده‌ی برای حافظه هستند. این ردیف‌ها همواره به انتهای data file اضافه می‌شوند و دسترسی به آن‌ها ترتیبی است. کارآیی IO این روش نسبت به روش دسترسی اتفاقی به مراتب بالاتر است. حداکثر اندازه این فایل 128 مگابایت است و پس از آن یک فایل جدید ساخته می‌شود.
- Delta File شامل ردیف‌هایی است که حذف شده‌اند. به ازای هر ردیف، حداقل اطلاعاتی از آن را در خود ذخیره خواهد کرد؛ شامل ID ردیف حذف شده و شماره تراکنش آن. همانطور که پیشتر نیز ذکر شد، این موتور جدید درون حافظه‌ای، برای یافتن راه چاره‌ای جهت به حداقل رسانی قفل گذاری بر روی اطلاعات، چندین نگارش از ردیف‌ها را به همراه timestamp آن‌ها در خود ذخیره می‌کند. به این ترتیب، هر به روز رسانی به همراه یک حذف و سپس ثبت جدید است. به این ترتیب دیگر بانک اطلاعاتی نیازی نخواهد داشت تا به دنبال رکورد موجود برگردد و سپس اطلاعات آن‌را به روز نماید. این موتور جدید فقط اطلاعات به روز شده را در انتهای رکوردهای موجود با فرمت خود ثبت می‌کند.


ایجاد جداول بهینه سازی شده برای حافظه

پس از آماده سازی بانک اطلاعاتی خود و افزودن گروه فایل استریم جدیدی به آن برای ذخیره سازی اطلاعات جداول بهینه سازی شده برای حافظه، اکنون می‌توانیم اینگونه جداول خاص را در کنار سایر جداول متداول موجود، تعریف و استفاده نمائیم:
-- It is not a Memory Optimized
CREATE TABLE tblNormal
(
   [CustomerID] int NOT NULL PRIMARY KEY NONCLUSTERED, 
   [Name] nvarchar(250) NOT NULL,
   CustomerSince DATETIME not NULL
      INDEX [ICustomerSince] NONCLUSTERED
)

--  DURABILITY = SCHEMA_AND_DATA
CREATE TABLE tblMemoryOptimized_Schema_And_Data
(
    [CustomerID] INT NOT NULL 
PRIMARY KEY NONCLUSTERED HASH WITH (BUCKET_COUNT = 1000000),
    [Name] NVARCHAR(250) NOT NULL,
    [CustomerSince] DATETIME NOT NULL
INDEX [ICustomerSince] NONCLUSTERED
) WITH (MEMORY_OPTIMIZED = ON, DURABILITY = SCHEMA_AND_DATA)


-- DURABILITY = SCHEMA_ONLY
CREATE TABLE tblMemoryOptimized_Schema_Only
(
    [CustomerID] INT NOT NULL 
PRIMARY KEY NONCLUSTERED HASH WITH (BUCKET_COUNT = 1000000),
    [Name] NVARCHAR(250) NOT NULL,
    [CustomerSince] DATETIME NOT NULL
INDEX [ICustomerSince] NONCLUSTERED
) WITH (MEMORY_OPTIMIZED = ON, DURABILITY = SCHEMA_ONLY)
در اینجا سه جدول را مشاهده می‌کنید که در بانک اطلاعاتی آماده شده در مرحله‌ی قبل، ایجاد خواهند شد. مورد اول یک جدول معمولی است که از آن برای مقایسه سرعت ثبت اطلاعات با سایر جداول ایجاد شده، استفاده خواهد شد.
همانطور که مشخص است، دو جدول بهینه سازی شده برای حافظه، همان سه ستون جدول معمولی مبتنی بر دیسک سخت را دارا هستند؛ اما با این تفاوت‌ها:
- دارای ویژگی MEMORY_OPTIMIZED = ON می‌باشند. به این ترتیب اینگونه جداول نسبت به جداول متداول مبتنی به دیسک سخت متمایز خواهند شد.
- دارای ویژگی DURABILITY بوده و توسط مقدار SCHEMA_AND_DATA آن مشخص می‌کنیم که آیا قرار است اطلاعات و ساختار جدول، ذخیره شوند یا تنها قرار است ساختار جدول ذخیره گردد (حالت SCHEMA_ONLY).
- بر روی ستون Id آن‌ها یک hash index ایجاد شده‌است که وجود آن ضروری است و در کل بیش از 8 ایندکس را نمی‌توان تعریف کرد.
برخلاف ایندکس‌های B-tree جداول مبتنی بر سخت دیسک، ایندکس‌های جداول بهینه سازی شده برای حافظه، اطلاعات را تکرار نمی‌کنند. این‌ها صرفا اشاره‌گرهایی هستند به ردیف‌های اصلی اطلاعات. به این معنا که این ایندکس‌ها لاگ نشده و همچنین بر روی سخت دیسک ذخیره نمی‌شوند. کار بازسازی مجدد آن‌ها در اولین بار بازیابی بانک اطلاعاتی و آغاز آن به صورت خودکار انجام می‌شود. به همین جهت مباحثی مانند index fragmentation و نگهداری ایندکس‌ها دیگر در اینجا معنا پیدا نمی‌کنند.
دو نوع ایندکس را در اینجا می‌توان تعریف کرد. اولین آن‌ها hash index است و دومین آن‌ها range index. هش ایندکس‌ها برای حالاتی که در کوئری‌ها از عملگر تساوی استفاده می‌شود بسیار مناسب هستند. برای عملگرهای مقایسه‌ای از ایندکس‌های بازه‌ای استفاده می‌شود.
همچنین باید دقت داشت که پس از ایجاد ایندکس‌ها، دیگر امکان تغییر آن‌ها و یا تغییر ساختار جدول ایجاد شده نیست.
همچنین ایندکس‌های تعریف شده در جداول بهینه سازی شده برای حافظه، تنها بر روی ستون‌هایی غیرنال پذیر از نوع BIN2 collation مانند int و datetime قابل تعریف هستند. برای مثال اگر سعی کنیم بر روی ستون Name ایندکسی را تعریف کنیم، به این خطا خواهیم رسید:
 Indexes on character columns that do not use a *_BIN2 collation are not supported with indexes on memory optimized tables.
- در حین تعریف هش ایندکس‌ها، مقدار BUCKET_COUNT نیز باید تنظیم شود. هر bucket توسط مقداری که حاصل هش کردن یک ستون است مشخص می‌شود. کلیدهای منحصربفرد دارای هش‌های یکسان در bucketهای یکسانی ذخیره می‌شوند. به همین جهت توصیه شده‌است که حداقل مقدار bucket تعیین شده در اینجا مساوی یا بیشتر از مقدار تعداد کلیدهای منحصربفرد یک جدول باشد؛ مقدار پیش فرض 2 برابر توسط مایکروسافت توصیه شده‌است.
- نوع‌های قابل تعریف ستون‌ها نیز در اینجا به موارد ذیل محدود هستند و جمع طول آن‌ها از 8060 نباید بیشتر شود:
 bit, tinyint, smallint, int, bigint, money, smallmoney, float, real, datetime, smalldatetime, datetime2,
date, time, numberic, decimal, char(n),  varchar(n) ,nchar(n),  nvarchar(n), sysname, binary(n),
varbinary(n), and Uniqueidentifier


همچنین در management studio، گزینه‌ی جدید new -> memory optimized table نیز اضافه شده‌است و انتخاب آن سبب می‌شود تا قالب T-SQL ایی برای تهیه این نوع جداول، به صورت خودکار تولید گردد.


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


ثبت اطلاعات در جداول معمولی و بهینه سازی شده برای حافظه و مقایسه کارآیی آن‌ها

در مثال زیر، 100 هزار رکورد را در سه جدولی که پیشتر ایجاد کردیم، ثبت کرده و سپس مدت زمان اجرای هر کدام از مجموعه عملیات را بر حسب میلی ثانیه بررسی می‌کنیم:
set statistics time off
SET STATISTICS IO Off
set nocount on
go
-----------------------------

Print 'insert into tblNormal'

DECLARE @start datetime = getdate()
declare @insertCount int = 100000
declare @startId int = 1
declare @customerID int = @startId

while @customerID < @startId + @insertCount
begin
    insert into tblNormal values (@customerID, 'Test', '2013-01-01T00:00:00')
    set @customerID +=1
end

Print DATEDIFF(ms,@start,getdate());
go
-----------------------------

Print 'insert into tblMemoryOptimized_Schema_And_Data'

DECLARE @start datetime = getdate()
declare @insertCount int = 100000
declare @startId int = 1
declare @customerID int = @startId

while @customerID < @startId + @insertCount
begin
    insert into tblMemoryOptimized_Schema_And_Data values (@customerID, 'Test', '2013-01-01T00:00:00')
    set @customerID +=1
end
Print DATEDIFF(ms,@start,getdate());
Go
-----------------------------

Print 'insert into tblMemoryOptimized_Schema_Only'

DECLARE @start datetime = getdate()
declare @insertCount int = 100000
declare @startId int = 1
declare @customerID int = @startId

while @customerID < @startId + @insertCount
begin
    insert into tblMemoryOptimized_Schema_Only values (@customerID, 'Test', '2013-01-01T00:00:00')
    set @customerID +=1
end
Print DATEDIFF(ms,@start,getdate());

Go
با این خروجی تقریبی که بر اساس توانمندی‌های سخت افزاری سیستم می‌تواند متفاوت باشد:
 insert into tblNormal
36423

insert into tblMemoryOptimized_Schema_And_Data
30516

insert into tblMemoryOptimized_Schema_Only
3176
و برای حالت select خواهیم داشت:
 set nocount on
print 'tblNormal'
set statistics time on
select count(CustomerID) from tblNormal
set statistics time off
go
print 'tblMemoryOptimized_Schema_And_Data'
set statistics time on
select count(CustomerID) from tblMemoryOptimized_Schema_And_Data
set statistics time off
go
print 'tblMemoryOptimized_Schema_Only'
set statistics time on
select count(CustomerID) from tblMemoryOptimized_Schema_Only
set statistics time off
go
با این خروجی
 tblNormal
 SQL Server Execution Times:
CPU time = 46 ms,  elapsed time = 52 ms.

tblMemoryOptimized_Schema_And_Data
 SQL Server Execution Times:
CPU time = 32 ms,  elapsed time = 33 ms.

tblMemoryOptimized_Schema_Only
 SQL Server Execution Times:
CPU time = 31 ms,  elapsed time = 30 ms.
تاثیر جداول بهینه سازی شده برای حافظه را در 350K inserts بهتر می‌توان با نمونه‌های متداول مبتنی بر دیسک مقایسه کرد.


برای مطالعه بیشتر

Getting started with SQL Server 2014 In-Memory OLTP
Introduction to SQL Server 2014 CTP1 Memory-Optimized Tables
Overcoming storage speed limitations with Memory-Optimized Tables for SQL Server
Memory-optimized Table – Day 1 Test
Memory-Optimized Tables – Insert Test
Memory Optimized Table – Insert Test …Again
مطالب
معرفی افزونه CAT.NET

اخیرا مایکروسافت افزونه‌ رایگانی را برای آنالیز امنیتی کدهای برنامه‌های نوشته شده با VS.Net ارائه داده است به نام CAT.Net.
دریافت افزونه 32 بیتی، 64 بیتی

این افزونه قابلیت بررسی کدهای شما را جهت یافتن خطرات جدی SQL Injection ، XSS و XPath Injection دارد.
نحوه استفاده:
VS.Net خود را ببندید. در ادامه، پس از نصب، به منوی Tools و گزینه‌ی جدید CAT.NET Code Analysis مراجعه نمائید.
صفحه‌ای ظاهر خواهد شد که پس از کلیک بر روی دکمه آغاز آنالیز آن، کار بررسی امنیتی پروژه را آغاز می‌کند (شکل زیر)




این افزونه همانند FxCop ، اسمبلی برنامه را آنالیز می‌کند. پس از پایان آنالیز،‌ با کلیک بر روی هر سطری که گزارش داده، آن سطر را می‌توان در پروژه یافت و تغییرات لازم را اعمال نمود.



همچنین پس از پایان کار بررسی، یک فایل xml هم در مسیر فایل‌های پروژه ایجاد می‌کند که در آینده در صورت نیاز، توسط همین افزونه قابل گشودن است.

بعلاوه پس از نصب، یک فایل chm هم در دایرکتوری آن جهت آشنایی بیشتر با اجزای مختلف این افزونه قرار خواهد گرفت.

البته، برنامه هنوز در مراحل آزمایشی به سر می‌برد. برای مثال پس از نصب در مسیر دیگری غیر از مسیر پیش فرض نصب، پیغام می‌داد که فایل‌ها را نمی‌تواند پیدا کند. اگر این مورد برای شما هم رخ داد، مسیری را که گزارش می‌دهد به صورت دستی درست کنید و فایل‌های کانفیگ آن‌را از جایی که نصب کرده‌اید به آنجایی که برنامه به شما گزارش می‌دهد کپی کنید تا کار کند!

یا می‌توان این افزونه را از طریق خط فرمان هم اجرا کرد (مسیر پروژه، اسمبلی آن و مسیر نصب cat.net‌ را لازم دارد). به صورت زیر:

CATNETCmd /file:"I:\prog\bin\prog.dll" /search:"I:\prog" /report:"I:\prog\report.xsl" /rule:"J:\microsoft\cat.net\Rules"

که نتیجه حاصل را در فایل xsl نهایی ثبت خواهد نمود.

اگر علاقمند به مطالعه تاریخچه‌ی این برنامه هستید به وبلاگ زیر مراجعه نمائید:
مشاهده وبلاگ