اشتراک‌ها
کار با HubContext در ASP.NET Core SignalR

In this section, I’m going to cover how you can use SignalR outside of a Hub. In most asp.net core applications, you will likely want to communicate with the connect clients from within your application but outside of a Hub. You can accomplish this by using the HubContext.

For example, an ASP.NET Core MVC Controller or any other class that is instantiated by ASP.NET Core’s Dependency Injection.

The HubContext allows you to send messages to your connected clients. It has many of the same features to communicate with clients as when you are inside of a Hub.  

کار با HubContext در ASP.NET Core SignalR
مطالب
محاسبه مجدد میزان مصرف حافظه‌ی برنامه‌های دات نت

اگر به میزان مصرف حافظه‌ اولیه‌ی برنامه‌های دات نت دقت کنیم، نسبت به مثلا یک برنامه‌ی MFC چند برابر به نظر می‌رسند و ... این علت دارد:
زمانیکه یک برنامه‌ی مبتنی بر دات نت اجرا می‌شود، ابتدا JIT compiler شروع به کار کرده و شروع به کامپایل برنامه می‌کند. این بارگزاری هم در همان پروسه‌ی اصلی برنامه انجام می‌شود. به همین جهت میزان مصرف حافظه‌ی برنامه‌های دات نت عموما بالا به نظر می‌رسد.
اکنون سؤال اینجا است که آیا می توان این حافظه‌ای را که دیگر مورد استفاده نیست (و توسط JIT compiler اخذ شده) به سیستم بازگرداند و محاسبه‌ی مجددی را در این مورد انجام داد. پاسخ به این سؤال را در متد ReEvaluateWorkingSet زیر می‌توان مشاهده کرد:


using System;
using System.Diagnostics;

namespace Toolkit
{
public static class Memory
{
public static void ReEvaluateWorkingSet()
{
try
{
Process loProcess = Process.GetCurrentProcess();
//it doesn't matter what you set maxWorkingSet to
//setting it to any value apparently causes the working set to be re-evaluated and excess discarded
loProcess.MaxWorkingSet = (IntPtr)((int)loProcess.MaxWorkingSet + 1);
}
catch
{
//The above code requires Admin privileges.
//So it's important to trap exceptions in case you're running without admin rights.
}
}
}
}

در این متد ابتدا پروسه جاری دریافت شده و سپس MaxWorkingSet به یک عدد دلخواه تنظیم می‌شود. مهم نیست که این عدد چه چیزی باشد، زیرا این تنظیم سبب می‌شود که در پشت صحنه به شکل حساب شده‌ای حافظه‌ای که مورد استفاده نیست به سیستم بازگردانده شود و سپس عددی که در task manager نمایش داده می‌شود، مجددا محاسبه گردد. همچنین باید دقت داشت که این کد تنها با دسترسی مدیریتی قابل اجرا است و به همین دلیل وجود این try/catch ضروری است.

نحوه استفاده از متد ReEvaluateWorkingSet در برنامه‌های WinForms :
فایل Program.cs را یافته و سپس در روال رویداد گردان Idle برنامه، متد ReEvaluateWorkingSet را فراخوانی کنید (مثلا هر زمان که برنامه minimized شد اجرا می‌شود):

//Program.cs
namespace MemUsage
{
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
//...

Application.Idle += applicationIdle;
}

static void applicationIdle(object sender, EventArgs e)
{
Memory.ReEvaluateWorkingSet();
}
}
}

نحوه استفاده از متد ReEvaluateWorkingSet در برنامه‌های WPF :
فایل App.xaml.cs را یافته و سپس در روال رویدادگردان Deactivated برنامه، متد ReEvaluateWorkingSet را فراخوانی کنید:

//App.xaml.cs

public App()
{
this.Deactivated += appDeactivated;
}

void appDeactivated(object sender, EventArgs e)
{
Memory.ReEvaluateWorkingSet();
}

تاثیر آن هم قابل ملاحظه است (حداقل از لحاظ روانی!). تست کنید!

اشتراک‌ها
ReScript زبان پس از TypeScript؟

In the confusing jungle of transpiler languages for JavaScript, there are some gems. TypeScript is mainstream, ReScript is starting to establish itself, and Elm is still an insider tip. This article takes a detailed look at ReScript – but also sheds light on the limitations of the young language. In what projects does its use make sense? What projects should rather use TypeScript on the one hand or Elm on the other? 

ReScript زبان پس از TypeScript؟
مطالب
فرم‌های مبتنی بر قالب‌ها در Angular - قسمت پنجم - ارسال اطلاعات به سرور
تا اینجا تنظیمات اصلی فرم ثبت اطلاعات کارمندان را انجام دادیم. اکنون نوبت به ارسال این اطلاعات به سمت سرور است. پیشنیاز آن نیز تدارک مواردی است که در مطلب «یکپارچه سازی Angular CLI و ASP.NET Core در VS 2017» پیشتر بحث شدند. از این مطلب تنها تنظیمات موارد ذیل را نیاز خواهیم داشت و از تکرار آن‌ها در اینجا صرفنظر می‌شود تا هم مطلب کوتاه‌تر شود و هم بتوان بر روی اصل موضوع جاری، تمرکز کرد:
- ایجاد یک پروژه‌ی جدید ASP.NET Core در VS 2017
- تنظیمات یک برنامه‌ی ASP.NET Core خالی برای اجرای یک برنامه‌ی Angular CLI
- تنظیمات فایل آغازین یک برنامه‌ی ASP.NET Core جهت ارائه‌ی برنامه‌های Angular
- ایجاد ساختار اولیه‌ی برنامه‌ی Angular CLI در داخل پروژه‌ی جاری: این مورد را تاکنون انجام داده‌ایم و تکمیل کرده‌ایم. بنابراین تنها کاری که نیاز است انجام شود، cut و paste محتوای پوشه‌ی angular-template-driven-forms-lab (پروژه‌ی این سری) به ریشه‌ی پروژه‌ی ASP.NET Core است.
- تنظیم محل خروجی نهایی Angular CLI به پوشه‌ی wwwroot
- روش اول و یا دوم اجرای برنامه‌های مبتنی بر ASP.NET Core و Angular CLI

البته سورس کامل تمام این تنظیمات را از انتهای بحث نیز می‌توانید دریافت کنید.
ضمن اینکه هیچ نیازی هم به استفاده از VS 2017 نیست و هر دوی برنامه‌ی Angular و ASP.NET Core را می‌توان توسط VSCode به خوبی مدیریت و اجرا کرد.


ایجاد ساختار مقدماتی سرویس ارسال اطلاعات به سرور

در برنامه‌های Angular مرسوم است جهت کاهش مسئولیت‌های یک کلاس و امکان استفاده‌ی مجدد از کدها، منطق ارسال اطلاعات به سرور، به درون کلاس یک سرویس منتقل شود و سپس این سرویس به کلاس‌های کامپوننت‌ها، برای مثال یک فرم ثبت اطلاعات، برای ارسال و یا دریافت اطلاعات، تزریق گردد. به همین جهت، ابتدا ساختار ابتدایی این سرویس و تنظیمات مرتبط با آن‌را انجام می‌دهیم.
ابتدا از طریق خط فرمان به پوشه‌ی ریشه‌ی برنامه وارد شده (جائیکه فایل Startup.cs قرار دارد) و سپس دستور ذیل را اجرا می‌کنیم:
 >ng g s employee/FormPoster -m employee.module
با این خروجی
 installing service
  create src\app\employee\form-poster.service.spec.ts
  create src\app\employee\form-poster.service.ts
  update src\app\employee\employee.module.ts
همانطور که در سطر آخر نیز ملاحظه می‌کنید، فایل employee.module.ts را جهت درج کلاس جدید FormPosterService در قسمت providers ماژول آن به روز رسانی می‌کند؛ تا بتوانیم این سرویس را در کامپوننت‌های این ماژول تزریق کرده و استفاده کنیم.
ساختار ابتدایی این سرویس را نیز به نحو ذیل تغییر می‌دهیم:
import { Injectable } from '@angular/core';
import { Http } from '@angular/http';

import { Employee } from './employee';

@Injectable()
export class FormPosterService {

    constructor(private http:Http) {
    }

    postEmployeeForm(employee: Employee) {
    }
}
در اینجا سرویس Http انگیولار به سازنده‌ی کلاس تزریق شده‌است و این نحوه‌ی تعریف سبب می‌شود تا بتوان به پارامتر http، به صورت یک فیلد خصوصی تعریف شده‌ی در سطح کلاس نیز دسترسی پیدا کنیم.
چون این کلاس از ماژول توکار Http استفاده می‌کند، نیاز است این ماژول را نیز به قسمت imports فایل src\app\app.module.ts اضافه کنیم:
import { HttpModule } from "@angular/http";

@NgModule({
  imports: [
    BrowserModule,
    FormsModule,
    HttpModule,
    EmployeeModule,
    AppRoutingModule
  ]
اکنون می‌توانیم این سرویس جدید FormPosterService را به سازنده‌ی کامپوننت EmployeeRegisterComponent در فایل src\app\employee\employee-register\employee-register.component.ts تزریق کنیم:
import { FormPosterService } from "../form-poster.service";

export class EmployeeRegisterComponent implements OnInit {

  constructor(private formPoster: FormPosterService) {}

}

در ادامه برای آزمایش برنامه، به ریشه‌ی پروژه وارد شده و دو پنجره‌ی کنسول مجزا را باز کنید. در اولی، دستورات:
>npm install
>ng build --watch
و در دومی، دستورات ذیل را اجرا کنید:
>dotnet restore
>dotnet watch run
دستورات اول کار بازیابی وابستگی‌های سمت کلاینت و سپس ساخت تدریجی برنامه‌ی Angular را دنبال می‌کند. دستورات دوم، وابستگی‌های برنامه‌ی ASP.NET Core را دریافت و نصب کرده و سپس برنامه را در حالت watch ساخته و بر روی پورت 5000 ارائه می‌کند (بدون نیاز به اجرای VS 2017؛ این دستور عمومی است).
به همین جهت برای آزمایش ابتدایی آن، آدرس http://localhost:5000 را در مرورگر باز کنید. برگه‌ی developer tools مرورگر را نیز بررسی کنید تا خطایی در آن ظاهر نشده باشد. برای مثال اگر فراموش کرده باشید تا HttpModule را به app.module اضافه کنید، خطای no provider for HttpModule را مشاهده خواهید کرد.


مدیریت رخداد submit فرم در Angular

تا اینجا کار برپایی تنظیمات اولیه‌ی کار با سرویس Http را انجام دادیم. مرحله‌ی بعد مدیریت رخداد submit فرم است. به همین جهت فایل src\app\employee\employee-register\employee-register.component.html را گشوده و سپس رخدادگردان submit را به فرم آن اضافه کنید:
<form #form="ngForm" (submit)="submitForm(form)" novalidate>
در حین رخدادگردانی submit می‌توان به template reference variable تعریف شده‌ی form# برای دسترسی به وهله‌ای از ngForm نیز کمک گرفت.
export class EmployeeRegisterComponent implements OnInit {
  submitForm(form: NgForm) {
    console.log(this.model);
    console.log(form.value);
  }
}
امضای متد submitForm را در اینجا مشاهده می‌کنید. form دریافتی آن از نوع NgForm است که در ابتدای فایل import شده‌است.
در همین حال اگر بر روی دکمه‌ی ok کلیک کنیم، چنین خروجی را در کنسول developer مروگر می‌توان مشاهده کرد:


اولین مورد، محتوای this.model است و دومی محتوای form.value را گزارش کرده‌است. همانطور که مشاهده می‌کنید، مقدار form.value بسیار شبیه است به وهله‌ای از مدلی که در سطح کلاس تعریف کرده‌ایم و این مقدار همواره توسط Angular نگهداری و مدیریت می‌شود. بنابراین حتما الزامی نیست تا مدلی را جهت کار با فرم‌های مبتنی بر قالب‌ها به صورت جداگانه‌ای تهیه کرد. توسط شیء form نیز می‌توان به تمام اطلاعات فیلدها دسترسی یافت.


تکمیل سرویس ارسال اطلاعات به سرور

در ادامه می‌خواهیم اطلاعات مدل فرم را به سرور ارسال کنیم. برای این منظور سرویس FormPoster را به صورت ذیل تکمیل می‌کنیم:
import { Injectable } from "@angular/core";
import { Http, Response, Headers, RequestOptions } from "@angular/http";

import { Observable } from "rxjs/Observable";
import "rxjs/add/operator/do";
import "rxjs/add/operator/catch";
import "rxjs/add/observable/throw";
import "rxjs/add/operator/map";
import "rxjs/add/observable/of";

import { Employee } from "./employee";

@Injectable()
export class FormPosterService {
  private baseUrl = "api/employee";

  constructor(private http: Http) {}

  private extractData(res: Response) {
    const body = res.json();
    return body.fields || {};
  }

  private handleError(error: Response): Observable<any> {
    console.error("observable error: ", error);
    return Observable.throw(error.statusText);
  }

  postEmployeeForm(employee: Employee): Observable<Employee> {
    const body = JSON.stringify(employee);
    const headers = new Headers({ "Content-Type": "application/json" });
    const options = new RequestOptions({ headers: headers });

    return this.http
      .post(this.baseUrl, body, options)
      .map(this.extractData)
      .catch(this.handleError);
  }
}
برای کار با Observables یا می‌توان نوشت 'import 'rxjs/Rx که تمام بسته‌ی RxJS را import می‌کند، یا همانند این مثال بهتر است تنها اپراتورهایی را که به آن‌ها نیاز پیدا می‌کنیم، import نمائیم. به این ترتیب حجم نهایی ارائه‌ی برنامه نیز کاهش خواهد یافت.
در متد postEmployeeForm، ابتدا توسط JSON.stringify محتوای شیء کارمند encode می‌شود. البته متد post اینکار را به صورت توکار نیز می‌تواند مدیریت کند. سپس ذکر هدر مناسب در اینجا الزامی است تا در سمت سرور بتوانیم اطلاعات دریافتی را به شیء متناظری نگاشت کنیم. در غیراینصورت model binder سمت سرور نمی‌داند که چه نوع فرمتی را دریافت کرده‌است و چه نوع decoding را باید انجام دهد.
در قسمت map، کار بررسی اطلاعات دریافتی از سرور را انجام خواهیم داد و اگر در این بین خطایی وجود داشت، توسط متد handleError در کنسول developer مرورگر نمایش داده می‌شود.
خروجی متد postEmployeeForm یک Observable است. بنابراین تا زمانیکه یک subscriber نداشته باشد، اجرا نخواهد شد. به همین جهت به کلاس EmployeeRegisterComponent مراجعه کرده و متد submitForm را به نحو ذیل تکمیل می‌کنیم:
  submitForm(form: NgForm) {
    console.log(this.model);
    console.log(form.value);

    // validate form
    this.validatePrimaryLanguage(this.model.primaryLanguage);
    if (this.hasPrimaryLanguageError) {
      return;
    }

    this.formPoster
      .postEmployeeForm(this.model)
      .subscribe(
        data => console.log("success: ", data),
        err => console.log("error: ", err)
      );
  }
در اینجا ابتدا اعتبارسنجی سفارشی drop down را که در قسمت قبل بررسی کردیم، قرار داده‌ایم. پس از آن متد postEmployeeForm سرویس formPoster فراخوانی شده‌است و در اینجا کار subscribe به نتیجه‌ی عملیات صورت گرفته‌است که می‌تواند حاوی اطلاعاتی از سمت سرور و یا خطایی در این بین باشد.

یک نکته: اگر علاقمند باشید تا ساختار واقعی شیء NgForm را مشاهده کنید، در ابتدای متد فوق، console.log(form.form) را فراخوانی کنید و سپس شیء حاصل را در کنسول developer مرورگر بررسی نمائید.


تکمیل Web API برنامه‌ی ASP.NET Core جهت دریافت اطلاعات از کلاینت‌ها

در ابتدای سرویس formPoster، یک چنین تعریفی را داریم:
export class FormPosterService {
  private baseUrl = "api/employee";
به همین جهت نیاز است سرویس Web API سمت سرور خود را بر این مبنا تکمیل کنیم.
ابتدا مدل زیر را به پروژه‌ی ASP.NET Core جاری، معادل نمونه‌ی تایپ‌اسکریپتی سمت کلاینت آن اضافه می‌کنیم. البته در اینجا یک Id نیز اضافه شده‌است:
namespace AngularTemplateDrivenFormsLab.Models
{
    public class Employee
    {
        public int Id { set; get; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public bool IsFullTime { get; set; }
        public string PaymentType { get; set; }
        public string PrimaryLanguage { get; set; }
    }
}

سپس کنترلر جدید EmployeeController را با محتوای ذیل اضافه خواهیم کرد:
using Microsoft.AspNetCore.Mvc;
using AngularTemplateDrivenFormsLab.Models;

namespace AngularTemplateDrivenFormsLab.Controllers
{
    [Route("api/[controller]")]
    public class EmployeeController : Controller
    {
        public IActionResult Post([FromBody] Employee model)
        {
            //todo: save model

            model.Id = 100;
            return Created("", new { fields = model });
        }
    }
}
این کنترلر با شیوه‌ی Web API تعریف شده‌است. مسیریابی آن با api شروع می‌شود تا با مسیر baseUrl سرویس formPoster تطابق پیدا کند.
در اینجا پس از ثبت فرضی مدل، Id آن به همراه اطلاعات مدل، به نحوی که ملاحظه می‌کنید، بازگشت داده شده‌است. این نوع خروجی، یک چنین JSON ایی را تولید می‌کند:
{"fields":{"id":100,"firstName":"Vahid","lastName":"N","isFullTime":true,"paymentType":"FullTime","primaryLanguage":"Persian"}}
به همین جهت است که در متد extractData، دسترسی به body.fields را مشاهده می‌کنید. این fields در اینجا دربرگیرنده‌ی اطلاعات بازگشتی از سرور است (نام آن دلخواه است و درصورت تغییر آن در سمت سرو، باید این نام را در متد extractData نیز اصلاح کنید).
  private extractData(res: Response) {
    const body = res.json();
    return body.fields || {};
  }
اکنون اگر برنامه را با دستورات dotnet watch build و ng build --watch اجرا کنیم، بر روی پورت 5000 قابل دسترسی خواهد بود و پس از ارسال فرم به سرور، چنین خروجی را می‌توان در کنسول developer مرورگر مشاهده کرد:


نمایش success به همراه شیءایی که از سمت سرور دریافت شده‌است؛ که حاصل اجرای سطر ذیل در متد submitForm است:
 data => console.log("success: ", data)
همانطور که مشاهده می‌کنید، این شیء به همراه Id نیز هست. بنابراین درصورت نیاز به آن در سمت کلاینت، خاصیت معادل آن‌را به کلاس کارمند اضافه کرده و در همین سطر فوق می‌توان به آن دسترسی یافت.


بارگذاری اطلاعات drop down از سرور

تا اینجا اطلاعات drop down نمایش داده شده از یک آرایه‌ی مشخص سمت کلاینت تامین شدند. در ادامه قصد داریم تا آن‌ها را از سرور دریافت کنیم. به همین جهت اکشن متد ذیل را به کنترلر سمت سرور برنامه اضافه کنید:
[HttpGet("/api/[controller]/[action]")]
public IActionResult Languages()
{
    string[] languages = { "Persian", "English", "Spanish", "Other" };
    return Ok(languages);
}
که برای آزمایش آن می‌توانید مسیر http://localhost:5000/api/employee/languages را جداگانه در مرورگر درخواست کنید.
پس از آن در سمت کلاینت این تغییرات نیاز هستند:
ابتدا به سرویس FormPosterService دو متد ذیل را اضافه می‌کنیم که کار آن‌ها دریافت و پردازش اطلاعات از api/employee/languages سمت سرور هستند:
  private extractLanguages(res: Response) {
    const body = res.json();
    return body || {};
  }

  getLanguages(): Observable<any> {
    return this.http
      .get(`${this.baseUrl}/languages`)
      .map(this.extractLanguages)
      .catch(this.handleError);
  }
اینبار چون خروجی سمت سرور را مانند قبل (متد extractData) داخل فیلدی مانند fields محصور نکردیم، همان body دریافتی بازگشت داده شده‌است.
پس از آن دو تغییر ذیل را نیاز است به EmployeeRegisterComponent اعمال کنیم:
  languages = [];

  ngOnInit() {
    this.formPoster
      .getLanguages()
      .subscribe(
        data => this.languages = data,
        err => console.log("get error: ", err)
      );
  }
ابتدا آرایه‌ی زبان‌ها با یک آرایه‌ی خالی مقدار دهی شده‌است و سپس در متد ngOnInit، کار دریافت اطلاعات آن از سرور، صورت گرفته‌است.

مشکل! ممکن است مدت زمانی طول بکشد تا این اطلاعات از سمت سرور دریافت شوند. در این حالت می‌توان به شکل زیر در فایل employee-register.component.html فرم را تا زمان پر شدن دراپ داون آن مخفی کرد:
<h3 *ngIf="languages.length == 0">Loading...</h3>
<div class="container" *ngIf="languages.length > 0">
در این حالت هر زمانیکه آرایه‌ی زبان‌ها پر شد، loading حذف شده و div نمایان می‌گردد.

کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: angular-template-driven-forms-lab-05.zip
برای اجرای آن فرض بر این است که پیشتر Angular CLI را نصب کرده‌اید. سپس به ریشه‌ی پروژه وارد شده و دو پنجره‌ی کنسول مجزا را باز کنید. در اولی دستورات:
>npm install
>ng build --watch
و در دومی دستورات ذیل را اجرا کنید:
>dotnet restore
>dotnet watch run
اکنون می‌توانید برنامه را در آدرس http://localhost:5000 مشاهده و اجرا کنید.
مطالب
تغییرات دسترسی به کدها در دات نت 4

دو پروژه‌ی سورس باز XML RPC و Log4Net برای اجرا شدن در برنامه‌های دات نت 4 نیاز به اندکی تغییر در هر دو برنامه‌ی فراخوان و اسمبلی‌های آن‌ها دارند که در ادامه توضیحات مربوطه ارائه خواهند شد.

اگر یک پروژه‌ی جدید دات نت 4 را آغاز کنید و سپس ارجاعی را به یکی از اسمبلی‌های ذکر شده اضافه نمائید، اولین خطایی را که حین استفاده مشاهده خواهید نمود، مورد زیر است:
Could not resolve assembly "System.Web".
The assembly is not in the currently targeted framework ".NETFramework,Version=v4.0,Profile=Client".
Please remove references to assemblies not in the targeted framework or consider retargeting your project.
علت هم اینجا است که در تنظیمات پروژه‌ها‌ی جدید مبتنی بر دات نت 4، پیش فرض Target framework انتخابی توسط VS.NET 2010 از نوع Client profile است؛ که صرفا جهت کاهش حجم دات نت فریم ورک مورد نیاز این نوع برنامه‌ها طراحی شده است. در این پروفایل ساده شده، اسمبلی System.Web وجود ندارد. بنابراین جهت استفاده از کتابخانه‌های XML RPC و یا Log4Net نیاز است تا در خواص پروژه، Target framework را بر روی دات نت فریم ورک 4 کامل قرار داد تا خطای فوق برطرف شود.

خطای دومی که حین کار با کتابخانه‌های XML RPC و یا Log4Net در یک برنامه‌ی دات نت 4 حتما با آن مواجه خواهید شد در ادامه ذکر گردیده است:
Inheritance security rules violated while overriding member:
GetObjectData(System.Runtime.Serialization.SerializationInfo, System.Runtime.Serialization.StreamingContext),
Security accessibility of the overriding method must match the security accessibility of the method being overriden.
متد ISerializable.GetObjectData با ویژگی SecurityCritical در دات نت فریم ورک مزین شده است. با تغییرات امنیتی صورت گرفته در دات نت 4، متدی که این متد را تحریف می‌کند نیز باید با همان سطح دسترسی متد virtual اصلی معرفی گردد و گرنه برنامه اجرا نخواهد شد. البته این مشکل ما نیست؛ مشکل سازندگان کتابخانه‌های ذکر شده است! ولی خوب تا این لحظه برای مثال کتابخانه XML RPC برای دات نت 4 به روز نشده است ولی سورس کامل آن در دسترس است.
برای رفع این مشکل ابتدا سورس این کتابخانه‌ها را دریافت کرده و سپس در فایل AssemblyInfo.cs آن‌ها یک سطر زیر را حذف نموده و پروژه را مجددا کامپایل کنید:
[assembly: AllowPartiallyTrustedCallers]
علت وجود این ویژگی‌ در کتابخانه‌های ذکر شده این است که بتوان از آن‌ها در محیط‌های اصطلاحا partially trusted (برای مثال هر برنامه‌ای که در internet zone یا intranet zone اجرا می‌شود) استفاده کرد. در دات نت 4 با تغییرات انجام شده معنای AllowPartiallyTrustedCallers به security-transparency تغییر کرده است. بنابراین با قید آن یا باید هر جایی که متد GetObjectData ذکر شده در این کتابخانه‌ها تحریف می‌شود، ویژگی SecurityCritical را صریحا اعمال کرد یا اینکه می‌توان AllowPartiallyTrustedCallers را حذف کرده و وظیفه‌ی انجام آن‌را به CLR محول نمود.


برای مطالعه بیشتر:
Using Libraries from Partially Trusted Code
Security Changes in the .NET Framework 4
TypeLoadException based on Security-Transparent Code, Level 2
Making log4net run on .NET 4.0

مطالب
تنظیمات 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 را ارسال نمی‌کند
اطلاعات بیشتر 
مطالب
بررسی ساختار ویجت‌های وب Kendo UI
ویجت‌های وب Kendo UI کدامند؟

ویجت‌های وب Kendo UI مجموعه‌ای از کنترل‌های سفارشی HTML 5 هستند که برفراز jQuery تهیه شده‌اند. این کنترل‌ها برای برنامه‌های وب و همچنین برنامه‌های دسکتاپ لمسی طراحی شده‌اند.
بهترین روش برای مشاهده‌ی این مجموعه، مراجعه به فایل examples\index.html پوشه‌ی اصلی Kendo UI است که لیست کاملی از این ویجت‌ها را به همراه مثال‌های مرتبط ارائه می‌دهد. تعدادی از اعضای این مجموعه شامل کنترل‌های ذیل هستند:
Window, TreeView, Tooltip, ToolBar, TimePicker, TabStrip, Splitter, Sortable, Slider, Gantt, Scheduler, ProgressBar, PanelBar, NumericTextBox, Notification, MultiSelect, Menu, MaskedTextBox, ListView, PivotGrid, Grid, Editor, DropDownList, DateTimePicker, DatePicker, ComboBox, ColorPicker, Calendar, Button, AutoComplete


نحوه‌ی استفاده کلی از ویجت‌های وب Kendo UI

با توجه به اینکه کنترل‌های Kendo UI مبتنی بر jQuery هستند، نحوه‌ی استفاده از آن‌ها، مشابه سایر افزونه‌های جی‌کوئری است. ابتدا المانی به صفحه اضافه می‌شود:
 <input id="pickDate" type="text"/>
سپس این المان را در رویداد document ready، به یکی از کنترل‌های Kendo UI مزین خواهیم کرد. برای مثال تزئین یک TextBox معمولی با یک Date Picker:
 <script type="text/javascript">
  $(function() {
        $("#pickDate").kendoDatePicker();
  });
</script>
روش دیگری به نام declarative initialization نیز برای اعمال ویجت‌های وب Kendo UI قابل استفاده است که از ویژگی‌های *-data مرتبط با HTML 5 کمک می‌گیرد. برای نمونه، کدهای جاوا اسکریپتی فوق را می‌توان با ویژگی data-role ذیل جایگزین کرد:
 <input id="dateOfBirth" type="text" data-role="datepicker" />
اگر در این حالت برنامه را اجرا کنید، تفاوتی را مشاهده نخواهید کرد.
برای فعال سازی حالت declarative initialization باید به دو نکته‌ی مهم دقت داشت:
الف) در مطلب معرفی Kendo UI اسکریپت‌های ذیل برای آماده سازی Kendo Ui معرفی شدند:
 <!--KendoUI: Web-->
<link href="styles/kendo.common.min.css" rel="stylesheet" type="text/css" />
<link href="styles/kendo.default.min.css" rel="stylesheet" type="text/css" />
<script src="js/jquery.min.js" type="text/javascript"></script>
<script src="js/kendo.web.min.js" type="text/javascript"></script>

<!--KendoUI: DataViz-->
<link href="styles/kendo.dataviz.min.css" rel="stylesheet" type="text/css" />
<script src="js/kendo.dataviz.min.js" type="text/javascript"></script>

<!--KendoUI: Mobile-->
<link href="styles/kendo.mobile.all.min.css" rel="stylesheet" type="text/css" />
<script src="js/kendo.mobile.min.js" type="text/javascript"></script>
باید دقت داشت که در آن واحد نمی‌توان تمام این بسته‌ها را با هم بکار برد؛ چون برای مثال فایل‌های جداگانه ویجت‌های وب و موبایل با هم تداخل ایجاد می‌کنند. بجای اینکار بهتر است از فایل‌های kendo.all.min.js (که حاوی تمام اسکریپت‌های لازم است) و css‌های عنوان شده استفاده کرد:
 <link href="styles/kendo.common.min.css" rel="stylesheet" type="text/css" />
<link href="styles/kendo.default.min.css" rel="stylesheet" type="text/css" />
<script src="js/jquery.min.js" type="text/javascript"></script>
<script src="js/kendo.all.min.js" type="text/javascript"></script>
ب) data-roleها توسط متد kendo.init فعال می‌شوند.
یک مثال کامل:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title></title>

    <link href="styles/kendo.common.min.css" rel="stylesheet" type="text/css" />
    <link href="styles/kendo.default.min.css" rel="stylesheet" type="text/css" />
    <script src="js/jquery.min.js" type="text/javascript"></script>
    <script src="js/kendo.all.min.js" type="text/javascript"></script>

    <script type="text/javascript">
        $(function () {
            $("#pickDate").kendoDatePicker();
        });

        $(function () {
            // initialize any widgets in the #container div
            kendo.init($("#container"));
        });
    </script>
</head>
<body>
    <span>
        Pick a date: <input id="pickDate" type="text" />
    </span>

    <div id="container">
        <input id="dateOfBirth" type="text" data-role="datepicker" />
        <div id="colors"
             data-role="colorpalette"
             data-columns="4"
             data-tile-size="{ width: 34, height: 19 }"></div>
    </div>
</body>
</html>
- در این مثال نحوه‌ی پیوست تمام فایل‌های لازم Kendo UI را به صورت یکجا ملاحظه می‌کنید که در ابتدای head صفحه ذکر شده‌اند.
- در اینجا pickDate به صورت معمولی فعال شد‌ه‌است.
- اما در قسمت kendo.init نام یک ناحیه یا نام یک کنترل را می‌توان ذکر کرد. برای مثال در اینجا کل ناحیه‌ی مشخص شده توسط یک div با id مساوی container به صورت یکجا با تمام کنترل‌های داخل آن فعال گردیده‌است.

بنابراین برای اعمال declarative initialization، یک ناحیه را توسط kendo.init مشخص کرده و سپس توسط data-roleها، نام ویجت وب مورد نظر را به صورت lower case مشخص می‌کنیم. همچنین فایل‌های اسکریپت مورد استفاده نیز نباید تداخلی داشته باشند.


تنظیمات ویجت‌های وب Kendo UI

تاکنون نمونه‌ی ساده‌ای از بکارگیری ویجت‌های وب Kendo UI را بررسی کردیم؛ اما این ویجت‌ها توسط تنظیمات پیش بینی شده برای آن‌ها بسیار قابل تنظیم و تغییر هستند. تنظیمات آن‌ها نیز بستگی به روش استفاده و آغاز آن‌ها دارد. برای مثال اگر این ویجت‌ها را توسط کدهای جاوا اسکریپتی آغاز کرده‌اید، در همانجا توسط پارامترهای افزونه‌ی جی‌کوئری می‌توان تنظیمات مرتبط را اعمال کرد:
 <script type="text/javascript">
  $(function () {
      $("#pickDate").kendoDatePicker({
              format: "yyyy/MM/dd"
        });
  });
</script>
که در اینجا توسط پارامتر format، نحوه‌ی دریافت تاریخ نهایی مشخص می‌شود.
در حالت declarative initialization، پارامتر format تبدیل به ویژگی data-format خواهد شد:
<input id="dateOfBirth" type="text"
          data-role="datepicker"
          data-format="yyyy/MM/dd" />


تنظیمات DataSource ویجت‌های وب

بسیاری از ویجت‌های وب Kendo UI با داده‌ها سر و کار دارند مانند Grid، Auto Complete، Combo box و غیره. این کنترل‌ها داده‌های خود را از طریق خاصیت DataSource دریافت می‌کنند. برای نمونه در اینجا یک combo box را در نظر بگیرید. در مثال اول، خاصیت dataSource کنترل ComboBox در همان افزونه‌ی جی‌کوئری تنظیم شده‌است:
    <input id="colorPicker1" />
    <script type="text/javascript">
        $(document).ready(function () {
            $("#colorPicker1").kendoComboBox({
                dataSource: ["Blue", "Green", "Red", "Yellow"]
            });
        });
    </script>
و در مثال دوم، نحوه‌ی مقدار دهی ویژگی data-source را در حالت declarative initialization مشاهده می‌کنید. همانطور که عنوان شد، در این حالت ذکر متد kendo.init بر روی یک ناحیه و یا یک کنترل ویژه، جهت آغاز فعالیت آن ضروری است:
    <input id="colorPicker2" data-role="combobox" data-source='["Blue", "Green", "Red", "Yellow"]' />
    <script type="text/javascript">
        $(document).ready(function () {
            kendo.init($("#colorPicker2"));
        });
    </script>


کار با رویدادهای ویجت‌های وب

نحوه‌ی کار با رویدادهای ویجت‌های وب نیز بر اساس نحوه‌ی آغاز آن‌ها متفاوت است. در مثال‌های ذیل، دو حالت متفاوت تنظیم رویداد change را توسط خواص افزونه‌ی جی‌کوئری:
    <input id="colorPicker3" />
    <script type="text/javascript">
        function onColorChange(e) {
             alert('Color Change!');
        }

        $(document).ready(function () {
            $("#colorPicker3").kendoComboBox({
                dataSource: ["Blue", "Green", "Red", "Yellow"],
                change: onColorChange
            });
        });
    </script>
و همچنین توسط ویژگی data-change مشاهده می‌کنید:
    <input id="colorPicker4" data-role="combobox"
           data-source='["Blue", "Green", "Red", "Yellow"]'
           data-change="onColorChange" />

    <script type="text/javascript">
        function onColorChange(e) {
            alert('Color Change!');
        }

        $(document).ready(function () {
            kendo.init($("#colorPicker4"));
        });
    </script>
در هر دو حالت، انتخاب یک گزینه‌ی جدید combo box، سبب فراخوانی متد callback ایی به نام onColorChange می‌شود.


تغییر قالب ویجت‌های وب

Kendo UI همیشه یک جفت CSS را جهت تعیین قالب‌های ویجت‌های خود، مورد استفاده قرار می‌دهد. برای نمونه در مثال‌های فوق، kendo.common.min.css حاوی اطلاعات محل قرارگیری و اندازه‌ی ویجت‌ها است. شیوه نامه‌ی دوم همیشه به شکل kendo.[skin].min.css تعریف می‌شود که دارای اطلاعات رنگ و پس زمینه‌ی ویجت‌ها خواهد بود؛ مانند kendo.black.min.css، kendo.blueopal.min.css و امثال آن که در پوشه‌ی styles قابل مشاهده هستند.
همچنین باید دقت داشت که همیشه common باید پیش از skin ذکر شود؛ زیرا در تعدادی از حالات، شیوه نامه‌ی skin، اطلاعات common را بازنویسی می‌کند.
علاوه بر skinهای پیش فرض موجود در پوشه‌ی styles، امکان استفاده از یک theme builder آنلاین نیز وجود دارد: kendo-ui-themebuilder
نظرات مطالب
فعال سازی قسمت ارسال فایل و تصویر ویرایشگر آنلاین RedActor در ASP.NET MVC
آقای نصیری من برای ذخیره یه متن با redactor به مشکل خوردم. Error ای به صورت زیر میدهد: 
System.Web.HttpRequestValidationException: A potentially dangerous Request.Form value was detected from the client (Body="<p>aaaaaaa...").