مطالب
ایجاد سرویس Account Manager با تکنولوژی های Identity 2.1 و Web API 2.2
ASP.NET Identity 2.1 جدیدترین فریم ورک عضویت و مدیریت کاربر است که چندی پیش توسط شرکت مایکروسافت منتشر شد. این سیستم عضویت می‌تواند به تمامی فریم‌ورک‌های دات نتی مانند Web API، MVC و ... متصل گردد.
در این دوره چند قسمتی به همراه یک پروژه‌ی نمونه، نحوه‌ی ارتباط Identity و Web API را نمایش خواهیم داد. در قسمت front-end این پروژه‌ی SPA، ما از AngularJs استفاده خواهیم نمود. قسمت front-end که توسط AngularJs توسعه داده می‌شود از bearet token based authentication استفاده می‌کند. این متد از JSON Web Token برای فرایند Authorization بهره می‌گیرد که به اختصار آن را JWT نیز می‌نامند. این روش سیستم اعتبار سنجی role based بوده و  تمامی آنچه را که در یک سیستم membership داریم، پوشش می‌دهد. توجه داشته باشید که برای فهم بهتر تمامی مراحل، ما پروژه را بدون هیچ قالب از پیش تعریف شده‌ای در VS2013 آغاز می‌کنیم.
به دلیل اینکه پروژه در طی چند قسمت انجام می‌پذیرد آن را به بخش‌های زیر تقسیم می‌کنیم.
  1.  تنظیمات اولیه ASP.NET Identity 2.1 با Web API
  2.  ایجاد Account Confirmation به وسیله Identity به همراه تنظیمات policy برای user name و password
  3.  توسعه OAuth Json Web Token Authentication به کمک Web API و Identity
  4.  ایجاد یک سیستم Role Based و تایید صلاحیت‌های مربوط به آن
  5.  توسعه Web API Claims Authorization در Identity 2.1
  6.  توسعه بخش front-end با AngularJs

تنظیمات اولیه ASP.NET Identity 2.1 با Web API 

1. تنظیمات ASP.Net Identity 2.1

1-1. ساخت یک پروژه Web API

در ابتدا ما یک empty solution را با نام "AspNetIdentity" همانند شکل مقابل می‌سازیم.

یک ASP.NET Web Application با نام "AspNetIdentity.WebApi" را به این solution اضافه می‌نماییم. در بخش select template ما empty template را انتخاب می‌کنیم. همچنین در قسمت add folders and core references for: نیز هیچیک از گزینه‌ها را انتخاب نمی‌کنیم.

1-2. نصب package‌های مورد نیاز از Nuget

در زیر package‌های مورد نیاز برای ASP.NET Web API و Owin را مشاهده میکنید. همچنین package‌های مربوط به ASP.Net Identity 2.1 نیز در زیر قرار داده شده‌اند.

Install-Package Microsoft.AspNet.Identity.Owin -Version 2.1.0
Install-Package Microsoft.AspNet.Identity.EntityFramework -Version 2.1.0
Install-Package Microsoft.Owin.Host.SystemWeb -Version 3.0.0
Install-Package Microsoft.AspNet.WebApi.Owin -Version 5.2.2
Install-Package Microsoft.Owin.Security.OAuth -Version 3.0.0
Install-Package Microsoft.Owin.Cors -Version 3.0.0

1-3. اضافه کردن user class و database context

حال که تمامی پکیج‌های مورد نیاز را به پروژه خود اضافه نمودیم، قصد داریم تا اولین کلاس EF را با نام "ApplicationUser" به پروژه اضافه کنیم. این کلاس، کاربری را که قصد ثبت نام در membership system، دارد را نمایش می‌دهد. برای این کار ما یک کلاس جدید را به نام "ApplicationUser" می‌سازیم و کلاس "Microsoft.AspNet.Identity.EntityFramework.IdentityUser" را در آن به ارث می‌بریم.

برای این کار ما یک پوشه‌ی جدید را در برنامه با نام "Infrastructure" می‌سازیم و درون آن کلاس ذکر شده را اضافه می‌کنیم:

 public class ApplicationUser : IdentityUser
    {
        [Required]
        [MaxLength(100)]
        public string FirstName { get; set; }
 
        [Required]
        [MaxLength(100)]
        public string LastName { get; set; }
 
        [Required]
        public byte Level { get; set; }
 
        [Required]
        public DateTime JoinDate { get; set; }
 
    }

پس از افزودن کلاس User، نوبت به اضافه نمودن Db Context است. این کلاس وظیفه‌ی ارتباط با پایگاه داده را بر عهده دارد. ما یک کلاس جدید را با نام ApplicationDbContext، به پوشه‌ی Infrastructure اضافه می‌نماییم. کد مربوط به این کلاس به صورت زیر است:

 public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
    {
        public ApplicationDbContext()
            : base("DefaultConnection", throwIfV1Schema: false)
        {
            Configuration.ProxyCreationEnabled = false;
            Configuration.LazyLoadingEnabled = false;
        }
 
        public static ApplicationDbContext Create()
        {
            return new ApplicationDbContext();
        }
 
    }

همانطور که ملاحظه می‌کنید این کلاس از IdentityDbContext ارث بری نموده است. این کلاس یک نسخه‌ی جدیدتر از DbContext است که تمامی نگاشت‌های Entity Framework Code First را انجام می‌دهد. در ادامه ما یک Connection String را با نام DefaultConnection در فایل web.config اضافه می‌نماییم.

همچنین متد static ایی را با نام Create که در تکه کد فوق از سوی Owin Startup class فراخوانی می‌گردد که در ادامه به شرح آن نیز خواهیم پرداخت.

ConnectionString قرار داده شده در فایل web.config در قسمت زیر قرار داده شده است:

<connectionStrings>
    <add name="DefaultConnection" connectionString="Data Source=.\sqlexpress;Initial Catalog=AspNetIdentity;Integrated Security=SSPI;" providerName="System.Data.SqlClient" />
  </connectionStrings>

قدم 4: ساخت پایگاه داده و فعال سازی DB Migration

حال ما باید EF CodeFirst Migration را برای آپدیت کردن دیتابیس، بجای دوباره ساختن آن به ازای هر تغییری، فعال نماییم. برای این کار بایستی در قسمت NuGet Package Manager Console عبارت زیر را وارد نماییم:

enable-migrations
add-migration InitialCreate

اگر تا به اینجای کار تمامی مراحل به درستی صورت گرفته باشند، پوشه‌ی Migration با موفقیت ساخته می‌شود. همچنین پایگاه داده متشکل از جداول مورد نیاز برای سیستم Identity نیز ایجاد می‌شود. برای اطلاعات بیشتر می‌توانید مقالات مرتبط با Code First را مطالعه نمایید. ساختار جداول ما باید به صورت زیر باشد:

در بخش بعدی، کلاس‌های مربوط به UserManager را ایجاد خواهیم کرد و پس از آن فایل Startup Owin را برای مدیریت کاربران، تشریح می‌کنیم.

مطالب
ساخت یک بلاگ ساده با Ember.js، قسمت اول
پس از آشنایی مقدماتی با اجزای مهم تشکیل دهنده‌ی Ember.js (^ و ^)، بهتر است این دانسته‌ها را جهت تکمیل یک پروژه‌ی ساده‌ی تک صفحه‌ای بلاگ، بکار بگیریم.
در این بلاگ می‌توان:
- یک مطلب جدید را ارسال کرد.
- مطالب قابل ویرایش و یا حذف هستند.
- مطالب بلاگ قسمت ارسال نظرات دارند.
- امکان گزارشگیری از آخرین نظرات ارسالی وجود دارد.
- سایت صفحات درباره و تماس با ما را نیز دارا است.


ساختار پوشه‌های برنامه

در تصویر ذیل، ساختار پوشه‌های برنامه بلاگ را ملاحظه می‌کنید. چون قسمت سمت کلاینت این برنامه کاملا جاوا اسکریپتی است، پوشه‌های App، Controllers، Libs، Models، Routes و Templates آن در پوشه‌ی Scripts تعریف شده‌اند و به این ترتیب می‌توان تفکیک بهتری را بین اجزای تشکیل دهنده‌ی یک برنامه‌ی تک صفحه‌ای وب Emeber.js پدید آورد.


فایل CSS بوت استرپ نیز به پوشه‌ی Content اضافه شده‌است.


دریافت پیشنیازهای سمت کاربر برنامه

در ساختار پوشه‌های فوق، از پوشه‌ی Libs برای قرار دادن کتابخانه‌های پایه برنامه مانند jQuery و Ember.js استفاده خواهیم کرد. به این ترتیب:
- نیاز به آخرین نگارش‌های Ember.js و همچنین افزونه‌ی Ember-Data آن برای کار ساده‌تر با داده‌ها و سرور وجود دارد. این فایل‌ها را از آدرس ذیل می‌توانید دریافت کنید (نسخه‌‌های نیوگت به دلیل قدیمی بودن و به روز نشدن مداوم آن‌ها توصیه نمی‌شوند):
http://emberjs.com/builds/#/beta
برای حالت آزمایش برنامه، استفاده از فایل‌های دیباگ آن توصیه می‌شوند (فایل‌هایی با نام اصلی و بدون پسوند prod یا min). زیرا این فایل‌ها خطاها و اطلاعات بسیار مفصلی را از اشکالات رخ داده، در کنسول وب مرورگرها، فایرباگ و یا Developer tools آن‌ها نمایش می‌دهند. نسخه‌ی min برای حالت ارائه‌ی نهایی برنامه است. نسخه‌ی prod همان نسخه‌ی دیباگ است با حذف اطلاعات دیباگ (نسخه‌ی production فشرده نشده). نسخه‌ی فشرده شده‌ی prod آن، فایل min نهایی را تشکیل می‌دهد.
- دریافت جی‌کوئری
- آخرین نگارش handlebars.js را از سایت رسمی آن دریافت کنید: http://handlebarsjs.com
- Ember Handlebars Loader: https://github.com/michaelrkn/ember-handlebars-loader
- Ember Data Local Storage Adapter: https://github.com/kurko/ember-localstorage-adapter

ترتیب تعریف و قرارگیری این فایل‌ها را پس از دریافت، در فایل index.html قرار گرفته در ریشه‌ی سایت، در کدهای ذیل مشاهده می‌کنید:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
  <title>Ember Blog</title>
 
  <link href="Content/bootstrap.css" rel="stylesheet" />
  <link href="Content/bootstrap-theme.css" rel="stylesheet" />
  <link href="Content/styles.css" rel="stylesheet" />
 
  <script src="Scripts/Libs/jquery-2.1.1.min.js" type="text/javascript"></script>
  <script src="Scripts/Libs/bootstrap.min.js" type="text/javascript"></script>
  <script src="Scripts/Libs/handlebars-v2.0.0.js" type="text/javascript"></script>
  <script src="Scripts/Libs/ember.js" type="text/javascript"></script>
  <script src="Scripts/Libs/ember-handlebars-loader-0.0.1.js" type="text/javascript"></script>
  <script src="Scripts/Libs/ember-data.js" type="text/javascript"></script>
  <script src="Scripts/Libs/localstorage_adapter.js" type="text/javascript"></script> 
</head>
<body>
 
</body>
</html>


اصلاح فایل ember-handlebars-loader-0.0.1.js
اگر به فایل ember-handlebars-loader-0.0.1.js مراجعه کنید، مسیر فایل‌های قالب handlebars قسمت‌های مختلف برنامه را از پوشه‌ی templates واقع در ریشه‌ی سایت می‌خواند. با توجه به تصویر ساختار پوشه‌ی پروژه‌ی جاری، پوشه‌ی template به داخل پوشه‌ی Scripts منتقل شده‌است و نیاز به یک چنین اصلاحی دارد:
 url: "Scripts/Templates/" + name + ".hbs",
کار اسکریپت ember-handlebars-loader-0.0.1.js بارگذاری خودکار فایل‌های hbs یا handlebars از پوشه‌ی قالب‌های سفارشی برنامه است. در این حالت اگر برنامه را اجرا کنید، خطای 404 را دریافت خواهید کرد. از این جهت که mime-type پسوند hbs در IIS تعریف نشده‌است. اضافه کردن آن نیز ساده‌است. به فایل web.config برنامه مراجعه کرده و تغییر ذیل را اعمال کنید:
 <system.webServer>
  <staticContent>
        <mimeMap fileExtension=".hbs" mimeType="text/x-handlebars-template" />
  </staticContent>
 </system.webServer>


مزیت استفاده از نسخه‌ی دیباگ ember.js در حین توسعه‌ی برنامه

نسخه‌ی دیباگ ember.js علاوه بر به همراه داشتن خطاهای بسیار جامع و توضیح علل مشکلات، مواردی را که در آینده منسوخ خواهند شد نیز توضیح می‌دهد:


برای مثال در اینجا عنوان شده‌است که دیگر از linkTo استفاده نکنید و آن‌را به link-to تغییر دهید.
همچنین اگر از مرورگر کروم استفاده می‌کنید، افزونه‌ی Ember Inspector را نیز می‌توانید نصب کنید تا اطلاعات بیشتری را از جزئیات مسیریابی‌های تعریف شده و قالب‌های Ember.js بتوان مشاهده کرد. این افزونه به صورت یک برگه‌ی جدید در Developer tools آن ظاهر می‌شود.


ایجاد شیء Application

همانطور که در قسمت‌های پیشین نیز عنوان شد (^ و ^  )، یک برنامه‌ی Ember.js با تعریف وهله‌ای از شیء Application آن آغاز می‌شود. برای این منظور به پوشه‌ی App مراجعه کرده و فایل جدید Scripts\App\blogger.js را اضافه کنید؛ با این محتوا:
 Blogger = Ember.Application.create();
مدخل این فایل را نیز پس از تعاریف وابستگی‌های اصلی برنامه، به فایل index.html اضافه خواهیم کرد:
<script src="Scripts/App/blogger.js" type="text/javascript"></script>
تا اینجا برپایی اولیه‌ی برنامه‌ی تک صفحه‌ای وب مبتنی بر Ember.js ما به پایان می‌رسد.


تعاریف مسیریابی و قالب‌ها

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


کار router در Ember.js، نگاشت آدرس درخواستی توسط کاربر، به یک route یا مسیریابی تعریف شده‌است. به این ترتیب مدل، کنترلر و قالب آن route به صورت خودکار بارگذاری و پردازش خواهند.
با مراجعه به ریشه‌ی سایت، فایل index.html بارگذاری می‌شود.


در اینجا تصویری از صفحه‌ی آغازین بلاگ را مشاهده می‌کنید. در آن صفحه‌ی posts همان ریشه‌ی سایت نیز می‌باشد. بنابراین نیاز است ابتدا مسیریابی آن‌را تعریف کرد. برای این منظور، فایل جدید Scripts\App\router.js را به پوشه‌ی App اضافه کنید؛ با این محتوا:
Blogger.Router.map(function () {
   this.resource('posts', { path: '/' });
});
علت تعریف قسمت path این است که ریشه‌ی سایت (/) نیز به مسیریابی posts ختم شود. در غیر اینصورت کاربر با مراجعه به ریشه‌ی سایت، یک صفحه‌ی خالی را مشاهده خواهد کرد؛ زیرا به صورت پیش فرض، آدرس قابل ترجمه‌ی یک صفحه، با آدرس و نام مسیریابی آن یکی است.

همچنین مدخل آن‌را نیز در فایل index.html تعریف نمائید:
 <script src="Scripts/App/blogger.js" type="text/javascript"></script>
<script src="Scripts/App/router.js" type="text/javascript"></script>
در اینجا Blogger همان شیء Application برنامه است که پیشتر در فایل Scripts\App\blogger.js تعریف کردیم. سپس به کمک متد Blogger.Router.map، اولین مسیریابی برنامه را افزوده‌ایم.


افزودن مسیریابی و قالب posts

در ادامه فایل جدید Scripts\Templates\posts.hbs را اضافه کنید. به این ترتیب قالب خالی مطالب به پوشه‌ی templates اضافه می‌شود. محتوای این فایل را به نحو ذیل تنظیم کنید:
<div class="container">
  <h1>Emeber.js blog</h1>
  <ul>
   <li>Item 1</li>
   <li>Item 2</li>
   <li>Item 3</li>
  </ul>
</div>
در اینجا دیگر نیازی به ذکر تگ script از نوع text/x-handlebars نیست.
برای بارگذاری این قالب نیاز است آن‌را به template loader توضیح داده شده در ابتدای بحث، در فایل index.html اضافه کنیم:
 <script>
 EmberHandlebarsLoader.loadTemplates([
 'posts'
 ]);
</script>
اکنون برنامه را اجرا کنید. به تصویر ذیل خواهید رسید که در آن افزونه‌ی Ember Inspector نیز نمایش داده شده‌است:



افزودن مسیریابی و قالب about

در ادامه قصد داریم صفحه‌ی about را اضافه کنیم. مجددا با افزودن مسیریابی آن به فایل Scripts\App\router.js شروع می‌کنیم:
Blogger.Router.map(function () {
  this.resource('posts', { path: '/' });
  this.resource('about');
});
سپس فایل قالب آن‌را در مسیر Scripts\Templates\about.hbs ایجاد خواهیم کرد؛ برای مثال با این محتوای فرضی:
 <h1>About Ember Blog</h1>
<p>Bla bla bla!</p>
اکنون نام این فایل را به template loader فایل index.html اضافه می‌کنیم.
 <script>
 EmberHandlebarsLoader.loadTemplates([
 'posts', 'about'
 ]);
</script>
اگر از قسمت قبل به خاطر داشته باشید، ساختار کلی قالب‌های ember.js یک چنین شکلی را دارد:
 <script type="text/x-handlebars" data-template-name="about">

</script>
اسکریپت template loader، این تعاریف را به صورت خودکار اضافه می‌کند. مقدار data-template-name را نیز به نام فایل متناظر با آن تنظیم خواهد کرد و چون ember.js بر اساس ایده‌ی convention over configuration کار می‌کند، مسیریابی about با کنترلری به همین نام و قالبی هم نام پردازش خواهد شد. بنابراین نام فایل‌های قالب را باید بر اساس مسیریابی‌های متناظر با آن‌ها تعیین کرد.
برای آزمایش این مسیر و قالب جدید آن، آدرس http://localhost/#/about را بررسی کنید.


اضافه کردن منوی ثابت بالای سایت

روش اول این است که به ابتدای هر دو قالب about.hbs و posts.hbs، تعاریف منو را اضافه کنیم. مشکل این‌کار، تکرار کدها و پایین آمدن قابلیت نگهداری برنامه است. روش بهتر، افزودن کدهای مشترک بین صفحات، در قالب application برنامه است. نمونه‌ی آن‌را در مثال قسمت قبل مشاهده کرده‌اید. در اینجا چون قصد نداریم به صورت دستی قالب‌ها را به صفحه اضافه کنیم و همچنین برای ساده شدن نگهداری برنامه، قالب‌ها را در فایل‌های مجزایی قرار داده‌ایم، تنها کافی است فایل جدید Scripts\Templates\application.hbs را به پوشه‌ی قالب‌های برنامه اضافه کنیم؛ با این محتوا:
<div class='container'>
 <nav class='navbar navbar-default' role='navigation'>
  <ul class='nav navbar-nav'>
  <li>{{#link-to 'posts'}}Posts{{/link-to}}</li>
  <li>{{#link-to 'about'}}About{{/link-to}}</li>
  </ul>
 </nav>

  {{outlet}}
</div>
و سپس همانند قبل، نام فایل قالب اضافه شده را به template loader فایل index.html اضافه می‌کنیم:
<script>
 EmberHandlebarsLoader.loadTemplates([
 'posts', 'about', 'application'
 ]);
</script>


افزودن مسیریابی و قالب contact

برای افزودن صفحه‌ی تماس با مای سایت، ابتدا مسیریابی آن‌را در فایل Scripts\App\router.js تعریف می‌کنیم:
Blogger.Router.map(function () {
  this.resource('posts', { path: '/' });
  this.resource('about');
  this.resource('contact');
});
سپس قالب متناظر با آن‌را به نام Scripts\Templates\contact.hbs اضافه خواهیم کرد؛ فعلا با این محتوای اولیه:
<h1>Contact</h1>
<ul>
  <li>Phone: ...</li>
  <li>Email: ...</li>
</ul>
و بعد template loader فایل index.html را از وجود آن مطلع خواهیم کرد:
 <script>
 EmberHandlebarsLoader.loadTemplates([
 'posts', 'about', 'application', 'contact' ]);
</script>
همچنین لینکی به مسیریابی آن‌را به فایل Scripts\Templates\application.hbs که منوی سایت در آن تعریف شده‌است، اضافه می‌کنیم:
<div class='container'>
 <nav class='navbar navbar-default' role='navigation'>
  <ul class='nav navbar-nav'>
  <li>{{#link-to 'posts'}}Posts{{/link-to}}</li>
  <li>{{#link-to 'about'}}About{{/link-to}}</li>
  <li>{{#link-to 'contact'}}Contact{{/link-to}}</li>
  </ul>
 </nav>

  {{outlet}}
</div>


تعریف مسیریابی تو در تو در صفحه‌ی contact

در حال حاضر صفحه‌ی Contact، ایمیل و شماره تماس را در همان بار اول نمایش می‌دهد. می‌خواهیم این دو را تبدیل به دو لینک جدید کنیم که با کلیک بر روی هر کدام، محتوای مرتبط، در قسمتی از همان صفحه بارگذاری شده و نمایش داده شود.
برای اینکار نیاز است مسیریابی را تو در تو تعریف کنیم:
Blogger.Router.map(function () {
  this.resource('posts', { path: '/' });
  this.resource('about');
  this.resource('contact', function () {
   this.resource('email');
   this.resource('phone');
  });
});
اگر مسیریابی‌های email و یا phone را به صورت مستقل مانند about و یا posts تعریف کنیم، با کلیک کاربر بر روی لینک متناظر با هر کدام، یک صفحه‌ی کاملا جدید نمایش داده می‌شود. اما در اینجا قصد داریم تنها قسمت کوچکی از همان صفحه‌ی contact را با محتوای ایمیل و یا شماره تماس جایگزین نمائیم. به همین جهت مسیریابی‌های متناظر را در اینجا به صورت تو در تو و ذیل مسیریابی contact تعریف کرده‌ایم.

پس از آن دو فایل قالب جدید Scripts\Templates\email.hbs را با محتوای:
 <h2>Email</h2>
<p>
<span></span> Email name@site.com.
</p>
و فایل قالب Scripts\Templates\phone.hbs را با محتوای:
 <h2>Phone</h2>
<p>
<span></span> Call 12345678.
</p>
به پوشه‌ی قالب‌ها اضافه نمائید به همراه معرفی نام آن‌ها به template loader برنامه در صفحه‌ی index.html :
 <script>
 EmberHandlebarsLoader.loadTemplates([
 'posts', 'about', 'application', 'contact', 'email', 'phone' ]);
</script>
اکنون به قالب contact.hbs مجددا مراجعه کرده و تعاریف آن را به نحو ذیل تغییر دهید:
<h1>Contact</h1>
<div class="row">
  <div class="col-md-6">
   <p>
    Want to get in touch?
    <ul>
      <li>{{#link-to 'phone'}}Phone{{/link-to}}</li>
      <li>{{#link-to 'email'}}Email{{/link-to}}</li>
    </ul>
   </p>
  </div>
  <div class="col-md-6">
   {{outlet}}
  </div>
</div>
در اینجا دو لینک به مسیریابی‌های Phone و Email تعریف شده‌اند. همچنین {{outlet}} نیز قابل مشاهده است. با کلیک بر روی لینک Phone، مسیریابی آن فعال شده و سپس قالب متناظر با آن در قسمت {{outlet}} رندر می‌شود. در مورد لینک Email نیز به همین نحو رفتار خواهد شد.


در اینجا نحوه‌ی پردازش مسیریابی contact را ملاحظه می‌کنید. ابتدا درخواستی جهت مشاهده‌ی آدرس http://localhost/#/contact دریافت می‌شود. سپس router این درخواست را به مسیریابی همنامی منتقل می‌کند. این مسیریابی ابتدا قالب عمومی application را رندر کرده و سپس قالب اصلی و همنام مسیریابی جاری یا همان contact.hbs را رندر می‌کند. در این صفحه چون مسیریابی تو در تویی تعریف شده‌است، اگر درخواستی برای مشاهده‌ی http://localhost/#/contact/phone دریافت شود، محتوای آن‌را در {{outlet}} قالب contact.hbs جاری رندر می‌کند.



کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید:
EmberJS03_01.zip
نظرات اشتراک‌ها
پیاده سازی چندمستاجری با EF Core و Blazor Server
blazor بحث front-end هست (مانند React که معادل Blazor WASM است یا Server-side rendering (SSR) آن که معادل Blazor Server هست). پیاده سازی چندمستاجری، بیشتر بحث backend هست در اصل؛ که DNT Framework هم نمونه‌ای از آن‌را دارد.
مطالب
کار با Razor در ASP.NET Core 2.0
پیش نویس: این مقاله ترجمه شده فصل 5 کتاب Pro Asp.Net Core MVC2 می‌باشد.


ایجاد یک پروژه با استفاده Razor

در ادامه با هم یک مثال را با استفاده از Razor ایجاد می‌کنیم. یک پروژه جدید را با قالب Empty و با نام Razor ایجاد می‌کنیم.

مراحل:

1- ابتدا در کلاس startup قابلیت MVC را فعال می‌کنیم؛ با قرار دادن کد زیر در متد ConfigureServices:
 services.AddMvc();
و بعد کد زیر را که مربوط به اجرای پروژه‌ی hello Word است ، از متد Configure حذف می‌کنیم:
app.Run(async (context) =>
{
   await context.Response.WriteAsync("Hello World!");
});
در نهایت محتویات  فایل StartUp به صورت زیر می‌باشد:

namespace Razor
{
    public class Startup
    {
        // This method gets called by the runtime. Use this method to add services to the container.
        // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            //app.Run(async (context) =>
            //{
            //    await context.Response.WriteAsync("Hello World!");
            //});
        }
    }
}


ایجاد یک Model
 یک پوشه جدید را به نام Models ایجاد و بعد در این پوشه یک کلاس را به نام Product ایجاد می‌کنیم و کدهای زیر را در آن قرار میدهیم:
namespace Razor.Models
{
    public class Product
    {
        public int ProductID { get; set; }
        public string Name { get; set; }
        public string Description { get; set; }
        public decimal Price { get; set; }
        public string Category { set; get; }
    }
}

ایجاد Controller
تنظیمات پیشفرض را در فایل Startup انجام داده‌ایم. درخواست‌هایی را که توسط کاربر ارسال میشوند، به controller پیشفرضی که نامش در اینجا Home است، ارسال می‌کند. حالا ما یک پوشه جدید را به نام Controllers ایجاد می‌کنیم و در آن یک کنترلر جدید را به نام HomeController ایجاد می‌کنیم و کدهای زیر را در آن قرار میدهیم:
namespace Razor.Controllers
{
    public class HomeController : Controller
    {
        // GET: /<controller>/
        public ViewResult Index()
        {
            Product myProduct = new Product
            {
                ProductID = 1,
                Name = "Kayak",
                Description = "A boat for one person",
                Category = "Watersports",
                Price = 275M
            };
            return View(myProduct);
        }
    }
}
در این کلاس یک Action Method را به نام index ایجاد می‌کنیم. سپس در آن یک شیء را از مدل ایجاد و مقدار دهی و آن‌را به View ارسال می‌کنیم تا در زمان بارگذاری View از این شیء استفاده نماییم. نیاز نیست نام View را مشخص کنید. به صورت پیشفرض نام View با نام اکشن متد یکسان می‌باشد.

 
ایجاد View
 برای ایجاد یک View پیشفرض برای Action Method فوق در پوشه Views/Home یک MVC View Page (Razor View Page) را به نام Index.schtml ایجاد می‌کنیم.
- نکته1: پوشه View و داخل آن Home را ایجاد کنید.
- نکته2: معادل MVC View Page در نسخه جدید، Razor View می‌باشد. اگر در لیست این آیتم را انتخاب کنید، در توضیحات پنل سمت راست میتوانید این مطلب را مشاهده کنید.
- نکته3: دقت نمایید برای اینکه پروژه net Core2. باشد و تمام مشخصات موردنظر را داشته باشد، باید نگارش ویژوال استودیو VS 2017.15.6.6 و یا بیشتر باشد.
 
@model Razor.Models.Product
@{
    Layout = null;
}

<!DOCTYPE html>
<html>
<head>
    <meta name="viewport" content="width=device-width"/>
    <title>Index</title>
</head>
<body>
    Content will go here
</body>
</html>

تا اینجا ما یک پروژه ساده را ایجاد نموده‌ایم که قابلیت استفاده‌ی از Razor را هم دارد. در ادامه نحوه‌ی استفاده از امکانات Razor شرح داده میشوند.


استفاده از Model در یک View
برای استفاده از شیء مدل در View، باید در View به آن شیء و مشخصات آن دسترسی داشته باشیم که این دسترسی را Razor با استفاده از کاراکتر @ برای ما ایجاد می‌کند. برای اتصال به Model از عبارت model@ (حتما باید حروف کوچک باشد) استفاده می‌کنیم و برای دسترسی به مشخصات مدل از عبارت Model@ (حتما باید حرف اول آن بزرگ باشد) استفاده می‌کنیم. به کد زیر دقت کنید:

@model Razor.Models.Product
@{
    Layout = null;
}

<!DOCTYPE html>
<html>
<head>
    <meta name="viewport" content="width=device-width"/>
    <title>Index</title>
</head>
<body>
  @Model.Name
</body>
</html>
خط اولی که در View تعریف شده است، با استفاده از عبارت model@ مانند تعریف نوع مدل می‌باشد و کار اتصال مدل به View را انجام میدهد و همین خط باعث میشود زمانی که شما در تگ body عبارت Model@ وبعد دات (.) را میزنید، لیست خصوصیات آن مدل ظاهر میشوند. لیست شدن خصوصیات بعد از دات(.) یکی از کارهای پیشفرض ویژوال استودیو می‌باشد؛ برای اینکه از خطاهای احتمالی کاربر جلوگیری کند.

نتیجه خروجی بالا مانند زیر می‌باشد:

 



معرفی View Imports

زمانیکه بخواهیم به یک کلاس در View دسترسی داشته باشیم، باید فضای نام آن کلاس را مانند کد زیر در بالای View اضافه کنیم. حالا اگر بخواهیم به چند کلاس دسترسی داشته باشیم، باید این کار را به ازای هر کلاس در هر View انجام دهیم که سبب ایجاد کدهای اضافی در View‌ها میشود. برای بهبود این وضعیت می‌توانید یک کلاس View Import را در پوشه‌ی Views ایجاد کنید و تمام فضاهای نام را در آن قرار دهید. با اینکار تمام فضاهای نامی که در این کلاس View Import قرار گرفته‌اند، در تمام Viewهای موجود در پوشه Views قابل دسترسی خواهند بود.

در پوشه View راست کلیک کرده و گزینه Add و بعد New Item را انتخاب می‌کنیم و در کادر باز شده، آیتم MVC View Import Page (در نسخه جدید نام آن  Razor View Imports است) انتخاب می‌کنیم. ویژوال استودیو به صورت پیش فرض نام ViewImports.cshtml_ را برای آن قرار میدهد.


نکته: استاندارد نام گذاری این View این می‌باشد که ابتدای آن کاراکتر (_) حتما وجود داشته باشد.
 
در کلاس تعریف شده با استفاده از عبارت using@ فضای نام‌های خود را قرار میدهیم؛ مانند زیر:
 @using Razor.Models
در این کلاس شما فقط میتوانید فضاهای نام را مانند بالا قرار دهید. پس از آم قسمت فضاهای نام اضافی در Viewها قابل حذف میشوند و در این حالت فقط نام کلاس مدل را در بالای فرم قرار میدهیم مانند زیر:
@model Product
@{
    Layout = null;
}

<!DOCTYPE html>
<html>
<head>
    <meta name="viewport" content="width=device-width"/>
    <title>Index</title>
</head>
<body>
  @Model.Name
</body>
</html>


Layout ها

یکی دیگر از عبارت‌های مهم Razor که در فایل Index وجود دارد، عبارت زیر است:
@{
    Layout = null;
}
شما می‌توانید در بین {} کدهای سی شارپ را قرار دهید. حالا مقدار Layout را مساوی نال قرار داده‌ایم که بگوییم View مستقلی است و از قالب مشخصی استفاده نمی‌کند.

از Layout برای طراحی الگوی Viewها استفاده می‌کنیم. اگر بخواهیم برای View ها یک قالب طراحی کنیم و این الگو بین تمام یا چندتای از آن‌ها مشترک باشد، کدهای مربوط به الگو را با استفاده از Layout ایجاد می‌کنیم و از آن در View ها استفاده می‌کنیم. اینکار برای جلوگیری از درج کدهای تکراری قالب در برنامه انجام میشود. با اینکار اگر بخواهیم در الگو تغییری را انجام دهیم، این تغییر را در یک قسمت انجام میدهم و سپس به تمام Viewها اعمال میشود.
 
Layout
طرحبندی  Viewهای برنامه بطور معمول بین چند View مشترک است و طبق استاندارد ویژوال استودیو در پوشه‌ی Views/Shared قرار میگیرد. برای ایجاد Layout، روی پوشه Views/shared راست کلیک کرده و بعد گزینه Add وبعد NewItem و سپس گزینه MVC View Layout Page (نام آن در نسخه جدید Razor Layout است) را انتخاب می‌کنیم و ابتدای نام آن را به صورت پیشفرض کاراکتر (_) قرار میدهیم.
 


هنگام ایجاد این فایل توسط ویژوال استودیو، کدهای زیر به صورت پیش فرض در فایل ایجاد شده وجود دارند: 
<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>@ViewBag.Title</title>
</head>
<body>
    <div>
        @RenderBody()
    </div>
</body>
</html>
طرحبندی‌ها فرم خاصی از View هستند و دو عبارت @ در کدهای آن وجود دارد. در اینجا فراخوانی RenderBody@ سبب درج محتویات View مشخص شده توسط Action Method در این مکان می‌شود. عبارت دیگری که در اینجا وجود دارد، ViewBag است که برای مشخص کردن عنوان در اینجا استفاده شده‌است.
ViewBag ویژگی مفیدی است که اجازه می‌دهد تا مقادیر و داده‌ها در برنامه گردش داشته باشند و در این مورد بین یک View و Layout منتقل شوند. در ادامه خواهید دید وقتی Layout را به یک نمایه اعمال می‌کنیم، این مورد چگونه کار می‌کند.

عناصر HTML در یک Layout به هر View که از آن استفاده می‌کند، اعمال و توسط آن یک الگو برای تعریف محتوای معمولی ارائه می‌شود؛ مانند کدهای زیر. من برخی از نشانه گذاری‌های ساده را به Layout اضافه کردم تا اثر قالب آن آشکارتر شود:
<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>@ViewBag.Title</title>
    <style>
        #mainDiv {
            padding: 20px;
            border: solid medium black;
            font-size: 20pt
        }
    </style>
</head>
<body>
    <h1>Product Information</h1>
    <div id="mainDiv">
        @RenderBody()
    </div>
</body>
</html>
در اینجا یک عنصر عنوان و همچنین بعضی از CSS‌ها را به عنصر div که حاوی عبارت RenderBody@ است، اضافه کرده‌ام؛ فقط برای اینکه مشخص شود، چه محتوایی از طرحبندی سایت می‌آید و چه چیزی از View.
 

اعمال Layout

برای اعمال کردن Layout به یک View، نیاز است مشخصه Layout آن‌را مقدار دهی و سپس Htmlهای اضافی موجود در آن‌را مانند المنت‌های head و Body حذف کنید؛ همانند کدهای زیر:
@model Product
@{
    Layout = "_BasicLayout";
    ViewBag.Title = "Product";
}
در خاصیت Layout، مقدار را برابر نام فایل Layout، بدون پسوند cshtml آن قرار میدهیم. Razor در مسیر پوشه Views/shared و پوشه Views/Home فایل Layout را جستجو می‌کند.
در اینجا عبارت ViewBag.Title را نیز مقدار دهی می‌کنیم. زمانیکه فایل فراخوانی میشود، عنوان آن صفحه با این مقدار، جایگزین خواهد شد.
تغییرات این View بسیار چشمگیر است؛ حتی برای چنین برنامه ساده‌ای. طرحبندی شامل تمام ساختار مورد نیاز برای هر پاسخ HTML است که View را به صورت یک محتوای پویا ارائه می‌دهد و داده‌ها را به کاربر منتقل می‌کند. هنگامیکه MVC فایل Index.cshtmal را پردازش می‌کند، این طرحبندی برای ایجاد پاسخ HTML نهایی یکپارچه می‌شود؛ مانند عکس زیر:
 


 
View Start

بعضی موارد هنوز در برنامه وجود دارند که می‌توان کنترل بیشتری بر روی آن‌ها داشته باشید. مثلا اگر بخواهیم نام یک فایل layout را تغییر دهیم، مجبور هستیم تمام Viewهایی را که از آن Layout استفاده می‌کنند، پیدا کنید و نام Layout استفاده شده در آن‌ها را تغییر دهیم. اینکار احتمال خطای بالایی دارد و امکان دارد بعضی View ها از قلم بیفتند و برنامه دچار خطا شود. بنابراین با استفاده از View Start می‌توانیم این مشکل را برطرف کنیم. وقتی نام Layout تغییر کرد، تنها کافی است نام آن‌را در View Start تغییر دهیم. اکنون زمانیکه برنامه را اجرا می‌کنیم، MVC به دنبال فایل View Start می‌گردد و اگر اطلاعاتی داشته باشد، آن را اجرا می‌کند و الویت این فایل از تمام فایل‌های دیگر بیشتر است و ابتدا تمام آنها اجرا میشوند.

برای ایجاد یک فایل شروع مشاهده، روی پوشه‌ی Views کلیک راست کرده و گزینه add->New Items را انتخاب می‌کنیم و از پنجره باز شده گزینه ( Razor View Start ) Mvc View Start Page را انتخاب می‌کنیم؛ مانند تصویر زیر:


ویژوال استودیو به صورت پیش فرض نام ViewStart.cshtml_ را به عنوان نام آن قرار میدهد؛ شما گزینه‌ی Create را در این حالت انتخاب کنید. محتویات فایل ایجاد شده به صورت زیر می‌باشد:
@{
    Layout = "_Layout";
}
برای اعمال Layout جدید به تمام Viewها، مقدار Layout را معادل طرحبندی خود تغییر میدهیم؛ مانند کد زیر: 
@{
    Layout = "_BasicLayout";
}
از آنجا که فایل View Start دارای مقداری برای Layout می‌باشد، می‌توانیم عبارت‌های مربوطه را در Index.cshtml‌ها حذف کنیم:
@model Product
@{
    ViewBag.Title = "Product";
}
در اینجا لازم نیست مشخص کنیم که من می‌خواهم از فایل View Start استفاده کنم. MVC این فایل را پیدا خواهد کرد و از محتویات آن به طور خودکار استفاده می‌کند. البته باید دقت داشت که مقادیر تعریف شده‌ی در فایل View اولویت دارند و باعث میشوند با معادل‌های فایل View Start جایگزین شوند.

شما همچنین می‌توانید چندین فایل View Start را برای تنظیم مقادیر پیش فرض قسمت‌های مختلف برنامه، استفاده کنید. یک فایل Razor همواره توسط نزدیک‌ترین فایل View start، پردازش می‌شود. به این معنا که شما می‌توانید تنظیمات پیش فرض را با افزودن یک فایل View Start به پوشه Views / Home و یا Views / Shared لغو کنید.

نکته: درک تفاوت میان حذف محتویات فایل View Start یا مساوی Null قرار دادن آن مهم است. اگر View شما مستقل است و شما نمی‌خواهید از آن استفاده کنید، بنابراین مقدار Layout آن‌را صریحا برابر Null قرار دهید. اگر مقدار دهی صریح شما مشخصه Layout را نادیده بگیرید، Mvc فرض می‌کند که میخواهید layout را داشته باشید و مقدار آن را از فایل View Start تامین می‌کند.
 

استفاده از عبارت‌های شرطی در Razor
 
حالا که من اصول و مبانی View و Layout را به شما نشان دادم، قصد دارم به انواع مختلفی از اصطلاحات که Razor آن‌ها را پشتیبانی می‌کند و نحوه استفاده‌ی از آنها را برای ایجاد محتوای نمایشی، ارائه دهم. در یک برنامه MVC، بین نقش‌هایی که توسط View و Action متدها انجام می‌شود، جدایی روشنی وجود دارد. در اینجا قوانین ساده‌ای وجود دارند که در جدول زیر مشخص شده‌اند:

کامپوننت 
انجام میشود 
انجام نمیشود 
  Action Method    یک شیء ViewModel را به View ارسال می‌کند.
  یک فرمت داده را به View ارسال می‌کند.
  View    از شیء ViewModel برای ارائه محتوا به کاربر استفاده می‌کند.
  هر جنبه‌ای از شیء View Model مشخصات را تغییر می‌دهد.
 
برای به دست آوردن بهترین نتیجه از MVC، نیاز به تفکیک و جداسازی بین قسمت‌های مختلف برنامه را دارید. همانطور که می‌بینید، می‌توانید کاملا با Razor کار کنید و این نوع فایل‌ها شامل دستورالعمل‌های سی شارپ نیز هستند. اما شما نباید از Razor برای انجام منطق کسب و کار استفاده کنید و یا هر گونه اشیاء Domain Model خود را دستکاری کنید. کد زیر نشان میدهد که یک عبارت جدید به View اضافه میشود:
*@
@model Product
@{

    ViewBag.Title = "Product";
}
<p>Product Name: @Model.Name</p> <p>Product Price: @($"{Model.Price:C2}")</p>
می‌توان برای خصوصیت price، در اکشن متد فرمتی را تعریف و بعد آن را به View ارسال کنیم. این روش کار می‌کند، اما استفاده از این رویکرد منافع الگوی MVC را تضعیف می‌کند و توانایی من برای پاسخ دادن به تغییرات در آینده را کاهش می‌دهد. باید به یاد داشته باشید که در ASP NET Core MVC، استفاده مناسب از الگوی MVC اجتناب ناپذیر است و شما باید از تاثیر تصمیمات طراحی و کدگذاری که انجام می‌دهید مطلع باشید.
 

پردازش داده‌ها در مقابل فرمت

تفاوت بین پردازش داده و قالب بندی داده مهم است.
- نمایش فرمت داده‌ها: به همین دلیل در آموزش قبل من یک نمونه از شیء کلاس Product را برای View ارسال کرده‌ام و نه فرمت خاص یک شیء را به صورت یک رشته نمایشی.
- پردازش داده: انتخاب اشیاء داده‌‌ای برای نمایش، مسئولیت کنترلر است و در این حالت مدلی را برای دریافت و تغییر داده مورد نیاز، فراخوانی می‌کند.
گاهی سخت است که متوجه شویم کدی جهت پردازش داده است و یا فرمت آن.


اضافه نمودن مقدار داده ای

ساده‌ترین کاری را که می‌توانید با یک عبارت Razor انجام دهید این است که یک مقدار داده را در نمایش دهید. رایج‌ترین کار برای انجام آن، استفاده از عبارت Model@ است. ویوو Index یک مثال از این مورد است؛ شبیه به این مورد:
 <p>Product Name: @Model.Name</p>
شما همچنین می‌توانید یک مقدار را با استفاده قابلیت ViewBag نیز به View ارسال نمایید که از این قابلیت در Layout برای تنظیم کردن محتوای عنوان استفاده کردیم. اما در حالت زیر یک مدل نوع دار را به سمت View ارسال کرده‌ایم:
using Microsoft.AspNetCore.Mvc;
using Razor.Models;


namespace Razor.Controllers
{
    public class HomeController : Controller
    {
        // GET: /<controller>/
        public ViewResult Index()
        {
            Product myProduct = new Product
            {
                ProductID = 1,
                Name = "Kayak",
                Description = "A boat for one person",
                Category = "Watersports",
                Price = 275M
            };
            return View(myProduct);
        }
    }
}

خصوصیت ViewBag یک شیء پویا را باز می‌گرداند که می‌تواند برای تعیین خواص دلخواهی مورد استفاده قرار گیرد. از آنجا که ویژگی ViewBag پویا است، لازم نیست که نام خصوصیات را پیش از آن اعلام کنم. اما این بدان معنا است که ویژوال استودیو قادر به ارائه پیشنهادهای تکمیل کننده برای ViewBag نیست.
در مثال زیر از یک مدل نوع دار و مزایای به همراه آن استفاده شده‌است: 
 <p>Product Name: @Model.Name</p> <p>Product Price: @($"{Model.Price:C2}")</p> <p>Stock Level: @ViewBag.StockLevel</p>
نتیجه آن‌را در زیر می‌توانید مشاهده کنید:



تنظیم مقادیر مشخص

شما همچنین می‌توانید از عبارات Razor برای تعیین مقدار عناصر، استفاده کنید:
@model Product
@{

    ViewBag.Title = "Product";
}
p>Product Name: @Model.Name</p> <p>Product Price: @($"{Model.Price:C2}")</p> 
<p>Stock Level: @ViewBag.StockLevel</p>
<div data-productid="@Model.ProductID" data-stocklevel="@ViewBag.StockLevel">    
<p>Product Name: @Model.Name</p>    
<p>Product Price: @($"{Model.Price:C2}")</p>   
 <p>Stock Level: @ViewBag.StockLevel</p> 
</div>
در اینجا از عبارات Razor، برای تعیین مقدار برای برخی از ویژگی‌های داده در عنصر div استفاده کرده‌ام.

نکته: ویژگی‌های داده‌ها که نام آنها *-data است، روشی برای ایجاد ویژگی‌های سفارشی برای سال‌ها بوده است و بعنوان بخشی از استاندارد HTML5 است. عموما کدهای جاوا اسکریپت از آن‌ها برای یافتن اطلاعات استفاده می‌کنند.

اگر برنامه را اجرا کنید و به منبع HTML که به مرورگر فرستاده شده نگاهی بیندازید، خواهید دید که Razor مقادیر صفات را تعیین کرده است؛ مانند این:
<div data-productid="1" data-stocklevel="2">    <p>Product Name: Kayak</p>    <p>Product Price: £275.00</p>    <p>Stock Level: 2</p> </div>


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

Razor قادر به پردازش عبارات شرطی است. در ادامه کدهای Index View را که در آن دستورات شرطی اضافه شده‌اند می‌بینید:

@model Product
@{ ViewBag.Title = "Product Name"; }
<div data-productid="@Model.ProductID" data-stocklevel="@ViewBag.StockLevel">  
  <p>Product Name: @Model.Name</p>   
 <p>Product Price: @($"{Model.Price:C2}")</p> 
   <p>Stock Level:       
 @switch (ViewBag.StockLevel)
{
    case 0:@:Out of Stock                break;           
    case 1:          
    case 2:        
    case 3:            
    <b>Low Stock (@ViewBag.StockLevel)</b>         
       break;      
    default:            
    @: @ViewBag.StockLevel in Stock          
      break;      
  }    
</p>
</div>


برای شروع یک عبارت شرطی، یک علامت @ را در مقابل کلمه کلیدی if یا swicth سی شارپ قرار دهید. سپس بخش کد را داخل } قرار می‌دهیم. درون قطعه کد Razor، می‌توانید عناصر HTML و مقادیر داده را در خروجی نمایش دهید؛ مانند:
 <b>Low Stock (@ViewBag.StockLevel)</b>
در اینجا لازم نیست عناصر یا عبارات را در نقل قول قرار دهیم و یا آنها را به روش خاصی تعریف کنیم. موتور Razor این را به عنوان خروجی برای پردازش تفسیر خواهد کرد.
با این حال، اگر می‌خواهید متن واقعی را در نظر بگیرید و دستورات Razor را لغو کنید،‌می‌توانید از :@ استفاده کنید تا عین آن عبارت درج شود.
مطالب
MTOM در WCF
در  WCF سه نوع Message Encoder وجود دارد:
  • Text(Xml) Message Encoder(به صورت پیش فرض در تمام Http-Base Binding‌ها از این Encoder استفاده می‌شود)
  • Binary Message Encoder(به صورت پیش فرض در تمام Net* Binding‌ها از این encoder استفاده می‌شود که برای سرویس‌های وب مناسب نیست)
  • MTOM Message Encoder (در حالت استفاده از Http-Base Binding‌ها و انتقال اطلاعات به صورت باینری از این گزینه استفاده می‌شود که به صورت پیش فرض غیر فعال است)
Encoding یا رمزگذاری در WCF به این معنی است که داده‌های مورد نظر برای انتقال، به یکی از فرمت‌های MTOM ، Text-Xml یا  Binary سریالایز شوند.
 وضعیتی را در نظر بگیرید که در یک پروژه مبتنی بر WCF قصد دارید حجمی زیاد از داده به فرمت باینری  (نظیر فایل ها) را بین سرور و کلاینت رد و بدل کنید. به صورت معمول بسیاری از برنامه نویسان، یک کلاس به همراه DataContractAttribute ایجاد می‌کنند که در آن خاصیتی به صورت آرایه ای از نوع بایت که DataMemberAttribute را نیز دارد برای انتقال محتویات فایل استفاده می‌شود. اما باید یک نکته را مد نظر داشت و آن این است که به صورت پیش فرض فرمت انتقال داده‌ها در WCF به صورت Text/Xml است  و برای انتقال داده‌ها نیز از فرمت Base 64 استفاده خواهد شد. مشکل اصلی این جاست که در حالت Text/Xml Encoding برای انتقال داده‌های باینری، برای هر سه بایت، چهار کاراکتر استفاده می‌شود در نتیجه، این باعث افزایش حجم داده تا 33 درصد خواهد شد که کارایی سیستم را تحت تاثیر قرار می‌دهد.
اما خبر خوب این است که استانداردی وجود دارد به نام MTOM یا همان Message Transmission Optimization Mechanism، برای این که بتوان محتوای باینری را بدون افزایش حجم داده انتقال داد. برای پیاده سازی این روش باید موارد زیر را در نظر داشته باشید:

»متد یا همان OperationContract که وظیفه آن ارسال یا دریافت داده‌ها با فرمت MTOM است فقط کلاس هایی را انتقال دهد که دارای MessageContractAttribute هستند. نباید از DataContractAttribute استفاده نمایید.
»خاصیتی که نوع آن آرایه ای از بایت‌ها است نباید دارای DataMemberAttribute باشد؛ بلکه به جای آن باید از MessageBodyMember استفاده نمایید.
»به جای []Byte می‌توان از نوع Stream نیز استفاده کرد(الزامی نیست).
»مقدار خاصیت MessageEncoding در Binding استفاده شده باید MTOM تعیین شود.
پیاده سازی یک مثال
ابتدا کلاس مورد نظر را به صورت زیر تهیه می‌کنیم:
[MessageContract]
public class MyFile
{
    [MessageHeader] 
    public String Filename { get; set; }

    [MessageBodyMember]    
    public Byte[] Contents { get; set; } 
}
چند تذکر
  • به جای DataContract از MessageContract استفاده می‌شود؛
  • تمام خاصیت هایی که نوع آن‌ها غیر از []Byte است باید دارای MessageHeader باشند؛
  • خاصیتی که برای انتقال محتوای باینری تهیه شده است، باید از MessageBodyMember استفاده نماید؛
  • مجاز به تعریف فیلد یا فیلد هایی که نوع آن‌ها Primitive Type است نمی‌باشید.

تنظیمات مربوط به Binding نیز به صورت خواهد بود:

<bindings> 
    <wsHttpBinding>
        <binding name="WsHttpMtomBinding" messageEncoding="Mtom" /> 
    </wsHttpBinding>
 </bindings>
اما یک نکته...
هدف از استفاده از MTOM برای افزایش کارایی انتقال داده‌های باینری در حجم زیاد است. در زیر نتایج مقایسه بررسی انتقال اطلاعات به دو صورت MTOM و Text برای حجم داده‌های متفاوت را مشاهده می‌کنید:


با دقت در نتایج بالا مشخص می‌شود که این روش در حجم داده‌های پایین (مثل 100 بایت یا 1000 بایت) عملکرد مورد انتظار را نخواهد داشت. پس این نکته را نیز در هنگام پیاده سازی به این روش مد نظر داشته باشید.

 
نظرات مطالب
اعمال تزریق وابستگی‌ها به مثال رسمی ASP.NET Identity
- هدف از بحث جاری صرفا «اعمال تزریق وابستگی‌ها» هست.
- سری مباحث ASP.NET Identity 2,x را از اینجا شروع به مطالعه کنید. برای نمونه: «افزودن تصدیق ایمیل به ASP.NET Identity در MVC 5» و «Identity 2.0 : تایید حساب‌های کاربری و احراز هویت دو مرحله ای » و «پیاده سازی یک سیستم دسترسی Role Based در Web API و AngularJs - بخش اول»


جهت اطلاع عموم
ASP.NET Identity 2.x یک پروژه‌ی خاتمه یافته از دیدگاه مایکروسافت محسوب شده و دیگر پشتیبانی خاصی ندارد . نگارش بعدی آن ASP.NET Core Identity نام دارد.
نظرات مطالب
شروع به کار با AngularJS 2.0 و TypeScript - قسمت دوازدهم - توزیع برنامه
- در مورد تنظیمات سمت سرور AngularJS 2.0 در ASP.NET Core 1.0، در قسمت «معرفی بسته‌ی نیوگت Microsoft.AspNetCore.SpaServices» بحث شده‌است.
- اما ... Gulp جاوا اسکریپتی اساسا وابستگی خاصی به فناوری‌های سمت سرور ندارد. در اینجا فقط نحوه‌ی مسیردهی این پوشه‌ها مهم هستند (و Task runner آن فقط به این مسایل دقت می‌کند):
var appFolder = "./app";
var outFolder = "wwwroot";
در اینجا app/. به این معنا است که محل واقع شدن فایل تنظیمات gulp و همچنین فایل systemjs.config.js، یک سطح بالاتر هستند از پوشه‌ی app (جایی که قرار است فایل‌های اصلی از آن دریافت شوند) و همچنین پوشه‌ی wwwroot ذکر شده (جایی که قرار است فایل‌های نهایی کپی شوند):
gulpfile.js
systemjs.config.js
--app
--wwwroot
مطالب
React 16x - قسمت 16 - مسیریابی - بخش 2 - پارامترهای مسیریابی
در قسمت قبل، نحوه‌ی برپایی و تنظیمات اولیه‌ی کتابخانه‌ی مسیریابی react-router-dom را بررسی کردیم. در ادامه نحوه‌ی دریافت پارامترهای مسیریابی و سایر جزئیات این کتابخانه را مرور می‌کنیم.


دریافت پارامترهای مسیریابی

گاهی از اوقات نیاز به ارسال پارامترهایی به مسیریابی‌های تعریف شده‌است. برای مثال در لیست محصولات تعریف شده، بسته به انتخاب هر کدام، باید id متناظری نیز در URL نهایی ظاهر شود. به این Id، یک Route parameter گفته می‌شود. برای پیاده سازی این نیازمندی به صورت زیر عمل می‌کنیم:
در فایل app.js، یک مسیریابی جدید را برای نمایش جزئیات یک محصول اضافه می‌کنیم:
import ProductDetails from "./components/productDetails";
// ...
class App extends Component {
  render() {
    return (
      <div>
        <NavBar />
        <div className="container">
          <Switch>
            <Route path="/products/:id" component={ProductDetails} />
            <Route
              path="/products"
              render={props => (
                <Products param1="123" param2="456" {...props} />
              )}
            />
            <Route path="/posts/:year/:month" component={Posts} />
            <Route path="/admin" component={Dashboard} />
            <Route path="/" component={Home} />
          </Switch>
        </div>
      </div>
    );
  }
}
در اینجا برای تعریف یک پارامتر مسیریابی، آن‌را با : شروع می‌کنیم؛ مانند id:، در مسیریابی جدید فوق. البته امکان تعریف چندین پارامتر هم در اینجا وجود دارد؛ مانند تعریف پارامترهای سال و ماه برای مسیریابی مطالب. به علاوه چون این نوع مسیریابی‌ها ویژه‌تر هستند، باید در ابتدا قرارگیرند. برای مثال اگر مسیریابی products/ را در اول لیست قرار دهیم، دیگر کار به انتخاب products/:id/ نخواهد رسید.

کامپوننت جدید src\components\productDetails.jsx نیز به صورت زیر تعریف شده‌است:
import React, { Component } from "react";

class ProductDetails extends Component {
  handleSave = () => {
    // Navigate to /products
  };

  render() {
    return (
      <div>
        <h1>Product Details - </h1>
        <button className="btn btn-primary" onClick={this.handleSave}>
      </div>
    );
  }
}

export default ProductDetails;
پس از این تغییرات و ذخیره سازی برنامه، با بارگذاری مجدد برنامه در مرورگر، ابتدا صفحه‌ی products را از منوی راهبری سایت انتخاب کرده و سپس بر روی یکی از محصولات لیست شده کلیک می‌کنیم. سپس در افزونه‌ی react developer tools، کامپوننت نمایش داده شده‌ی ProductDetails را انتخاب می‌کنیم:


در اینجا با گشودن اطلاعات خاصیت match تزریق شده‌ی به کامپوننت ProductDetails، می‌توان اطلاعاتی مانند پارامترهای دریافتی مسیریابی را دقیقا مشاهده کرد. برای مثال در این تصویر id=1 از URL بالای صفحه که به http://localhost:3000/products/1 تنظیم شده‌است، دریافت می‌شود.
بنابراین امکان خواندن اطلاعات پارامترهای مسیریابی، توسط شیء match تزریق شده‌ی به یک کامپوننت وجود دارد. به همین جهت کامپوننت ProductDetails را ویرایش کرده و المان h1 آن‌را جهت نمایش id محصول به صورت زیر تغییر می‌دهیم که در آن شیء match.params، از props کامپوننت تامین می‌شود:
<h1>Product Details - {this.props.match.params.id} </h1>

برای آزمایش آن مجددا از صفحه‌ی products شروع کرده و بر روی لینک یکی از محصولات، کلیک کنید. در اینجا هرچند id محصول به درستی نمایش داده می‌شود، اما ... نمایش جزئیات آن به همراه بارگذاری کامل و مجدد صفحه‌ی آن است که از حالت SPA خارج شده‌است. برای رفع این مشکل به کامپوننت products مراجعه کرده و anchor‌های تعریف شده را همانطور که در قسمت قبل نیز بررسی کردیم، تبدیل به کامپوننت Link می‌کنیم.
از حالت قبلی:
{this.state.products.map(product => (
  <li key={product.id}>
    <a href={`/products/${product.id}`}>{product.name}</a>
  </li>
))}
به حالت جدید:
import { Link } from "react-router-dom";
// ...

<Link to={`/products/${product.id}`}>{product.name}</Link>
با این تغییر دیگر در حین نمایش یک کامپوننت، بارگذاری کامل صفحه رخ نمی‌دهد.


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

به تعریف مسیریابی زیر، دو پارامتر سال و ماه، اضافه شده‌اند:
<Route path="/posts/:year/:month" component={Posts} />
و برای مثال اگر بر روی لینک posts در منوی راهبری کلیک کنیم، آدرسی مانند http://localhost:3000/posts/2018/06 ایجاد شده و سپس کامپوننت Posts رندر می‌شود. حال اگر پارامتر ماه را حذف کنیم http://localhost:3000/posts/2018 چه اتفاقی رخ می‌دهد؟ در این حالت برنامه کامپوننت Home را نمایش خواهد داد. علت اینجا است که پارامترهای تعریف شده‌ی در مسیریابی، به صورت پیش‌فرض اجباری هستند. به همین جهت URL وارد شده، چون با الگوی تعریفی Route فوق بدلیل نداشتن قسمت ماه، انطباق نیافته و تنها مسیریابی / که کامپوننت Home را نمایش می‌دهد، با آن تطابق یافته‌است.
برای رفع این مشکل می‌توان با اضافه کردن یک ? به هر پارامتر، پارامترهای تعریف شده را اختیاری کرد:
<Route path="/posts/:year?/:month?" component={Posts} />
در regexهای جاوا اسکریپتی زمانیکه یک ? را به یک عبارت باقاعده اضافه می‌کنیم، یعنی آن عبارت اختیاری است.

با این تغییرات اگر مجددا آدرس http://localhost:3000/posts/2018 را درخواست کنیم، کامپوننت Posts بجای کامپوننت Home نمایش داده می‌شود.

اکنون کامپوننت Posts را به صورت زیر تغییر می‌دهیم تا پارامترهای مسیریابی را نیز درج کند:
import React from "react";

const Posts = ({ match }) => {
  return (
    <div>
      <h1>Posts</h1>
      Year: {match.params.year} , Month: {match.params.month}
    </div>
  );
};

export default Posts;
پارامتر ({ match }) در اینجا به این معنا است که شیء props ارسالی به آن، توسط Object Destructuring تجزیه شده و خاصیت match آن در اینجا به صورت یک پارامتر در اختیار کامپوننت بدون حالت تابعی قرار گرفته‌است.

پس از ذخیره سازی این تغییرات و بارگذاری مجدد برنامه در مرورگر، اگر آدرس http://localhost:3000/posts/2018/1 را وارد کنیم، خروجی زیر حاصل می‌شود:



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

پارامترهای اختیاری، جزو قابلیت‌هایی هستند که باید تا حد ممکن از بکارگیری آن‌ها اجتناب و آن‌ها را با کوئری استرینگ‌ها تعریف کرد. کوئری استرینگ‌ها با یک ? در انتهای URL شروع می‌شوند و می‌توانند چندین پارامتر را داشته باشند؛ مانند: http://localhost:3000/posts?sortBy=newest&approved=true و یا حتی می‌توان آن‌ها را با پارامترهای اختیاری نیز ترکیب کرد مانند: http://localhost:3000/posts/2018/05?sortBy=newest&approved=true
برای استخراج کوئری استرینگ‌ها در برنامه‌های React باید از شیء location استفاده کرد:


در اینجا مقدار خاصیت search، کل قسمت کوئری استرینگ‌ها را به همراه دارد. البته ما قصد پردازش آن‌را به صورت دستی نداریم. به همین جهت از کتابخانه‌ی زیر برای انجام اینکار استفاده خواهیم کرد:
> npm i query-string --save
پس از نصب کتابخانه‌ی بسیار معروف query-string، به کامپوننت Posts مراجعه کرده و تغییرات زیر را اعمال می‌کنیم:
import queryString from "query-string";

const Posts = ({ match, location }) => {
  const result = queryString.parse(location.search);
  console.log(result);
  // ...
- پیشتر ذکر پارامتر ({ match }) را بررسی کردیم. در اینجا خاصیت location نیز به آن اضافه شده‌است تا پس از Object Destructuring شیء props ارسالی به کامپوننت، بتوان به مقدار شیء location نیز دسترسی یافت.
- سپس شیء queryString را از ماژول مرتبط، import می‌کنیم. در ادامه به کمک متد parse آن، می‌توان location.search را آنالیز کرد که خروجی آن، یک شیء جاوا اسکریپتی به صورت زیر است:
{approved: "true", sortBy: "newest"}
بنابراین در اینجا هم می‌توان توسط Object Destructuring، به این خواص دسترسی یافت:
 const { approved, sortBy } = queryString.parse(location.search);

یک نکته: باید دقت داشت که کتابخانه‌ی query-string، همیشه مقادیر خواص را رشته‌ای بازگشت می‌دهد؛ حتی اگر عدد باشند.


مدیریت مسیرهای نامعتبر درخواستی

فرض کنید کاربری آدرس غیرمعتبر http://localhost:3000/xyz را که هیچ نوع مسیریابی را برای آن تعریف نکرده‌ایم، درخواست می‌کند. در این حالت برنامه کامپوننت home را رندر می‌کند که مرتبط است با تعاریف مسیریابی برنامه در فایل app.js. تنها path تعریف شده‌ای که با این آدرس تطابق پیدا می‌کند، / متناظر با کامپوننت home است.
بجای این رفتار پیش‌فرض، مایل هستیم که کاربر به یک صفحه‌ی سفارشی «پیدا نشد» هدایت شود. به همین جهت ابتدا کامپوننت جدید تابعی بدون حالت src\components\notFound.jsx را با محتوای زیر ایجاد می‌کنیم:
import React from "react";

const NotFound = () => {
  return <h1>Not Found</h1>;
};

export default NotFound;
سپس ابتدا به مسیریابی /، ویژگی exact را هم اضافه می‌کنیم تا دیگر بجز ریشه‌ی سایت، به مسیر دیگری پاسخ ندهد:
<Route path="/" exact component={Home} />
اکنون اگر مجددا مسیر xyz را درخواست کنیم، فقط کامپوننت NavBar در صفحه ظاهر می‌شود. برای بهبود این وضعیت و نمایش کامپوننت NotFound، مراحل زیر را طی می‌کنیم:
- ابتدا شیء Redirect را از react-router-dom باید import کنیم.
- همچنین import کامپوننت NotFound نیز باید ذکر شود.
- سپس پیش از مسیریابی کلی /، مسیریابی جدید not-found را که به کامپوننت NotFound اشاره می‌کند، اضافه می‌کنیم.
- اکنون در انتهای Switch تعریف شده (جائی که دیگر هیچ مسیریابی تعریف شده‌ای، با مسیر درخواستی کاربر، تطابق نداشته)، باید کامپوننت Redirect را جهت هدایت به این مسیریابی جدید، تعریف کرد:
import { Redirect, Route, Switch } from "react-router-dom";
//...
import NotFound from "./components/notFound";
//...

class App extends Component {
  render() {
    return (
      <div>
        <NavBar />
        <div className="container">
          <Switch>
            //...
            <Route path="/not-found" component={NotFound} />
            <Route path="/" exact component={Home} />
            <Redirect to="/not-found" />
          </Switch>
        </div>
      </div>
    );
  }
}
پس از این تغییرات، اگر آدرس نامعتبر http://localhost:3000/xyz درخواست شود، بلافاصله به آدرس http://localhost:3000/not-found هدایت می‌شویم.

کاربرد دیگر کامپوننت Redirect، هدایت کاربران از یک آدرس قدیمی، به یک آدرس جدید است که نحوه‌ی تعریف آن به صورت زیر می‌باشد:
<Redirect from="/messages" to="/posts" />
با این تنظیم اگر کاربری مسیر http://localhost:3000/messages را درخواست دهد، به صورت خودکار به http://localhost:3000/posts هدایت خواهد شد.


هدایت کاربران به آدرس‌های مختلف با برنامه نویسی

گاهی از اوقات پس از تکمیل فرمی و یا کلیک بر روی دکمه‌ای، می‌خواهیم کاربر را به آدرس خاصی هدایت کنیم. برای مثال در برنامه‌ی جاری می‌خواهیم زمانیکه کاربری صفحه‌ی جزئیات یک محصول را مشاهده و بر روی دکمه‌ی فرضی Save کلیک کرد، دوباره به همان صفحه‌ی لیست محصولات هدایت شود؛ برای این منظور از شیء history استفاده خواهیم کرد:


در اینجا متدها و خواص شیء history را مشاهده می‌کنید. برای نمونه توسط متد push آن می‌توان آدرس جدیدی را به تاریخچه‌ی آدرس‌های مرور شده‌ی توسط کاربر، اضافه کرد و کاربر را با برنامه نویسی، به صفحه‌ی جدیدی هدایت نمود:
class ProductDetails extends Component {
  handleSave = () => {
    // Navigate to /products
    this.props.history.push("/products");
  };

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


تعریف مسیریابی‌های تو در تو


قصد داریم به صفحه‌ی admin، دو لینک جدید به مطالب و کاربران ادمین را اضافه کنیم، به نحوی که با کلیک بر روی هر کدام، محتوای هر صفحه‌ی متناظر، در همینجا نمایش داده شود (تصویر فوق). به عبارتی در حال حاضر، مسیریابی تعریف شده، جهت مدیریت لینک‌های NavBar بالای صفحه کار می‌کند. اکنون می‌خواهیم مسیریابی دیگری را داخل آن برای پوشش منوی کنار صفحه‌ی ادمین اضافه کنیم. به اینکار، تعریف مسیریابی‌های تو در تو گفته می‌شود و پیاده سازی آن توسط کامپوننت react-router-dom بسیار ساده‌است و شامل این موارد می‌شود:
- ابتدا مسیریابی‌های جدید را همینجا داخل کامپوننت src\components\admin\dashboard.jsx تعریف می‌کنیم:
const Dashboard = ({ match }) => {
  return (
    <div>
      <h1>Admin Dashboard</h1>
      <div className="row">
        <div className="col-3">
          <SideBar />
        </div>
        <div className="col">
          <Route path="/admin/users" component={Users} />
          <Route path="/admin/posts" component={Posts} />
        </div>
      </div>
    </div>
  );
};
در اینجا محتوای کامپوننت بدون حالت تابعی Dashboard را ملاحظه می‌کنید که از یک کامپوننت منوی SideBar و سپس در ستونی دیگر، از 2 کامپوننت Route تشکیل شده‌است که بر اساس URL رسیده، سبب رندر کامپوننت‌های جدید Users و Posts خواهند شد.
تنها نکته‌ی جدید آن، امکان درج کامپوننت Route در قسمت‌های مختلف برنامه، خارج از app.js می‌باشد و این امکان محدود به app.js نیست. در این حالت اگر مسیر /admin/posts توسط کاربر وارد شد، دقیقا در همان محلی که کامپوننت Route درج شده‌است، کامپوننت متناظر با این مسیر یعنی کامپوننت Posts، رندر می‌شود.

در ادامه محتوای این کامپوننت‌های جدید را نیز ملاحظه می‌کنید:
محتوای کامپوننت src\components\admin\sidebar.jsx
import React from "react";
import { Link } from "react-router-dom";

const SideBar = () => {
  return (
    <ul className="list-group">
      <li className="list-group-item">
        <Link to="/admin/posts">Posts</Link>
      </li>
      <li className="list-group-item">
        <Link to="/admin/users">Users</Link>
      </li>
    </ul>
  );
};

export default SideBar;

محتوای کامپوننت src\components\admin\users.jsx
import React from "react";

const Users = () => {
  return <h1>Admin Users</h1>;
};

export default Users;

محتوای کامپوننت src\components\admin\posts.jsx
import React from "react";

const Posts = () => {
  return (
    <div>
      <h1>Admin Posts</h1>
    </div>
  );
};

export default Posts;


کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: sample-15-part-02.zip
اشتراک‌ها
دریافت کتاب مرجع ng-book: The Complete Book on AngularJS
کتاب ng-book: The Complete Book on AngularJS راه حل هایی که برای حرفه ای شدن در AngularJS نیاز است را در خود دارد. AngularJS یک فریمورک حرفه ای front-end است که توسط تیمی در گوگل انتشار یافته است و شما را قادر می‌سازد تا یک تجربه‌ی غنی front-end را ساده و به سرعت ایجاد کنید. 
طبق بررسی‌های شخصی بنده واقعا این کتاب کامل‌ترین مرجع شناخته شده برای AngularJS به حساب می‌آید و حجم 600 صفحه ای آن گواهی بر این مدعاست. تمام موارد این framework را پوشش داده است؛ حتی مباحثی از قبیل SEO Optimization و Animation و ...
طبق گفته‌ی نویسنده اگر می‌خواهید که در AngularJS حرفه ای شوید حتما این کتاب را مطالعه کنید!
دریافت کتاب مرجع ng-book: The Complete Book on AngularJS
نظرات مطالب
شروع به کار با Ember.js
- صرف نوشتن مطلبی در مورد موضوعی خاص، به معنای «بهترین بودن آن» و «برترین حالت ممکن» نیست. امروز راجع به ember.js مطلب نوشتم. شاید هفته‌‌های بعد در مورد meteor.js مطلب نوشتم.
- مقایسه‌ای در اینجا «AngularJS vs Ember»
- مقایسه‌ای کامل‌تر در اینجا «AngularJs vs EmberJs»
- «Rails JS frameworks: Ember.js vs. AngularJS»
- «AngularJS vs. Backbone.js vs. Ember.js»
- یک نمونه‌ی دیگر «The Top 10 Javascript MVC Frameworks Reviewed» 
- این نظرسنجی را هم دنبال کنید: «آیا به یادگیری یا ادامه‌ی استفاده از AngularJS خواهید پرداخت؟»