مطالب
نکات استفاده از افزونه‌ی Web Essentials جهت پردازش LESS
در این مطلب، نحوه‌ی استفاده از افزونه‌ی Web Essentials جهت پردازش فایل‌های LESS را بررسی می‌کنیم. پیش‌تر مطالبی را در رابطه با CSS pre-processorها مطالعه کرده‌اید، LESS نیز یک CSS pre-processor است، یا در واقع بهتر است بگوئیم یک زبان جهت پویا کردن CSS می‌باشد که در سال 2009 توسط Alexis Sellier به صورت سورس باز ایجاد شد. یکی از خصوصیات جالب LESS نسبت به دیگر CSS preprocessorها، قابلیت کامپایل فایل‌های CSS به صورت real-time از طریق مرورگر توسط LESS.js می‌باشد.

روش‌های استفاده از LESS در دات نت
1- استفاده از SimpLESS
SimpLESS تمام تغییرات فایل‌های CSS را مشاهده می‌کند و در نهایت به صورت خودکار آنها را به CSS کامپایل و همچنین miniy می‌کند. این روش مستقل از دات نت است و برای هر فایل LESSی جوابگو خواهد بود.

2- استفاده از {}less.

Dotless یک پیاده سازی از کتابخانه جاوا اسکریپتی LESS برای دات نت می‌باشد. پکیج نیوگت DotLess را نیز می‌توانید از اینجا دریافت کنید. بعد از اضافه شدن فایل‌های آن، یک ارجاع به dotless.core به پروژه تان اضافه خواهد شد. همچنین در فایل Web.Config در قسمت HttpHandler خط زیر اضافه خواهد شد:

<add type="dotless.Core.LessCssHttpHandler,dotless.Core" validate="false" path="*.LESS" verb="*" />

خط فوق یعنی به محض مواجه شدن با فایل LESS، پردازشگر فایل‌های LESS وارد عمل می‌شود. همچنین خط زیر نیز جهت پیکربندی به قسمت configSections در فایل Web.Config اضافه می‌شود:

<section name="dotless" type="dotless.Core.configuration.DotlessConfigurationSectionHandler,dotless.Core" />

همچنین اگر مایل بودید می‌توانید تنظیمات مربوط به فشرده سازی و caching را نیز فعال کنید:

<dotless minifyCss="false" cache="true" />

3- استفاده از افزونه‌ی Web Essentials

Web Essentials برای کامپایل فایل‌های LESS از کامپایلر node استفاده می‌کند. کار با این افزونه خیلی ساده است. کافی است پسوند فایل CSS موجود در پروژه تان را درون ویژوال استودیو، به less. تغییر دهید. با دوبار کلیک بر روی فایل، ویرایشگر فایل‌های LESS برای شما نمایش داده می‌شود، همزمان نیز فایل یک فایل CSS و یک نسخه از فایل CSS را به صورت فشرده، برایتان تولید می‌کند. خب، هر بار که فایل LESS را تغییر دهید، Web Essentials به صورت خودکار فایل‌های css. و min.css. را برایتان روز رسانی می‌کند.

خوب با کلیک بر روی فایل less، ویرایشگر فایل‌های less نمایش داده می‌شود که با تغییر فایل css می‌توانید پیش نمایش آنرا در سمت راست مشاهده کنید:

تعریف متغیر

با استفاده از syntax زیر می‌توانید متغیرهای خود را تعریف کنید:

@variable-name: variableValue;

یکی از قابلیت‌های جالب در حین مقداردهی متغیرها به خصوص زمانیکه مقدار یک کد رنگی باشد، نمایش کادر انتخاب رنگ است، این کادر بلافاصله بعد از نوشتن علامت # در ابتدای مقدار متغیر نمایش داده می‌شود:

به طور مثال با تعریف متغیر فوق هر جایی می‌توانیم برای تعیین رنگ از آن استفاده کنیم:

@primary-color: #ff6a00;
body
{
    background-color: @primary-color;
}

استفاده از توابع

LESS شامل تعداد زیادی توابع از پیش نوشته شده است که می‌توانید به راحتی از آنها استفاده کنید، توابعی از جمله کار با رنگ ها، اعمال ریاضی و غیره. استفاده از آنها خیلی ساده است. به طور مثال در کد زیر از تابع percentage جهت تبدیل 0.5 به 50% استفاده کرده ایم:

.myClass
{
    width: percentage(0.5);
}

استخراج یک فایل

یکی دیگر از قابلیت‌های Web Essentials استخراج(Extract) یک فایل می‌باشد به طور مثال فایل LESS شما شامل متغیرهای زیر است:

@primary-color: #7BA857;
@primary-color-light: #B6DE8F;
@primary-color-lighter: #D3EFC3;
@primary-color-lightest: #EFFAE6;
@secondary-color: #AE855C;
@text-color-light: #666666;
@text-color-dark: #0444;

به راحتی می‌توانید تعاریف فوق را درون یک فایل LESS دیگر با نام colors.less قرار دهید:

تغییر تنظیمات پیش فرض Web Essentials

افزونه Web Essentials دارای یک قسمت جهت تغییر تنظیمات پیش فرض برای کار با LESS می‌باشد که با مراجعه به منوی Tools در ویژوال استودیو و سپس Options می‌توانید آنها را تغییر دهید:

Auto-compile dependent files on save: توسط این گزینه می‌توانیم تعیین کنیم که فایل‌های که import کرده ایم تنها در صورتی که تغییر کرده و ذخیره شده باشند، در فایل CSS جاری کامپایل شوند.

Compile files on build: توسط این گزینه می‌توانیم تعیین کنیم که فایل‌های less در زمان Build پروژه کامپایل شوند.

Compile files on save: توسط این گزینه می‌توانیم تعیین کنیم که فایل‌های less در زمان ذخیره کردن پروژه کامپایل شوند. 

Create source map files: اگر این گزینه True باشد فایل map. نیز تولید خواهد شد.

Custom output directory: اگر می‌خواهید خروجی در پوشه‌ی موردنظر شما نمایش داده شود می‌توانید آدرس آن را تعیین کنید.

Don't save raw compilation output: با فعال بودن این گزینه فایل CSS عادی ایجاد نخواهد شد.

Process source maps: توسط این گزینه می‌توانید قابلیت‌های ویرایشگر فایل‌های source map را فعال یا غیرفعال کنید.

Strict Math: با فعال بودن این گزینه LESS تمام اعمال ریاضی درون فایل CSS را پردازش خواهد کرد.

Show preview pane: از این گزینه نیز جهت نمایش یا عدم نمایش preview window استفاده می‌شود.

مطالب
پلاگین DataTables کتابخانه jQuery - قسمت دوم
در قسمت قبلی شما را با DataTables آشنا کردم. به طور خلاصه نحوه اعمال کردن DataTables به یک جدول ساده html را گفتیم که با این کار به صورت پیش فرض، امکاناتی مثل فیلتر کردن داده ها، صفحه بندی و مرتب سازی آنها و نیز اعمال شدن استایل‌های css به همین جدول html خام اضافه می‌شود. نکته مهم در مثال قبلی این بود که داده‌های درون این جدول با کدنویسی خام html فراهم شدند، اما این را در نظر داشته باشید که اکثریت مواقع باید داده‌ها از یک بانک اطلاعاتی دریافت شوند و سپس درون جدول قرار بگیرند.

در این قسمت سعی خواهیم کرد تا منبع داده جدول را یک آرایه جاوا اسکریپتی و سپس کالکشنی از آبجکتهای جاوا اسکریپتی (json) در نظر بگیریم و نیز برخی ویژگی‌های پیش فرض پلاگین را غیر فعال نمائیم.

فرض کنید می‌خواهید لیستی از اطلاعات دانشجویان شامل نام (FirstName)، نام خانوادگی (LastName)، و سن (Age) را نمایش دهید. اطلاعات قرار است در جدول زیر قرار بگیرند:
<table id="std-grid">
      <thead>
           <th>نام</th>
           <th>نام خانوادگی</th>
           <th>سن</th>
     </thead>
     <tbody>
                
     </tbody>
</table>


مشاهده می‌کنید که این جدول فقط شامل قسمت header است و در بدنه آن هیچ سطری قرار نگرفته است. در این مثال اطلاعات از یک آرایه جاوا اسکریپتی باید خوانده شوند و تبدیل به html شده و در نهایت درون قسمت <tbody></tbody> آن تزریق شوند. خوشبختانه DataTables برای این کار امکانات آماده ای را در اختیار قرار می‌دهد. این کار بدین صورت قابل انجام است:
<script>        
        $(document).ready(function() {
            $('#std-grid').dataTable({
                "aaData": [
                        ["پژمان", "پارسائی", "24"],
                        ["سعید", "الیاسی", "25"],
                        ["محمد رضا", "گلزار", "20"],
                        ["آرش", "ایرانی", "19"],
                        ["مرتضی", "فرمانی", "22"],
                        ["سعید", "حمیدیان", "23"],
                        ["امین", "پارسانیا", "23"],
                        ["محمد امین", "فقیهی", "24"],
                        ["محمد", "خرمی", "25"],
                        ["سینا", "امیریان", "20"],
                        ["آرش", "ایرانی", "19"],
                        ["وحید", "فرزانه", "22"],
                        ["امیر علی", "فرمانی", "23"],
                        ["امین", "حسینی", "23"],
            });
        });
        
    </script>

شرح کد:
aaData : یک آرایه دو بعدی (که به آن ماتریس یا آرایه ای از آرایه‌ها هم گفته می‌شود) است که مقادیر سلول هایی را نشان می‌دهد که در جدول قرار خواهند گرفت. تعداد ستون‌ها در این آرایه دو بعدی باید با تعداد ستون‌های جدول html متناظر یکسان باشند.

در مثال بالا از یک ماتریس به عنوان منبع داده استفاده شد. منبع داده می‌تواند به فرمت json نیز باشد. البته در این صورت باید ستون‌های جدول html را هم به پلاگین معرفی کنید، بدین صورت:
$(document).ready(function() {            
            $('#std-grid').dataTable({
                "aaData": [
                    {"FirstName" : "پژمان", "LastName" : "پارسائی", "Age" : "24"},
                    { "FirstName": "سعید", "LastName": "الیاسی", "Age": "25" },
                    { "FirstName": "محمد رضا", "LastName": "گلزار", "Age": "24" },
                    { "FirstName": "آرش", "LastName": "ایرانی", "Age": "24" },
                    { "FirstName": "مرتضی", "LastName": "فرمانی", "Age": "24" },
                    { "FirstName": "سعید", "LastName": "حمیدیان", "Age": "24" },
                    { "FirstName": "امین", "LastName": "پارسانیا", "Age": "24" },
                    { "FirstName": "محمد امین", "LastName": "فقیهی", "Age": "24" },
                    { "FirstName": "محمد", "LastName": "خرمی", "Age": "24" },
                    { "FirstName": "سینا", "LastName": "امیریان", "Age": "24" },
                    { "FirstName": "آرش", "LastName": "ایرانی", "Age": "24" },
                    { "FirstName": "وحید", "LastName": "فرزانه", "Age": "24" },
                    { "FirstName": "امیر علی", "LastName": "فرمانی", "Age": "24" },
                    { "FirstName": "امین", "LastName": "حسینی", "Age": "24" },
                ],
                "aoColumns": [
                      { "mDataProp": "FirstName" },
                      { "mDataProp": "LastName" },
                      { "mDataProp": "Age" }
                ]
            });
        });

aaData : همان طور که گفته شد در این قسمت دیتاهای درون جدول آورده می‌شوند و در این مثال آنها به فرمت json نوشته شده اند.
aoColumns : در این قسمت باید اسم ستون‌های جدول ذکر شوند.


غیرفعال کردن بعضی از ویژگی‌های پیش فرض DataTables

همان طور که گفته شد پلاگین DataTables به صورت پیش فرض ویژگی‌های مرتب سازی (sorting)، صفحه بندی (paging)، فیلتر کردن داده‌ها (filtering)، و غیره را به جدول مورد نظرش اعمال می‌کند. و بدین صورت قابل تغییر است:
$('#std-grid').dataTable({
          "bPaginate": false,
          "bLengthChange": false,
          "bFilter": false,
          "bSort": false,
          "bInfo": true,
          "bAutoWidth": false
});

bPaginate : بیان می‌کند آیا صفحه بندی سطرهای جدول فعال باشد یا نه.
bLengthChange : در صورتی که قابلیت صفحه بندی فعال باشد ، بیان می‌کند که کاربر بتواند اندازه صفحه را تغییر دهد یا نه.
bFilter : بیان می‌کند آیا قابلیت فیلتر کردن داده‌ها فعال باشد یا نه.
bSort : بیان می‌کند قابلیت مرتب سازی داده‌های جدول فعال باشد یا نه.
bInfo : بیان می‌کند که قسمت info زیر گرید نشان داده شود یا نه (در این قسمت اطلاعاتی راجع به تعداد کل رکوردهای بایند شده به جدول و نیز رکوردهای درون صفحه جاری نشان داده می‌شود)
bAutoWidth : در صورتی که این گزینه فعال باشد اندازه عرض هر ستون به صوتر خودکار توسط DataTables مقدار دهی خواهد شد.

مقدارهای قابل قبول برای هر کدام از این خصوصیات : true یا false

کدهای مربوط به این مثال را می‌توانید از لینک زیر دریافت کنید:
DataTables-DoteNetTips-Tutorial-02.zip 
مطالب
انجام پی در پی اعمال Async به کمک Iterators - قسمت اول

تقریبا تمام اعمال کار با شبکه در Silverlight از مدل asynchronous programming پیروی می‌کنند؛ از فراخوانی یک متد وب سرویس تا دریافت اطلاعات از وب و غیره. اگر در سایر فناوری‌های موجود در دات نت فریم ورک برای مثال جهت کار با یک وب سرویس هر دو متد همزمان و غیرهمزمان در اختیار برنامه نویس هستند اما اینجا خیر. اینجا فقط روش‌های غیرهمزمان مرسوم هستند و بس. خیلی هم خوب. یک چارچوب کاری خوب باید روش استفاده‌ی صحیح از کتابخانه‌های موجود را نیز ترویج کند و این مورد حداقل در Silverlight اتفاق افتاده است.
برای مثال فراخوانی‌های زیر را در نظر بگیرید:
private int n1, n2;

private void FirstCall()
{
Service.GetRandomNumber(10, SecondCall);
}

private void SecondCall(int number)
{
n1 = number;
Service.GetRandomNumber(n1, ThirdCall);
}

private void ThirdCall(int number)
{
n2 = number;
// etc
}
عموما در اعمال Async پس از پایان عملیات در تردی دیگر، یک متد فراخوانی می‌گردد که به آن callback delegate نیز گفته می‌شود. برای مثال توسط این سه متد قصد داریم اطلاعاتی را از یک وب سرویس دریافت و استفاده کنیم. ابتدا FirstCall فراخوانی می‌شود. پس از پایان کار آن به صورت خودکار متد SecondCall فراخوانی شده و این متد نیز یک عملیات Async دیگر را شروع کرده و الی آخر. در نهایت قصد داریم توسط مقادیر بازگشت داده شده منطق خاصی را پیاده سازی کنیم. همانطور که مشاهده می‌کنید این اعمال زیبا نیستند! چقدر خوب می‌شد مانند دوران synchronous programming (!) فراخوانی‌های این متدها به صورت ذیل انجام می‌شد:
private void FetchNumbers()
{
int n1 = Service.GetRandomNumber(10);
int n2 = Service.GetRandomNumber(n1);
}
در برنامه نویسی متداول همیشه عادت داریم که اعمال به صورت A –> B –> C انجام شوند. اما در Async programming ممکن است ابتدا C انجام شود، سپس A و بعد B یا هر حالت دیگری صرفنظر از تقدم و تاخر آن‌ها در حین معرفی متدهای مرتبط در یک قطعه کد. همچنین میزان خوانایی این نوع کدنویسی نیز مطلوب نیست. مانند مثال اول ذکر شده، یک عملیات به ظاهر ساده به چندین متد منقطع تقسیم شده است. البته به کمک lambda expressions مثال اول را به شکل زیر نیز می‌توان در طی یک متد ارائه داد اما اگر تعداد فراخوانی‌ها بیشتر بود چطور؟ همچنین آیا استفاده از عدد n2 بلافاصله پس از عبارت ذکر شده مجاز است؟ آیا عملیات واقعا به پایان رسیده و مقدار مطلوب به آن انتساب داده شده است؟
private void FetchNumbers()
{
int n1, n2;

Service.GetRandomNumber(10, result =>
{
n1 = result;
Service.GetRandomNumber(n1, secondResult =>
{
n2 = secondResult;
});
});
}

به عبارتی می‌خواهیم کل اعمال انجام شده در متد FetchNumbers هنوز Async باشند (ترد اصلی برنامه را قفل نکنند) اما پی در پی انجام شوند تا مدیریت آن‌ها ساده‌تر شوند (هر لحظه دقیقا بدانیم که کجا هستیم) و همچنین کدهای تولیدی نیز خواناتر باشند.
روش استانداری که توسط الگوهای برنامه نویسی برای حل این مساله پیشنهاد می‌شود، استفاده از الگوی coroutines است. توسط این الگو می‌توان چندین متد Async را در حالت معلق قرار داده و سپس در هر زمانی که نیاز به آن‌ها بود عملیات آن‌ها را از سر گرفت.
دات نت فریم ورک حالت ویژه‌ای از coroutines را توسط Iterators پشتیبانی می‌کند (از C# 2.0 به بعد) که در ابتدا نیاز است از دیدگاه این مساله مروری بر آن‌ها داشته باشیم. مثال بعد یک enumerator را به همراه yield return ارائه داده است:

using System;
using System.Collections.Generic;
using System.Threading;

namespace CoroutinesSample
{
class Program
{
static void printAll()
{
foreach (int x in integerList())
{
Console.WriteLine(x);
}
}

static IEnumerable<int> integerList()
{
yield return 1;
Thread.Sleep(1000);
yield return 2;
yield return 3;
}

static void Main()
{
printAll();
}
}
}

کامپایلر سی شارپ در عمل یک state machine را برای پیاده سازی این عملیات به صورت خودکار تولید خواهد کرد:

private bool MoveNext()
{
switch (this.<>1__state)
{
case 0:
this.<>1__state = -1;
this.<>2__current = 1;
this.<>1__state = 1;
return true;

case 1:
this.<>1__state = -1;
Thread.Sleep(0x3e8);
this.<>2__current = 2;
this.<>1__state = 2;
return true;

case 2:
this.<>1__state = -1;
this.<>2__current = 3;
this.<>1__state = 3;
return true;

case 3:
this.<>1__state = -1;
break;
}
return false;
}

در حین استفاده از یک IEnumerator ابتدا در وضعیت شیء Current آن قرار خواهیم داشت و تا زمانیکه متد MoveNext آن فراخوانی نشود هیچ اتفاق دیگری رخ نخواهد داد. هر بار که متد MoveNext این enumerator فرخوانی گردد (برای مثال توسط یک حلقه‌ی foreach) اجرای متد integerList ادامه خواهد یافت تا به yield return بعدی برسیم (سایر اعمال تعریف شده در حالت تعلیق قرار دارند) و همینطور الی آخر.
از همین قابلیت جهت مدیریت اعمال Async پی در پی نیز می‌توان استفاده کرد. State machine فوق تا پایان اولین عملیات تعریف شده صبر می‌کند تا به yield return برسد. سپس با فراخوانی متد MoveNext به عملیات بعدی رهنمون خواهیم شد. به این صورت دیدگاهی پی در پی از یک سلسه عملیات غیرهمزمان حاصل می‌گردد.

خوب ما الان نیاز به یک کلاس داریم که بتواند enumerator ایی از این دست را به صورت خودکار مرحله به مرحله آن هم پس از پایان واقعی عملیات Async قبلی (یا مرحله‌ی قبلی)، اجرا کند. قبل از اختراع چرخ باید متذکر شد که دیگران اینکار را انجام داده‌اند و کتابخانه‌های رایگان و یا سورس بازی برای این منظور موجود است.


ادامه دارد ...

مطالب
آشنایی با NHibernate - قسمت نهم

استفاده از Log4Net جهت ثبت خروجی‌های SQL حاصل از NHibernate

هنگام استفاده از NHibernate، پس از افزودن ارجاعات لازم به اسمبلی‌های مورد نیاز آن به برنامه، یکی از اسمبلی‌هایی که به پوشه build برنامه به صورت خودکار کپی می‌شود، فایل log4net.dll است (حتی اگر ارجاعی را به آن اضافه نکرده باشیم) که جهت ثبت وقایع مرتبط با NHibernate مورد استفاده قرار می‌گیرد. خوب اگر مجبوریم که این وابستگی کتابخانه NHibernate را نیز در پروژه‌های خود داشته باشیم، چرا از آن استفاده نکنیم؟!
شرح مفصل استفاده از این کتابخانه سورس باز را در سایت اصلی آن می‌توان مشاهده کرد:


برای اینکه از این کتابخانه در برنامه خود جهت ثبت عبارات SQL تولیدی توسط NHibernate استفاده کنیم، باید مراحل زیر طی شوند:
الف) ارجاعی را به اسمبلی log4net.dll اضافه نمائید (کنار اسمبلی NHibernate در پوشه build برنامه باید موجود باشد)
ب) فایل app.config برنامه را (برنامه ویندوزی) به صورت زیر ویرایش کرده و چند سطر مربوطه را اضافه نمائید (در مورد برنامه‌های وب هم به همین شکل است. configSections فایل web.config تنظیم شده و سپس تنظیمات log4net را قبل از بسته شدن تگ configuration اضافه نمائید ) :

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<section name="log4net"
type="log4net.Config.Log4NetConfigurationSectionHandler,log4net" />
</configSections>

<connectionStrings>
<!--NHSessionManager-->
<add name="DbConnectionString"
connectionString="Data Source=(local);Initial Catalog=HelloNHibernate;Integrated Security = true"/>
</connectionStrings>

<log4net>
<appender name="rollingFile"
type="log4net.Appender.RollingFileAppender,log4net" >
<param name="File" value="NHibernate_Log.txt" />
<param name="AppendToFile" value="true" />
<param name="DatePattern" value="yyyy.MM.dd" />
<rollingStyle value="Size" />
<maxSizeRollBackups value="10" />
<maximumFileSize value="500KB" />
<staticLogFileName value="true" />
<layout type="log4net.Layout.PatternLayout,log4net">
<conversionPattern value="%d %p %m%n" />
</layout>
</appender>
<logger name="NHibernate.SQL">
<level value="ALL" />
<appender-ref ref="rollingFile" />
</logger>
</log4net>

</configuration>
ج) سپس باید فراخوانی زیر نیز در ابتدای کار برنامه صورت گیرد:

log4net.Config.XmlConfigurator.Configure();
در یک برنامه ASP.Net این فراخوانی باید در Application_Start فایل Global.asax.cs صورت گیرد.
یا در یک برنامه از نوع WinForms تنها کافی است سطر زیر را به فایل AssemblyInfo.cs برنامه اضافه کرد:

// Configure log4net using the .config file
[assembly: log4net.Config.XmlConfigurator(Watch = true)]
یا این سطر را به فایل Global.asax.cs یک برنامه ASP.Net نیز می‌توان اضافه کرد. Watch=true آن، با کمک FileSystemWatcher تغییرات فایل کانفیگ را تحت نظر داشته و هر بار که تغییر کند بلافاصله، تغییرات جدید را اعمال خواهد کرد.

د) هنگام استفاده از کتابخانه Fluent NHibernate حتما باید متد ShowSql در جایی که دیتابیس برنامه را تنظیم می‌کنیم (Fluently.Configure().Database) ذکر گردد (که نمونه آن‌را در مثال‌های قسمت‌های قبل ملاحظه‌ کرده‌اید).

توضیحاتی در مورد تنظیمات فوق:
configSections حتما باید در ابتدای فایل app.config‌ ذکر شود و گرنه برنامه کار نخواهد کرد.
سپس کانکشن استرینگ مورد استفاده در قسمت کانفیگ برنامه ذکر شده است.
در ادامه تنظیمات استاندارد مربوط به log4net را مشاهده می‌کنید.
در تنظیمات این کتابخانه، appender مشخص کننده محل ثبت وقایع است. زمانیکه که از RollingFileAppender استفاده کنیم، اطلاعات را در یک سری فایل ذخیره خواهد کرد (امکان ثبت وقایع در EventLog ویندوز، ارسال از طریق ایمیل و غیره نیز میسر است که جهت توضیحات بیشتر می‌توان به مستندات آن رجوع نمود).
سپس نام فایلی که اطلاعات وقایع در آن ثبت خواهند شد ذکر شده است (برای مثال NHibernate_Log.txt)، در ادامه مشخص گردیده که اطلاعات باید هر بار به این فایل Append و اضافه شوند. سطرهای بعدی مشخص می‌کنند که هر زمانیکه این لاگ فایل به 10 مگابایت رسید، یک فایل جدید تولید کن و هر بار 10 فایل آخر را نگه دار و مابقی فایل‌های قدیمی را حذف کن.
در قسمت PatternLayout مشخصات می‌کنیم که خروجی ثبت شده با چه فرمتی باشد. برای مثال یک سطر خروجی مطابق با تنظیمات فوق به شکل زیر خواهد بود:

2009-10-18 20:03:54,187 DEBUG INSERT INTO [Student] (Name) VALUES (@p0); select SCOPE_IDENTITY();@p0 = 'Vahid'
در قسمت Logger یک نام دلخواه ذکر شده و میزان اطلاعاتی که باید درج شود، از طریق مقدار level مورد نظر، قابل تنظیم است که می‌تواند یکی از مقادیر ALL ،DEBUG ،INFO ،WARN ،ERROR ،FATAL و یا OFF باشد. اینجا level در نظر گرفته شده ALL است که تمامی اطلاعات مرتبط با اعمال پشت صحنه NHibernate را لاگ خواهد کرد.
توسط appender-ref آن appender ایی را که در ابتدای کار تعریف و تنظیم کردیم، مشخص خواهیم کرد.

اگر هم با برنامه نویسی بخواهیم اطلاعاتی را به این لاگ فایل اضافه کنیم تنها کافی است بنویسیم:

log4net.LogManager.GetLogger("NHibernate.SQL").Info("test1");

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

ادامه دارد ...

مطالب
تولید فایل Word بدون نصب MS Word بر روی سرور

یکی از مواردی که ممکن است در محیط کاری با آن برخورد داشت، تقاضای تولید فایل word یک گزارش با فرمتی مشخص از یک برنامه ASP.Net است. برای مثال یک قالب درست کرده‌اند که header‌ و footer و کلا یک فرمت رسمی دارد. الان برنامه شما باید این فایل word رسمی را با گزارشی که تولید می‌کند پر کند. حالا اینجاست که گرفتاری برنامه نویس شروع می‌شود! روی سرور باید word نصب باشد تا توسط اشیاء COM آن بتوان یک چنین کارهایی را آن‌هم با ASP.Net که به صورت پیش فرض کمترین سطح دسترسی را روی سیستم دارد انجام داد. یا اینکه باید به سراغ کامپوننت‌های تجاری رفت و حالا اینجا با این وضع تحریم و غیره چگونه بتوان آنها را خریداری کرد یا شاید احتمالا در سایت‌های وارز بتوان نسخه تکه پاره شده آنها را یافت. مشکلی هم که این نوع کامپوننت‌ها دارند این است که ممکن است سال دیگر اصلا ساپورت نشوند. محصولات مایکروسافت هم که مرتبا در حال به روز رسانی هستند. در این حالت برنامه متکی به این نوع کامپوننت‌های تجاری سورس بسته در همان نگارش قبلی خود مجبور است باقی بماند.
خوشبختانه با ارائه آفیس 2007 و فرمت OpenXML فایلهای آن، این مشکل تقریبا مرتفع شده است. مایکروسافت نیز برای سهولت تولید این نوع اسناد، OpenXML SDK را ارائه داده است که از آدرس زیر قابل دریافت است:
Open XML Format SDK 1.0

البته پیش نمایش نگارش دو SDK آن نیز موجود است که در مطلب جاری به آن پرداخته نخواهد شد.

فایل‌های office 2007 از یک فایلzip تشکیل شده از چند فایل xml داخل آن، ایجاد شده‌اند. برای مثال یک فایل docx را با winrar یا امثال آن باز کنید (تصویر زیر):



برای کار با اینگونه اسناد باید با اصطلاحات زیر آشنا شد:
Package : فایل zip شما (همان فایل برای مثال docx) اینجا یک بسته نام دارد.
Parts : اجزای این بسته که همان فایل‌های آن هستند، parts نامیده شده اند.
Relations : اگر به فایل‌های موجود در یک بسته دقت کنید، فایلهایی با پسوند rels را خواهید دید که بیانگر نحوه ارتباط Parts با یکدیگر هستند.
Relations Ids: هر ارتباط با یک ID منحصربفرد تعریف می‌گردد.

اگر علاقمند باشید که پوستری را در این رابطه مشاهده نمائید می‌توان به آدرس زیر مراجعه نمود.
Open XML Developer Map

نحوه استفاده از OpenXML SDK در دات نت:
ابتدا باید ارجاعی را به فایل DocumentFormat.OpenXml.dll که پس از نصب در مسیر OpenXMLSDK\1.0.1825\lib قرار گرفته است به پروژه افزود. سپس نیاز است تا ارجاعی به کتابخانه WindowsBase نیز به برنامه افزوده شود (تصویر زیر). افزودن ارجاعی به این کتابخانه جهت کامپایل برنامه ضروری است (شکل زیر).


تا اینجا ارجاعات برنامه به صورت زیر خواهند بود:



یک مثال ساده:
قصد داریم یک فایل docx ساده را با استفاده از OpenXML SDK ایجاد کنیم. در مثال زیر فرمت متغیر docXml را می‌توان با ایجاد یک فایل docx ساده در word و سپس باز کردن بسته فشرده شده آن و مشاهده محتوای فایل word\document.xml بدست آورد.
using System.IO;
using System.Text;
using DocumentFormat.OpenXml;
using DocumentFormat.OpenXml.Packaging;

namespace OpenXMLTestApp
{
class CWord
{

public static void CreateDocument(string documentFileName, string text)
{
using (WordprocessingDocument wordDoc =
WordprocessingDocument.Create(documentFileName, WordprocessingDocumentType.Document))
{
MainDocumentPart mainPart = wordDoc.AddMainDocumentPart();

string docXml =
@"<?xml version=""1.0"" encoding=""UTF-8"" standalone=""yes""?>
<w:document xmlns:w=""http://schemas.openxmlformats.org/wordprocessingml/2006/main"">
<w:body><w:p><w:r><w:t>#REPLACE#</w:t></w:r></w:p></w:body>
</w:document>";

docXml = docXml.Replace("#REPLACE#", text);

using (Stream stream = mainPart.GetStream())
{
byte[] buf = (new UTF8Encoding()).GetBytes(docXml);
stream.Write(buf, 0, buf.Length);
}
}
}
}
}

و نحوه استفاده از آن می‌تواند به صورت زیر باشد:

CWord.CreateDocument("test.docx", "سلام دنیا");

این کتابخانه کار ایجاد فایل‌های xml ، تولید روابط بین‌ آنها و همچنین بسته بندی و zip کردن نهایی را به صورت خودکار انجام می‌دهد.

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

یک ویدیوی آموزشی رایگان از مایکروسافت
دریافت

سؤالات متداول در MSDN
http://msdn.microsoft.com/en-us/library/bb491088.aspx
البته اگر پس از نصب SDK به پوشه doc آن مراجعه نمائید، این سؤال و جواب‌ها را در فایل راهنمای chm آن نیز می‌توان پیدا کرد.

مثال دیگری در مورد ایجاد یک گزارش از بانک اطلاعاتی و گرفتن خروجی docx از آن
http://openxmldeveloper.org/articles/GenerateWordTable.aspx
البته این مثال خیلی قدیمی است و قسمت‌های کار با پکیج را با SDK‌ ارائه شده می‌توان به صورت خودکار انجام داد. اما حداقل نحوه تولید جداول استاندارد OpenXML را می‌توان از آن ایده گرفت.

مثالی در مورد نحوه قرار دادن عکس در فایل docx تولیدی

همچنین مثال‌های بیشتری را در وبلاگ‌های مربوطه می‌توان یافت:
http://blogs.msdn.com/brian_jones/
http://blogs.msdn.com/ericwhite/default.aspx


مطالب
ASP.NET MVC #1

چرا ASP.NET MVC ؟

با وجود فریم ورک پخته‌ای به نام ASP.NET web forms، اولین سؤالی که حین سوئیچ به ASP.NET MVC مطرح می‌شود این است: «برای چی؟». بنابراین تا به این سؤال پاسخ داده نشود، هر نوع بحث فنی در این مورد بی فایده است.

مزایای ASP.NET MVC نسبت به ASP.NET web forms

1) سادگی نوشتن آزمون‌های واحد
مهم‌ترین دلیل استفاده از ASP.NET MVC صرفنظر از تمام دلایل دیگر، بحث طراحی ویژه آن جهت ساده سازی تهیه آزمون‌های واحد است. مشکل اصلی نوشتن آزمون‌های واحد برای برنامه‌های ASP.NET web forms، درگیر شدن مستقیم با تمام جزئیات طول عمر یک صفحه است. به علاوه فایل‌های code behind هر چند به ظاهر کدهای منطق یک صفحه را از کدهای HTML مانند آن جدا می‌کنند اما در عمل حاوی ارجاعات مستقیمی به تک تک عناصر بصری موجود در صفحه هستند (حس غلط جدا سازی کدها از اجزای یک فرم). اگر قرار باشد برای این وب فرم‌ها و صفحات، آزمون واحد بنویسیم باید علاوه بر شبیه سازی چرخه طول عمر صفحه و همچنین رخدادهای رسیده، کار وهله سازی تک تک عناصر بصری را نیز عهده دار شویم. اینجا است که ASP.NET web forms گزینه‌ی مطلوبی برای این منظور نخواهد بود و اگر نوشتن آزمون واحد برای آن غیرممکن نباشد، به همین دلایل آنچنان مرسوم هم نیست.
البته شاید بپرسید که این مساله چه اهمیتی دارد؟ امکان نوشتن ساده‌تر آزمون‌های واحد مساوی است با امکان ساده‌تر اعمال تغییرات به یک پروژه بزرگ. تغییرات در پروژه‌های بزرگی که آزمون واحد ندارند واقعا مشکل است. یک قسمت را تغییر می‌دهید، 10 قسمت دیگر به هم می‌ریزند. اینجا است که مدام باید به کارفرما گفت: «نه!»، «نمیشه!» یا به عبارتی «نمی‌تونم پروژه رو جمع کنم!» چون نمی‌تونم سریع برآورد کنم که این تغییرات کدام قسمت‌ها را تحت تاثیر قرار می‌دهند، کجا به هم ریخت. من باید خودم سریع بتونم مشخص کنم با این تغییر جدید چه قسمت‌هایی به هم ریخته تا اینکه دو روز بعد زنگ بزنند: «باز جایی رو تغییر دادی، یکجای دیگر کار نمی‌کنه!»

2) دستیابی به کنترل بیشتر بر روی اجزای فریم ورک
در طراحی ASP.NET MVC همه‌جا interface ها قابل مشاهد هستند. همین مساله به معنای افزونه پذیری اکثر قطعات تشکیل دهنده ASP.NET MVC است؛ برخلاف ASP.NET web forms. برای مثال تابحال چندین view engine، routing engine و غیره توسط برنامه نویس‌های مستقل برای ASP.NET MVC طراحی شده‌اند که هیچکدام با ASP.NET web forms میسر نیست. برای مثال از view engine پیش فرض آن خوشتان نمی‌آید؟ عوضش کنید! سیستم اعتبار سنجی توکار آن‌را دوست ندارید؟ آن‌را با یک نمونه بهتر تعویض کنید و الی آخر ...
به علاوه طراحی بر اساس interface ها یک مزیت دیگر را هم به همراه دارد و آن هم ساده سازی mocking (تقلید) آن‌ها است جهت ساده سازی نوشتن آزمون‌های واحد.

3) سرعت بیشتر اجرا
ASP.NET MVC یک سری از قابلیت‌های ذاتی ASP.NET web forms را مانند ViewState حذف کرده است. اگر وب را جستجو کنید، برنامه نویس‌های ASP.NET web forms مدام از این مساله شکایت دارند و راه‌ حل‌های مختلفی را جهت حذف یا فشرده سازی آن ارائه می‌دهند. ViewState در ابتدای امر جهت شبیه سازی محیط دسکتاپ در وب درنظر گرفته شده بود و مهاجرت ساده‌تر برنامه نویس‌های VB6 به وب، اما واقعیت این است که اگر یک برنامه نویس ASP.NET web forms به اندازه آن توجهی نداشته باشد، ممکن است حجم آن در یک صفحه پیچیده تا 500 کیلوبایت یا بیشتر هم برسد. همین مساله بر روی سرعت دریافت و اجرا تاثیر گذار خواهد بود.

4) کنترل‌های ASP.NET web forms آنچنان آش دهن‌سوزی هم نیستند!
خوب، ViewState حذف شده، بنابراین اکثر کنترل‌های ASP.NET web forms هم کاربرد آنچنانی در ASP.NET MVC نخواهند داشت؛ اما واقعیت این است که اکثر اوقات اگر شروع به سفارشی سازی یک کنترل توکار ASP.NET web forms کنید تا مطابق نیازهای کاری شما رفتار کند، پس از مدتی به یک کنترل کاملا از نو بازنویسی شده خواهید رسید! بنابراین در ابتدای امر تا 80 درصد کار اینطور به نظر می‌رسد که به عجب سرعت بالایی در توسعه دست یافته‌ایم، اما هنگامیکه قرار است این 20 درصد پایانی را پر کنیم، به این نتیجه خواهیم رسید که این کنترل‌ها با این وضع ابتدایی که دارند قابل استفاده نیستند و نیاز به دستکاری قابل ملاحظه‌ای دارند تا نیازهای واقعی کاری را برآورده کنند.

5) کنترل کامل بر روی HTML نهایی تولیدی
اگر علاقمند به کار با jQuery باشید، مدام نیاز خواهید تا با ID کنترل‌ها و عناصر صفحه کار کنید. پیشتر ASP.NET web forms این ID را یک طرفه و به صورت مقدار منحصربفردی تولید می‌کرد که جهت کار با فریم ورک‌های جاوا اسکریپتی عموما مشکل ساز بود. البته ASP.NET web forms در نگارش‌های جدید خود مشکل عدم امکان مقدار دهی ClientId سفارشی را برای کنترل‌های وب خود برطرف کرده است و این مورد را می‌توان دستی هم تنظیم کرد ولی در کل باز هم آنچنان کنترلی رو خروجی HTML نهایی کنترل‌های تولیدی نیست مگر اینکه مانند مورد چهارم یاد شده یک کنترل را از صفر بازنویسی کنید!
همچنین اگر باز هم بیشتر با jQuery و ASP.NET web forms کار کرده باشید می‌دانید که jQuery آنچنان سنخیتی با ViewState و Postback وب فرم‌ها ندارد و همین مساله عموما مشکل‌زا است. علاوه بر آن اخیرا مایکروسافت توسعه ASP.NET Ajax خود را تقریبا در حالت تعلیق و واگذار شده به شرکت‌های ثالث درآورده است و توصیه آن‌ها استفاده از jQuery Ajax است. اینجا است که مدل ASP.NET MVC سازگاری کاملی را با jQuery Ajax دارد هم از لحاظ نبود ViewState و هم از جنبه‌ی کنترل کامل بر روی markup نهایی تولیدی.
یا برای مثال خروجی پیش فرض یک GridView، جدول HTML ایی است که این روزها همه‌جا علیه آن صحبت می‌شود. البته یک سری آداپتور CSS friendly برای اکثر این کنترل‌ها موجود است و ... باز هم دستکاری بیش از حد کنترل‌های پیش فرض جهت رسیدن به خروجی دلخواه. تمام این‌ها را در ASP.NET MVC می‌شود با معادل‌های بسیار باکیفیت افزونه‌های jQuery جایگزین کرد و از همه مهم‌تر چون ViewState و مفاهیمی مانند PostBack حذف شده، استفاده از این افزونه‌ها مشکل ساز نخواهد بود.

6) استفاده از امکانات جدید زبان‌های دات نتی
طراحی اصلی ASP.NET web forms مربوط است به دوران دات نت یک؛ زمانیکه نه Generics وجود داشت، نه LINQ و نه آنچنان مباحث TDD یا استفاده از ORMs متداول بود. برای مثال شاید ایجاد یک strongly typed web form الان کمی دور از ذهن به نظر برسد، زمانیکه اصل آن بر مبنای بکارگیری گسترده datatable و dataset بوده است (با توجه به امکانات زبان‌های دات نتی در آن دوران). بنابراین اگر علاقمند هستید که این امکانات جدید را بکاربگیرید، ASP.NET MVC برای استفاده از آن‌ها طراحی شده است!

7) از ASP.NET web forms ساده‌تر است
طراحی ASP.NET MVC بر اساس ایده Convention over configuration است. به این معنا که اجزای آن بر اساس یک سری قرار داد در کنار هم مشغول به کار هستند. مشخص است View باید کجا باشد، نام کنترلرها چگونه باید تعیین شوند و قرار داد مرتبط به آن چیست، مدل باید کجا قرار گیرد، قرار داد پردازش آدرس‌های صفحات سایت به چه نحوی است و الی آخر. خلاصه در بدو امر با یک فریم ورک حساب شده که شما را در مورد نحوه استفاده صحیح از آن راهنمایی می‌کند، مواجه هستید.
به همین ترتیب هر پروژه MVC دیگری را هم که مشاهده کنید، سریع می‌توانید تشخیص دهد قراردادهای بکارگرفته شده در آن چیست. بنابراین اگر قرار است ASP.NET را امروز شروع کنید و هیچ سابقه‌ای هم از وب فرم‌ها ندارید، یک راست با ASP.NET MVC شروع کنید.

8) محدود به پیاده سازی مایکروسافت نیست
پیاده سازی‌های مستقلی هم از ASP.NET MVC توسط اشخاص و گروه‌های خارج از مایکروسافت وجود دارد: ^، ^، ^، ^ و ...


و در پایان یکی دیگر از دلایل سوئیچ به ASP.NET MVC ، «یاد گرفتن یک چیز جدید است» یا به عبارتی فرا گرفتن یک روش دیگر برای حل مسایل، هیچگاه ضرری را به همراه نخواهد داشت که هیچ، بلکه باعث بازتر شدن میدان دید نیز خواهد گردید.


یک دیدگاه دیگر
ASP.NET MVC برای شما مناسب نخواهد بود اگر ...
1) با پلی‌مرفیزم مشکل دارید.
ASP.NET MVC پر است از interfaces، abstract classes، virtual methods و امثال آن. بنابراین اگر تازه کار هستید، ابتدا باید مفاهیم شیءگرایی را تکمیل کنید.

2) اگر نمی‌توانید فریم ورک خودتون رو بر پایه ASP.NET MVC بنا کنید!
ASP.NET MVC برخلاف وب فرم‌ها به همراه آنچنان تعداد بالایی کنترل و افزونه از پیش مهیا شده نیست. در بدو امر شما فقط یک سری url helper، html helper و ajax helper ساده را خواهید دید؛ این نقطه ضعف ASP.NET MVC نیست. عمدا به این نحو طراحی شده است. همانطور که عنوان شد اکثر اجزای این فریم ورک قابل تعویض است. بنابراین دست شما را باز گذاشته است تا با پیاده سازی این اینترفیس‌ها، امکانات جدیدی را خلق کنید. البته پس از این چندین و چند سال که از ارائه آن می‌گذرد، به اندازه کافی افزونه برای ASP.NET MVC طراحی شده است که به هیچ عنوان احساس کمبود نکنید یا اینکه نیازی هم نداشته باشید تا آنچنان فریم ورک خاصی را بر پایه ASP.NET MVC تهیه کنید. برای مثال پروژه MvcContrib موجود است یا شرکت telerik یک مجموعه سورس باز کامل مخصوص ASP.NET MVC را ارائه داده است و الی آخر.

3) اگر نمی‌توانید از کتابخانه‌های سورس باز استفاده کنید.
همانطور که عنوان شد ASP.NET MVC به همراه کوهی از کنترل‌ها ارائه نشده است. اکثر افزونه‌های آن سورس باز هستند و کار با آن‌ها هم دنیای خاص خودش را دارد. چگونه باید کتابخانه‌های مناسب را پیدا کرد، کجا سؤال پرسید، کجا باگ گزارش داد، چگونه مشارکت کرد و غیره. خلاصه منتظر یک بسته شکیل حاضر و آماده نباید بود. خود ASP.NET MVC هم تحت مجوز MSPL به صورت سورس باز در دسترس است.


و یک نکته تکمیلی
مایکروسافت مدتی است شروع کرده به پرورش و زمزمه ایده «یک ASP.NET واحد». به عبارتی قصد دارند در یکی دو نگارش بعد، این دو (وب فرم و MVC) را یکی کنند. هم اکنون اگر مطالب وبلاگ‌ها را مطالعه کنید زیرساخت آن به نام ASP.NET Web API آماده شده است و در مرحله بتا است. نکته جالب اینجا است که این Web API امکان تعریف یکپارچه و مستقیم کنترلر‌های MVC را در وب فرم‌ها میسر می‌کند. ولی باز هم نام آن Controller است یعنی جزئی از ASP.NET MVC و کسی می‌تواند از آن استفاده کند که با MVC‌ مشکلی نداشته باشد. بنابراین یادگیری MVC هیچ ضرری نخواهد داشت و جای دوری نخواهد رفت!



مطالب
مسیریابی در Angular - قسمت پنجم - تعریف Child Routes
در Angular امکان تعریف مسیریابی‌هایی، درون سایر مسیریابی‌ها نیز پیش بینی شده‌است. با استفاده از مفهوم Child Routes، امکان تعریف سلسله مراتب مسیریابی‌ها جهت ساماندهی و مدیریت مسیریابی درون برنامه، وجود دارد. همچنین lazy loading مسیریابی‌ها را نیز ساده‌تر کرده و کارآیی آغاز برنامه را بهبود می‌بخشند.


علت نیاز به Child Routes
 
در مثال این سری، منوی اصلی آن به صورت ذیل تعریف شده‌است:
<ul class="nav navbar-nav">
      <li><a [routerLink]="['/home']">Home</a></li>
      <li><a [routerLink]="['/products']">Product List</a></li>
      <li><a [routerLink]="['/products', 0, 'edit']">Add Product</a></li>      
</ul>
سپس از دایرکتیو router-outlet جهت تعریف محل قرارگیری محتوای این مسیریابی‌ها استفاده شده‌است:
<div class="container">
  <router-outlet></router-outlet>
</div>
هربار که مسیری تغییر می‌کند، محتوای router-outlet با محتوای قالب آن کامپوننت جایگزین خواهد شد. اما اگر تعداد المان‌های صفحه‌ی ویرایش محصولات بیش از اندازه بودند و خواستیم فیلدهای آن‌را به دو برگه (tab) تقسیم کنیم چطور؟ برای اینکار نیاز است تا router-outlet ثانویه و مخصوص این قالب را تعریف کنیم. هربار که کاربری بر روی برگه‌ای کلیک می‌کند، به کمک Child routes، محتوای آن برگه را در این router-outlet ثانویه نمایش می‌دهیم. به این ترتیب به کمک Child routes می‌توان امکان نمایش محتوای مسیریابی دیگری را درون مسیریابی اصلی، میسر کرد.

کاربردهای Child routes
 - امکان تقسیم فرم‌های طولانی به چند Tab
 - امکان طراحی طرحبندی‌های Master/Layout
 - قرار دادن قالب یک کامپوننت، درون قالب کامپوننتی دیگر
 - بهبود کپسوله سازی ماژول‌های برنامه
 - جزو الزامات Lazy loading هستند


تنظیم کردن Child Routes

مثال جاری این سری، تنها به همراه یک سری primary routes است؛ مانند صفحه‌ی خوش‌آمد گویی، نمایش لیست محصولات، افزودن و ویرایش محصولات. قالب‌های کامپوننت‌های این‌ها نیز در router-outlet اصلی برنامه نمایش داده می‌شوند. در ادامه می‌خواهیم کامپوننت ویرایش محصولات را تغییر داده و تعدادی برگه را به آن اضافه کنیم. برای اینکار، نیاز به تعریف Child routes است تا بتوان قالب‌های کامپوننت‌های هر برگه را در router-outlet کامپوننت والد که در درون router-outlet اصلی برنامه قرار دارد، نمایش داد.
به همین جهت دو کامپوننت جدید ProductEditInfo و ProductEditTags را نیز به ماژول محصولات اضافه می‌کنیم:
>ng g c product/ProductEditInfo
>ng g c product/ProductEditTags
این دستورات سبب به روز رسانی فایل src\app\product\product.module.ts، جهت تکمیل قسمت declarations آن نیز خواهند شد.

به علاوه اینترفیس src\app\product\iproduct.ts را نیز جهت افزودن گروه محصولات و همچنین آرایه‌ی برچسب‌های یک محصول تکمیل می‌کنیم:
export interface IProduct {
    id: number;
    productName: string;
    productCode: string;
    category: string;
    tags?: string[];
}
در این حالت می‌توانید فایل app\product\product-data.ts را نیز ویرایش کرده و به هر محصول، تعدادی گروه و برچسب را نیز انتساب دهید؛ که البته ذکر tags آن اختیاری است. در اینجا فایل src\app\product\product.service.ts نیز باید ویرایش شده و متد initializeProduct آن تعاریف []:category: null, tags را نیز پیدا کنند.

در ادامه برای تنظیم Child Routes، فایل src\app\product\product-routing.module.ts را گشوده و آن‌را به نحو ذیل تکمیل کنید:
import { ProductEditTagsComponent } from './product-edit-tags/product-edit-tags.component';
import { ProductEditInfoComponent } from './product-edit-info/product-edit-info.component';

const routes: Routes = [
  { path: 'products', component: ProductListComponent },
  {
    path: 'products/:id', component: ProductDetailComponent,
    resolve: { product: ProductResolverService }
  },
  {
    path: 'products/:id/edit', component: ProductEditComponent,
    resolve: { product: ProductResolverService },
    children: [
      {
        path: '',
        redirectTo: 'info',
        pathMatch: 'full'
      },
      {
        path: 'info',
        component: ProductEditInfoComponent
      },
      {
        path: 'tags',
        component: ProductEditTagsComponent
      }
    ]
  }
];
- Child Routes، در داخل آرایه‌ی خاصیت children تنظیمات یک مسیریابی والد، قابل تعریف هستند. برای نمونه در اینجا Child Routes به تنظیمات مسیریابی ویرایش محصولات اضافه شده‌اند و کار توسعه‌ی مسیریابی والد خود را انجام می‌دهند.
- در اولین Child Route تعریف شده، مقدار path به '' تنظیم شده‌است. به این ترتیب مسیریابی پیش فرض آن (در صورت عدم ذکر صریح آن‌ها در URL) به صورت خودکار به مسیریابی info هدایت خواهد شد. بنابراین درخواست مسیر products/:id/edit به دومین Child Route تنظیم شده هدایت می‌شود.
- دومین Child Route تعریف شده با مسیری مانند products/:id/edit/info تطابق پیدا می‌کند.
- سومین Child Route تعریف شده با مسیری مانند products/:id/edit/tags تطابق پیدا می‌کند.


تعیین محل نمایش Child Views

برای نمایش قالب یک Child Route درون قالب والد آن، نیاز به تعریف یک دایرکتیو router-outlet جدید، درون قالب والد است و نحوه‌ی تعریف آن با primary outlet تعریف شده‌ی در فایل src\app\app.component.html تفاوتی ندارد.
برای پیاده سازی این مفهوم، نیاز است از قالب ویرایش محصولات و یا فایل src\app\product\product-edit\product-edit.component.html که قالب والد این Child Routes است شروع و آن‌را به دو Child View تقسیم کنیم. این قالب، تاکنون حاوی فرمی جهت ویرایش و افزودن محصولات است. در ادامه می‌خواهیم بجای آن چند برگه را نمایش دهیم. به همین جهت این فرم را حذف کرده و با دو برگه‌ی جدید جایگزین می‌کنیم. در اینجا نحوه‌ی تعریف لینک‌های جدید، به Child Routes و همچنین محل قرارگیری router-outlet ثانویه را نیز مشاهده می‌کنید:
<div class="panel panel-primary">
    <div class="panel-heading">
        {{pageTitle}}
    </div>

    <div class="panel-body" *ngIf="product">
        <div class="wizard">
            <a [routerLink]="['info']">
                Basic Information
            </a>
            <a [routerLink]="['tags']">
                Search Tags
            </a>
        </div>

        <router-outlet></router-outlet>
    </div>

    <div class="panel-footer">
        <div class="row">
            <div class="col-md-6 col-md-offset-2">
                <span>
                    <button class="btn btn-primary"
                            type="button"
                            style="width:80px;margin-right:10px"
                            [disabled]="!isValid()"
                            (click)="saveProduct()">
                        Save
                    </button>
                </span>
                <span>
                    <a class="btn btn-default"
                        [routerLink]="['/products']">
                        Cancel
                    </a>
                </span>
                <span>
                    <a class="btn btn-default"
                        (click)="deleteProduct()">
                        Delete
                    </a>
                </span>
            </div>
        </div>
    </div>

    <div class="has-error" *ngIf="errorMessage">{{errorMessage}}</div>
</div>
تا اینجا اگر برنامه را توسط دستور ng s -o اجرا کنید، صفحه‌ی ویرایش محصول اول، چنین شکلی را پیدا کرده‌است:




فعالسازی Child Routes

دو روش برای فعالسازی Child Routes وجود دارند:
الف) با ذکر مسیر مطلق
 <a [routerLink]="['/products',product.id,'edit','info']">Info</a>
در این حالت تمام URL segments این مسیر باید به عنوان پارامترهای لینک قید شوند.

ب) با ذکر مسیر نسبی
 <a [routerLink]="['info']">Info</a>
این مسیر از URL segment جاری شروع می‌شود و نباید در حین تعریف آن از / استفاده کرد. اگر از / استفاده شود، معنای ذکر مسیری مطلق را می‌دهد.
در این حالت اگر تنظیمات والد این مسیریابی تغییر کنند، نیازی به تغییر مسیر نسبی تعریف شده نیست (برخلاف حالت مطلق که بر اساس قید کامل تمام اجزای مسیریابی والد آن کار می‌کند).

دقیقا همین پارامترها، قابلیت استفاده‌ی در متد this.route.navigate را نیز دارند:
الف) برای حالت ذکر مسیر مطلق:
 this.router.navigate(['/products', this.product.id,'edit','info']);
ب) و برای حالت ذکر مسیر نسبی:
 this.router.navigate(['info', { relativeTo: this.route }]);
در حالت ذکر مسیر نسبی، نیاز است پارامتر اضافه‌ی دیگری را جهت مشخص سازی مسیریابی والد نیز قید کرد.


تکمیل Child Viewهای برنامه

تا اینجا لینک‌هایی نسبی را به مسیریابی‌های info و tags اضافه کردیم. در ادامه قالب‌ها و کامپوننت‌های آن‌ها را تکمیل می‌کنیم:
الف) تکمیل کامپوننت ProductEditInfoComponent در فایل src\app\product\product-edit\product-edit.component.ts
import { ActivatedRoute } from '@angular/router';
import { NgForm } from '@angular/forms';
import { Component, OnInit, ViewChild } from '@angular/core';

import { IProduct } from './../iproduct';

@Component({
  //selector: 'app-product-edit-info',
  templateUrl: './product-edit-info.component.html',
  styleUrls: ['./product-edit-info.component.css']
})
export class ProductEditInfoComponent implements OnInit {

  @ViewChild(NgForm) productForm: NgForm;

  errorMessage: string;
  product: IProduct;

  constructor(private route: ActivatedRoute) { }

  ngOnInit(): void {
    this.route.parent.data.subscribe(data => {
      this.product = data['product'];

      if (this.productForm) {
        this.productForm.reset();
      }
    });
  }
}
با قالب src\app\product\product-edit\product-edit.component.html که در حقیقت همان فرمی است که از کامپوننت والد حذف کردیم و به اینجا منتقل شده‌است:
<div class="panel-body">
    <form class="form-horizontal"
          novalidate
          #productForm="ngForm">
        <fieldset>
            <legend>Basic Product Information</legend>
            <div class="form-group" 
                    [ngClass]="{'has-error': (productNameVar.touched || 
                                              productNameVar.dirty || product.id !== 0) && 
                                              !productNameVar.valid }">
                <label class="col-md-2 control-label" 
                        for="productNameId">Product Name</label>

                <div class="col-md-8">
                    <input class="form-control" 
                            id="productNameId" 
                            type="text" 
                            placeholder="Name (required)"
                            required
                            minlength="3"
                            [(ngModel)] = product.productName
                            name="productName"
                            #productNameVar="ngModel" />
                    <span class="help-block" *ngIf="(productNameVar.touched ||
                                                     productNameVar.dirty || product.id !== 0) &&
                                                     productNameVar.errors">
                        <span *ngIf="productNameVar.errors.required">
                            Product name is required.
                        </span>
                        <span *ngIf="productNameVar.errors.minlength">
                            Product name must be at least three characters.
                        </span>
                    </span>
                </div>
            </div>
            
            <div class="form-group" 
                    [ngClass]="{'has-error': (productCodeVar.touched || 
                                              productCodeVar.dirty || product.id !== 0) && 
                                              !productCodeVar.valid }">
                <label class="col-md-2 control-label" for="productCodeId">Product Code</label>

                <div class="col-md-8">
                    <input class="form-control" 
                            id="productCodeId" 
                            type="text" 
                            placeholder="Code (required)"
                            required
                            [(ngModel)] = product.productCode
                            name="productCode"
                            #productCodeVar="ngModel" />
                    <span class="help-block" *ngIf="(productCodeVar.touched ||
                                                     productCodeVar.dirty || product.id !== 0) &&
                                                     productCodeVar.errors">
                        <span *ngIf="productCodeVar.errors.required">
                            Product code is required.
                        </span>
                    </span>
                </div>
            </div>           

            <div class="has-error" *ngIf="errorMessage">{{errorMessage}}</div>
        </fieldset>
    </form>
</div>


ب) تکمیل کامپوننت ProductEditTagsComponent در فایل src\app\product\product-edit-tags\product-edit-tags.component.ts
import { ActivatedRoute } from '@angular/router';
import { IProduct } from './../iproduct';
import { Component, OnInit } from '@angular/core';

@Component({
  //selector: 'app-product-edit-tags',
  templateUrl: './product-edit-tags.component.html',
  styleUrls: ['./product-edit-tags.component.css']
})
export class ProductEditTagsComponent implements OnInit {

  errorMessage: string;
  newTags = '';
  product: IProduct;

  constructor(private route: ActivatedRoute) { }

  ngOnInit(): void {
    this.route.parent.data.subscribe(data => {
      this.product = data['product'];
    });
  }

  // Add the defined tags
  addTags(): void {
    let tagArray = this.newTags.split(',');
    this.product.tags = this.product.tags ? this.product.tags.concat(tagArray) : tagArray;
    this.newTags = '';
  }

  // Remove the tag from the array of tags.
  removeTag(idx: number): void {
    this.product.tags.splice(idx, 1);
  }
}
با قالب src\app\product\product-edit-tags\product-edit-tags.component.html
<div class="panel-body">
    <form class="form-horizontal"
          novalidate>
        <fieldset>
            <legend>Product Search Tags</legend>
            <div class="form-group" 
                    [ngClass]="{'has-error': (categoryVar.touched || 
                                              categoryVar.dirty || product.id !== 0) && 
                                              !categoryVar.valid }">
                <label class="col-md-2 control-label" for="categoryId">Category</label>
                <div class="col-md-8">
                    <input class="form-control" 
                           id="categoryId" 
                           type="text"
                           placeholder="Category (required)"
                           required
                           minlength="3"
                           [(ngModel)]="product.category"
                           name="category"
                           #categoryVar="ngModel" />
                    <span class="help-block" *ngIf="(categoryVar.touched ||
                                                     categoryVar.dirty || product.id !== 0) &&
                                                     categoryVar.errors">
                        <span *ngIf="categoryVar.errors.required">
                            A category must be entered.
                        </span>
                        <span *ngIf="categoryVar.errors.minlength">
                            The category must be at least 3 characters in length.
                        </span>
                    </span>
                </div>
            </div>

            <div class="form-group" 
                    [ngClass]="{'has-error': (tagVar.touched || 
                                              tagVar.dirty || product.id !== 0) && 
                                              !tagVar.valid }">
                <label class="col-md-2 control-label" for="tagsId">Search Tags</label>
                <div class="col-md-8">
                    <input class="form-control" 
                           id="tagsId" 
                           type="text"
                           placeholder="Search keywords separated by commas"
                           minlength="3"
                           [(ngModel)]="newTags"
                           name="tags"
                           #tagVar="ngModel" />
                    <span class="help-block" *ngIf="(tagVar.touched ||
                                                     tagVar.dirty || product.id !== 0) &&
                                                     tagVar.errors">
                        <span *ngIf="tagVar.errors.minlength">
                            The search tag must be at least 3 characters in length.
                        </span>
                    </span>
                </div>
                <div class="col-md-1">
                    <button type="button"
                            class="btn btn-default"
                            (click)="addTags()">
                        Add
                    </button>
                </div>
            </div>
            <div class="row col-md-8 col-md-offset-2">
                <span *ngFor="let tag of product.tags; let i = index">
                    <button class="btn btn-default"
                            style="font-size:smaller;margin-bottom:12px"
                            (click)="removeTag(i)">
                        {{tag}}
                        <span class="glyphicon glyphicon-remove"></span>
                    </button>
                </span>
            </div>
            <div class="has-error" *ngIf="errorMessage">{{errorMessage}}</div>
        </fieldset>
    </form>
</div>



دریافت اطلاعات جهت Child Routes

روش‌های متعددی برای دریافت اطلاعات جهت Child Routes وجود دارند:
الف) می‌توان از متد this.productService.getProduct جهت دریافت اطلاعات یک محصول استفاده کرد. اما همانطور که در قسمت قبل نیز بررسی کردیم، این روش سبب نمایش ابتدایی یک قالب خالی و پس از مدتی، نمایش اطلاعات آن می‌شود.
ب) می‌توان توسط this.route.snapshot.data['product'] اطلاعات را از Route Resolver، پس از پیش واکشی آن‌ها از وب سرور، دریافت کرد.
ج) اگر قسمت‌های مختلف Child Routes قرار است با اطلاعاتی یکسان کار کنند که قرار است بین برگه‌های مختلف آن به اشتراک گذاشته شوند، این اطلاعات را می‌توانند از Route Resolver والد خود به کمک this.route.snapshot.data['product'] دریافت کنند.
در این مثال ما هرچند چندین برگه‌ی مختلف را طراحی کرده‌ایم، اما اطلاعات نمایش داده شده‌ی توسط آن‌ها متعلق به یک شیء محصول می‌باشند. بنابراین نیاز است بتوان این اطلاعات را بین کامپوننت‌های مختلف این Child Routes به اشتراک گذاشت و تنها با یک وهله‌ی آن کار کرد. به همین جهت با this.route.parent در هر یک از Child Components تعریف شده کار می‌کنیم تا بتوان به یک وهله‌ی شیء محصول، دسترسی یافت.
د) همچنین می‌توان از روش this.route.parent.data.subscribe نیز استفاده کرد. البته در اینجا چون صفحه‌ی افزودن محصولات با صفحه‌ی ویرایش محصولات، دارای root URL Segment یکسانی است، نیاز است از این روش استفاده کرد تا بتوان از تغییرات بعدی پارامتر id آن مطلع شد. این مورد روشی است که در کدهای ProductEditInfoComponent مشاهده می‌کنید.
ngOnInit(): void {
    this.route.parent.data.subscribe(data => {
      this.product = data['product'];

      if (this.productForm) {
        this.productForm.reset();
      }
    });
  }
در اینجا data['product'] به key/value تعریف شده‌ی resolve: { product: ProductResolverService } در تنظیمات مسیریابی اشاره می‌کند که آن‌را در قسمت قبل تکمیل کردیم.
شبیه به همین روش را در ProductEditTagsComponent نیز بکار گرفته‌ایم و در آنجا نیز با شیء  this.route.parent و دسترسی به اطلاعات دریافتی از Route Resolver، کار می‌کنیم. به این ترتیب مطمئن خواهیم شد که  this.product این دو کامپوننت مختلف، هر دو به یک وهله از شیء product دریافتی از سرور، اشاره می‌کنند.
به این ترتیب دکمه‌ی Save ذیل هر دو برگه، به درستی عمل کرده و می‌تواند اطلاعات نهایی یک شیء محصول را ذخیره کند.


رفع مشکلات اعتبارسنجی فرم‌های قرار گرفته‌ی در برگه‌های مختلف

علت استفاده‌ی از ViewChild در ProductEditInfoComponent
 @ViewChild(NgForm) productForm: NgForm;
که به فرم قالب آن اشاره می‌کند:
<form class="form-horizontal" novalidate
#productForm="ngForm">
این است که بتوان متد this.productForm.reset آن‌را پس از هربار دریافت اطلاعات از سرور، فراخوانی کرد. این متد نه تنها اطلاعات آن‌را پاک می‌کند، بلکه خطاهای اعتبارسنجی آن‌را نیز به حالت نخست برمی‌گرداند. بنابراین در این حالت اگر سبب بروز یک خطای اعتبارسنجی، در فرم ویرایش اطلاعات شویم و در همان لحظه صفحه‌ی افزودن یک محصول جدید را درخواست کنیم، کاربر همان خطای اعتبارسنجی قبلی را مجددا مشاهده نکرده و یک فرم از ابتدا آغاز شده را مشاهده می‌کند.
انجام اینکار برای برگه‌‌های دوم به بعد ضروری نیست. از این جهت که با اولین بار نمایش این صفحه، تمام آن‌ها از حافظه خارج می‌شوند و مجددا بازیابی خواهند شد.

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

در اولین بار نمایش Child Routes، کامپوننت ویرایش اطلاعات در router-outlet آن نمایش داده می‌شود. در این حالت اگر کاربر بر روی لینک نمایش کامپوننت edit tags کلیک کند، قالب کامپوننت edit info به طور کامل از router-outlet حذف می‌شود و با قالب کامپوننت edit tags جایگزین می‌شود. این فرآیند به این معنا است که فرم edit info به همراه تمام اطلاعات اعتبارسنجی آن unload می‌شوند. به همین ترتیب زمانیکه کاربر درخواست نمایش برگه‌ی ویرایش اطلاعات را می‌کند، قالب edit tags و اطلاعات اعتبارسنجی آن unload می‌شوند. به این معنا که در یک router-outlet در هر زمان تنها یک فرم، به همراه اطلاعات اعتبارسنجی آن در دسترس هستند.
راه حل‌‌های ممکن:
الف) بدنه‌ی اصلی فرم را در کامپوننت والد قرار دهیم و سپس هر کدام از فرزندان، المان‌های فرم‌های مرتبط را ارائه دهند. این روش کار نمی‌کند چون Angular المان‌های فرم‌های قرار گرفته‌ی درون router-outlet را شناسایی نمیکند.
ب) قرار دادن فرم‌ها، به صورت مجزا در هر کامپوننت فرزند (مانند روش فعلی) و سپس اعتبارسنجی دستی در کامپوننت والد.
تغییرات مورد نیاز کامپوننت ProductEditComponent را جهت افزودن اعتبارسنجی فرم‌های فرزند آن‌را در اینجا ملاحظه می‌کنید:
export class ProductEditComponent implements OnInit {
  private dataIsValid: { [key: string]: boolean } = {};

  isValid(path: string): boolean {
    this.validate();
    if (path) {
      return this.dataIsValid[path];
    }
    return (this.dataIsValid &&
      Object.keys(this.dataIsValid).every(d => this.dataIsValid[d] === true));
  }

  saveProduct(): void {
    if (this.isValid(null)) {
      this.productService.saveProduct(this.product)
        .subscribe(
        () => this.onSaveComplete(`${this.product.productName} was saved`),
        (error: any) => this.errorMessage = <any>error
        );
    } else {
      this.errorMessage = 'Please correct the validation errors.';
    }
  }

  validate(): void {
    // Clear the validation object
    this.dataIsValid = {};

    // 'info' tab
    if (this.product.productName &&
      this.product.productName.length >= 3 &&
      this.product.productCode) {
      this.dataIsValid['info'] = true;
    } else {
      this.dataIsValid['info'] = false;
    }

    // 'tags' tab
    if (this.product.category &&
      this.product.category.length >= 3) {
      this.dataIsValid['tags'] = true;
    } else {
      this.dataIsValid['tags'] = false;
    }
  }
}
 - در اینجا dataIsValid، به صورت key/value تعریف شده‌است که در آن key، مسیر یک برگه و مقدار آن، معتبر بودن یا غیرمعتبر بودن وضعیت اعتبارسنجی آن است.
 - سپس متد validate اضافه شده‌است تا کار اعتبارسنجی را انجام دهد. در اینجا از خود شیء this.product که بین دو برگه به اشتراک گذاشته شده‌است برای انجام اعتبارسنجی استفاده می‌کنیم. از این جهت که برگه‌ها نیز با استفاده از  this.route.parent.data، دقیقا به همین وهله دسترسی دارند. بنابراین هرتغییری که در برگه‌ها بر روی این وهله اعمال شود، به کامپوننت والد نیز منعکس می‌شود.
 - متد isValid، مسیر هر برگه را دریافت می‌کند و سپس به متغیر dataIsValid مراجعه کرده و وضعیت آن برگه را باز می‌گرداند. اگر path در اینجا قید نشود، وضعیت تمام برگه‌ها بررسی می‌شوند؛ مانند if (this.isValid(null)) در متد ذخیره سازی اطلاعات.
 - در آخر در فایل product-edit.component.html، وضعیت فعال و غیرفعال دکمه‌ی ثبت را نیز به این متد متصل می‌کنیم:
<button class="btn btn-primary"
                            type="button"
                            style="width:80px;margin-right:10px"
                            [disabled]="!isValid()"
                            (click)="saveProduct()">
      Save
</button>


کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: angular-routing-lab-04.zip
برای اجرای آن فرض بر این است که پیشتر Angular CLI را نصب کرده‌اید. سپس از طریق خط فرمان به ریشه‌ی پروژه وارد شده و دستور npm install را صادر کنید تا وابستگی‌های آن دریافت و نصب شوند. در آخر با اجرای دستور ng s -o برنامه ساخته شده و در مرورگر پیش فرض سیستم نمایش داده خواهد شد.
مطالب
معرفی ASP.NET Identity

سیستم ASP.NET Membership بهمراه ASP.NET 2.0 در سال 2005 معرفی شد، و از آن زمان تا بحال تغییرات زیادی در چگونگی مدیریت احزار هویت و اختیارات کاربران توسط اپلیکیشن‌های وب بوجود آمده است. ASP.NET Identity نگاهی تازه است به آنچه که سیستم Membership هنگام تولید اپلیکیشن‌های مدرن برای وب، موبایل و تبلت باید باشد.

پیش زمینه: سیستم عضویت در ASP.NET


ASP.NET Membership

ASP.NET Membership طراحی شده بود تا نیازهای سیستم عضویت وب سایت‌ها را تامین کند، نیازهایی که در سال 2005 رایج بود و شامل مواردی مانند مدل احراز هویت فرم، و یک پایگاه داده SQL Server برای ذخیره اطلاعات کاربران و پروفایل هایشان می‌شد. امروزه گزینه‌های بسیار بیشتری برای ذخیره داده‌های وب اپلیکیشن‌ها وجود دارد، و اکثر توسعه دهندگان می‌خواهند از اطلاعات شبکه‌های اجتماعی نیز برای احراز هویت و تعیین سطوح دسترسی کاربرانشان استفاده کنند. محدودیت‌های طراحی سیستم ASP.NET Membership گذر از این تحول را دشوار می‌کند:
  • الگوی پایگاه داده آن برای SQL Server طراحی شده است، و قادر به تغییرش هم نیستید. می‌توانید اطلاعات پروفایل را اضافه کنید، اما تمام داده‌ها در یک جدول دیگر ذخیره می‌شوند، که دسترسی به آنها نیز مشکل‌تر است، تنها راه دسترسی Profile Provider API خواهد بود.
  • سیستم تامین کننده (Provider System) امکان تغییر منبع داده‌ها را به شما می‌دهد، مثلا می‌توانید از بانک‌های اطلاعاتی MySQL یا Oracle استفاده کنید. اما تمام سیستم بر اساس پیش فرض هایی طراحی شده است که تنها برای بانک‌های اطلاعاتی relational درست هستند. می‌توانید تامین کننده (Provider) ای بنویسید که داده‌های سیستم عضویت را در منبعی به غیر از دیتابیس‌های relational ذخیره می‌کند؛ مثلا Windows Azure Storage Tables. اما در این صورت باید مقادیر زیادی کد بنویسید. مقادیر زیادی هم  System.NotImplementedException باید بنویسید، برای متد هایی که به دیتابیس‌های NoSQL مربوط نیستند.
  • از آنجایی که سیستم ورود/خروج سایت بر اساس مدل Forms Authentication کار می‌کند، سیستم عضویت نمی‌تواند از OWIN استفاده کند. OWIN شامل کامپوننت هایی برای احراز هویت است که شامل سرویس‌های خارجی هم می‌شود (مانند Microsoft Accounts, Facebook, Google, Twitter). همچنین امکان ورود به سیستم توسط حساب‌های کاربری سازمانی (Organizational Accounts) نیز وجود دارد مانند Active Directory و Windows Azure Active Directory. این کتابخانه از OAuth 2.0، JWT و CORS نیز پشتیبانی می‌کند.

ASP.NET Simple Membership

ASP.NET simple membership به عنوان یک سیستم عضویت، برای فریم ورک Web Pages توسعه داده شد. این سیستم با WebMatrix و Visual Studio 2010 SP1 انتشار یافت. هدف از توسعه این سیستم، آسان کردن پروسه افزودن سیستم عضویت به یک اپلیکیشن Web Pages بود.
این سیستم پروسه کلی کار را آسان‌تر کرد، اما هنوز مشکلات ASP.NET Membership را نیز داشت. محدودیت هایی نیز وجود دارند:
  • ذخیره داده‌های سیستم عضویت در بانک‌های اطلاعاتی non-relational مشکل است.
  • نمی توانید از آن در کنار OWIN استفاده کنید.
  • با فراهم کننده‌های موجود ASP.NET Membership بخوبی کار نمی‌کند. توسعه پذیر هم نیست.

ASP.NET Universal Providers   

ASP.NET Universal Providers برای ذخیره سازی اطلاعات سیستم عضویت در Windows Azure SQL Database توسعه پیدا کردند. با SQL Server Compact هم بخوبی کار می‌کنند. این تامین کننده‌ها بر اساس Entity Framework Code First ساخته شده بودند و بدین معنا بود که داده‌های سیستم عضویت را می‌توان در هر منبع داده ای که توسط EF پشتیبانی می‌شود ذخیره کرد. با انتشار این تامین کننده‌ها الگوی دیتابیس سیستم عضویت نیز بسیار سبک‌تر و بهتر شد. اما این سیستم بر پایه زیر ساخت ASP.NET Membership نوشته شده است، بنابراین محدودیت‌های پیشین مانند محدودیت‌های SqlMembershipProvider هنوز وجود دارند. به بیان دیگر، این سیستم‌ها همچنان برای بانک‌های اطلاعاتی relational طراحی شده اند، پس سفارشی سازی اطلاعات کاربران و پروفایل‌ها هنوز مشکل است. در آخر آنکه این تامین کننده‌ها هنوز از مدل احراز هویت فرم استفاده می‌کنند.


ASP.NET Identity

همانطور که داستان سیستم عضویت ASP.NET طی سالیان تغییر و رشد کرده است، تیم ASP.NET نیز آموخته‌های زیادی از بازخورد‌های مشتریان شان بدست آورده اند.
این پیش فرض که کاربران شما توسط یک نام کاربری و کلمه عبور که در اپلیکیشن خودتان هم ثبت شده است به سایت وارد خواهند شد، دیگر معتبر نیست. دنیای وب اجتماعی شده است. کاربران از طریق وب سایت‌ها و شبکه‌های اجتماعی متعددی با یکدیگر در تماس هستند، خیلی از اوقت بصورت زنده! شبکه هایی مانند Facebook و Twitter.
همانطور که توسعه نرم افزار‌های تحت وب رشد کرده است، الگو‌ها و مدل‌های پیاده سازی نیز تغییر و رشد کرده اند. امکان Unit Testing روی کد اپلیکیشن‌ها، یکی از مهم‌ترین دلواپسی‌های توسعه دهندگان شده است. در سال 2008 تیم ASP.NET فریم ورک جدیدی را بر اساس الگوی (Model-View-Controller (MVC اضافه کردند. هدف آن کمک به توسعه دهندگان، برای تولید برنامه‌های ASP.NET با قابلیت Unit Testing بهتر بود. توسعه دهندگانی که می‌خواستند کد اپلیکیشن‌های خود را Unit Test کنند، همین امکان را برای سیستم عضویت نیز می‌خواستند.

با در نظر گرفتن تغییراتی که در توسعه اپلیکیشن‌های وب بوجود آمده ASP.NET Identity با اهداف زیر متولد شد:
  • یک سیستم هویت واحد (One ASP.NET Identity system)
    • سیستم ASP.NET Identity می‌تواند در تمام فریم ورک‌های مشتق از ASP.NET استفاده شود. مانند ASP.NET MVC, Web Forms, Web Pages, Web API و SignalR 
    • از این سیستم می‌توانید در تولید اپلیکیشن‌های وب، موبایل، استور (Store) و یا اپلیکیشن‌های ترکیبی استفاده کنید. 
  • سادگی تزریق داده‌های پروفایل درباره کاربران
    • روی الگوی دیتابیس برای اطلاعات کاربران و پروفایل‌ها کنترل کامل دارید. مثلا می‌توانید به سادگی یک فیلد، برای تاریخ تولد در نظر بگیرید که کاربران هنگام ثبت نام در سایت باید آن را وارد کنند.
  • کنترل ذخیره سازی/واکشی اطلاعات 
    • بصورت پیش فرض ASP.NET Identity تمام اطلاعات کاربران را در یک دیتابیس ذخیره می‌کند. تمام مکانیزم‌های دسترسی به داده‌ها توسط EF Code First کار می‌کنند.
    • از آنجا که روی الگوی دیتابیس، کنترل کامل دارید، تغییر نام جداول و یا نوع داده فیلد‌های کلیدی و غیره ساده است.
    • استفاده از مکانیزم‌های دیگر برای مدیریت داده‌های آن ساده است، مانند SharePoint, Windows Azure Storage Table و دیتابیس‌های NoSQL.
  • تست پذیری
    • ASP.NET Identity تست پذیری اپلیکیشن وب شما را بیشتر می‌کند. می‌توانید برای تمام قسمت هایی که از ASP.NET Identity استفاده می‌کنند تست بنویسید.
  • تامین کننده نقش (Role Provider)
    • تامین کننده ای وجود دارد که به شما امکان محدود کردن سطوح دسترسی بر اساس نقوش را می‌دهد. بسادگی می‌توانید نقش‌های جدید مانند "Admin" بسازید و بخش‌های مختلف اپلیکیشن خود را محدود کنید.
  • Claims Based
    • ASP.NET Identity از امکان احراز هویت بر اساس Claims نیز پشتیبانی می‌کند. در این مدل، هویت کاربر بر اساس دسته ای از اختیارات او شناسایی می‌شود. با استفاده از این روش توسعه دهندگان برای تعریف هویت کاربران، آزادی عمل بیشتری نسبت به مدل Roles دارند. مدل نقش‌ها تنها یک مقدار منطقی (bool) است؛ یا عضو یک نقش هستید یا خیر، در حالیکه با استفاده از روش Claims می‌توانید اطلاعات بسیار ریز و دقیقی از هویت کاربر در دست داشته باشید.
  • تامین کنندگان اجتماعی
    • به راحتی می‌توانید از تامین کنندگان دیگری مانند Microsoft, Facebook, Twitter, Google و غیره استفاده کنید و اطلاعات مربوط به کاربران را در اپلیکیشن خود ذخیره کنید.
  • Windows Azure Active Directory
    • برای اطلاعات بیشتر به این لینک مراجعه کنید.
  • یکپارچگی با OWIN
    • ASP.NET Identity بر اساس OWIN توسعه پیدا کرده است، بنابراین از هر میزبانی که از OWIN پشتیبانی می‌کند می‌توانید استفاده کنید. همچنین هیچ وابستگی ای به System.Web وجود ندارد. ASP.NET Identity یک فریم ورک کامل و مستقل برای OWIN است و می‌تواند در هر اپلیکیشنی که روی OWIN میزبانی شده استفاده شود.
    • ASP.NET Identity از OWIN برای ورود/خروج کاربران در سایت استفاده می‌کند. این بدین معنا است که بجای استفاده از Forms Authentication برای تولید یک کوکی، از OWIN CookieAuthentication استفاده می‌شود.
  • پکیج NuGet
    • ASP.NET Identity در قالب یک بسته NuGet توزیع می‌شود. این بسته در قالب پروژه‌های ASP.NET MVC, Web Forms و Web API که با Visual Studio 2013 منتشر شدند گنجانده شده است.
    • توزیع این فریم ورک در قالب یک بسته NuGet این امکان را به تیم ASP.NET می‌دهد تا امکانات جدیدی توسعه دهند، باگ‌ها را برطرف کنند و نتیجه را بصورت چابک به توسعه دهندگان عرضه کنند.

شروع کار با ASP.NET Identity

ASP.NET Identity در قالب پروژه‌های ASP.NET MVC, Web Forms, Web API و SPA که بهمراه Visual Studio 2013 منتشر شده اند استفاده می‌شود. در ادامه به اختصار خواهیم دید که چگونه ASP.NET Identity کار می‌کند.
  1. یک پروژه جدید ASP.NET MVC با تنظیمات Individual User Accounts بسازید.

  2. پروژه ایجاد شده شامل سه بسته می‌شود که مربوط به ASP.NET Identity هستند: 

  • Microsoft.AspNet.Identity.EntityFramework این بسته شامل پیاده سازی ASP.NET Identity با Entity Framework می‌شود، که تمام داده‌های مربوطه را در یک دیتابیس SQL Server ذخیره می‌کند.
  • Microsoft.AspNet.Identity.Core این بسته محتوی تمام interface‌‌های ASP.NET Identity است. با استفاده از این بسته می‌توانید پیاده سازی دیگری از ASP.NET Identity بسازید که منبع داده متفاوتی را هدف قرار می‌دهد. مثلا Windows Azure Storage Table و دیتابیس‌های NoSQL.
  • Microsoft.AspNet.Identity.OWIN این بسته امکان استفاده از احراز هویت OWIN را در اپلیکیشن‌های ASP.NET فراهم می‌کند. هنگام تولید کوکی‌ها از OWIN Cookie Authentication استفاده خواهد شد.
اپلیکیشن را اجرا کرده و روی لینک Register کلیک کنید تا یک حساب کاربری جدید ایجاد کنید.

هنگامیکه بر روی دکمه‌ی Register کلیک شود، کنترلر Account، اکشن متد Register را فراخوانی می‌کند تا حساب کاربری جدیدی با استفاده از ASP.NET Identity API ساخته شود.

[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Register(RegisterViewModel model)
{
    if (ModelState.IsValid)
    {
        var user = new ApplicationUser() { UserName = model.UserName };
        var result = await UserManager.CreateAsync(user, model.Password);
        if (result.Succeeded)
        {
            await SignInAsync(user, isPersistent: false);
            return RedirectToAction("Index", "Home");
        }
        else
        {
            AddErrors(result);
        }
    }

    // If we got this far, something failed, redisplay form
    return View(model);
}

اگر حساب کاربری با موفقیت ایجاد شود، کاربر توسط فراخوانی متد SignInAsync به سایت وارد می‌شود.

[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Register(RegisterViewModel model)
{
    if (ModelState.IsValid)
    {
        var user = new ApplicationUser() { UserName = model.UserName };
        var result = await UserManager.CreateAsync(user, model.Password);
        if (result.Succeeded)
        {
            await SignInAsync(user, isPersistent: false);
            return RedirectToAction("Index", "Home");
        }
        else
        {
            AddErrors(result);
        }
    }

    // If we got this far, something failed, redisplay form
    return View(model);
}
private async Task SignInAsync(ApplicationUser user, bool isPersistent)
{
    AuthenticationManager.SignOut(DefaultAuthenticationTypes.ExternalCookie);

    var identity = await UserManager.CreateIdentityAsync(
       user, DefaultAuthenticationTypes.ApplicationCookie);

    AuthenticationManager.SignIn(
       new AuthenticationProperties() { 
          IsPersistent = isPersistent 
       }, identity);
}

از آنجا که ASP.NET Identity و OWIN Cookie Authentication هر دو Claims-based هستند، فریم ورک، انتظار آبجکتی از نوع ClaimsIdentity را خواهد داشت. این آبجکت تمامی اطلاعات لازم برای تشخیص هویت کاربر را در بر دارد. مثلا اینکه کاربر مورد نظر به چه نقش هایی تعلق دارد؟ و اطلاعاتی از این قبیل. در این مرحله می‌توانید Claim‌‌های بیشتری را به کاربر بیافزایید.

کلیک کردن روی لینک Log off در سایت، اکشن متد LogOff در کنترلر Account را اجرا می‌کند.

// POST: /Account/LogOff
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult LogOff()
{
    AuthenticationManager.SignOut();
    return RedirectToAction("Index", "Home");
}

همانطور که مشاهده می‌کنید برای ورود/خروج کاربران از AuthenticationManager استفاده می‌شود که متعلق به OWIN است. متد SignOut همتای متد FormsAuthentication.SignOut است.


کامپوننت‌های ASP.NET Identity

تصویر زیر اجزای تشکیل دهنده ASP.NET Identity را نمایش می‌دهد. بسته هایی که با رنگ سبز نشان داده شده اند سیستم کلی ASP.NET Identity را می‌سازند. مابقی بسته‌ها وابستگی هایی هستند که برای استفاده از ASP.NET Identity در اپلیکیشن‌های ASP.NET لازم اند.

دو پکیج دیگر نیز وجود دارند که به آنها اشاره نشد:

  • Microsoft.Security.Owin.Cookies این بسته امکان استفاده از مدل احراز هویت مبتنی بر کوکی (Cookie-based Authentication) را فراهم می‌کند. مدلی مانند سیستم ASP.NET Forms Authentication.
  • EntityFramework که نیازی به معرفی ندارد.


مهاجرت از Membership به ASP.NET Identity

تیم ASP.NET و مایکروسافت هنوز راهنمایی رسمی، برای این مقوله ارائه نکرده اند. گرچه پست‌های وبلاگ‌ها و منابع مختلفی وجود دارند که از جنبه‌های مختلفی به این مقوله پرداخته اند. امیدواریم تا در آینده نزدیک مایکروسافت راهنمایی‌های لازم را منتشر کند، ممکن است ابزار و افزونه هایی نیز توسعه پیدا کنند. اما در یک نگاه کلی می‌توان گفت مهاجرت بین این دو فریم ورک زیاد ساده نیست. تفاوت‌های فنی و ساختاری زیادی وجود دارند، مثلا الگوی دیتابیس‌ها برای ذخیره اطلاعات کاربران، مبتنی بودن بر فریم ورک OWIN و غیره. اگر قصد اجرای پروژه جدیدی را دارید پیشنهاد می‌کنم از فریم ورک جدید مایکروسافت ASP.NET Identity استفاده کنید.


قدم‌های بعدی

در این مقاله خواهید دید چگونه اطلاعات پروفایل را اضافه کنید و چطور از ASP.NET Identity برای احراز هویت کاربران توسط Facebook و Google استفاده کنید.
پروژه نمونه ASP.NET Identity می‌تواند مفید باشد. در این پروژه نحوه کارکردن با کاربران و نقش‌ها و همچنین نیازهای مدیریتی رایج نمایش داده شده. 
مطالب
مقدمه‌ای بر Docker
Docker به صورت ساده، پلتفرمی است که به سادگی قابلیت ساخت، انتقال و اجرا کردن Image‌ها را در اختیار دارد و همچنین به صورت native درون سرور‌های لینوکسی و ویندوزی اجرا میشود؛ به علاوه اینکه در محیط محلی، برای تست نیز بر روی ماشین‌های ویندوزی و مک از طریق virtual machine قابل اجراست.

دو مفهوم اساسی در محیط Docker وجود دارند که دانستن آن‌ها ضروری است: Image و Container
image عملا چیزی است که از آن برای Build یک Container استفاده می‌شود. image دارای یک سری فایل‌های لازم و اساسی است که باعث می‌شود بر روی یک Operation System اجرا شود؛ مثل Ubuntu یا Windows. بنابراین شما Application Framework خود را خواهید داشت و همچنین Databaseی که با آن کار میکند. بنابراین قابلیت استفاده از زبان‌ها و فریم ورک‌های مختلف چون Asp.net Core, Nodejs, Python و غیره را خواهد داشت. یک image به خودی خود غیر قابل استفاده است تا زمانیکه بر روی یک Container توزیع شده باشد، تا قابلیت اجرا پیدا کند. بنابراین نقطه‌ی شروع اصلی اجرایی یک برنامه با Container مربوط به آن میباشد.
به صورت خلاصه Image یک template از نوع Readonly است که ترکیبی از لایه‌های File System می‌باشد، به همراه فایل‌های share شده‌ی دیگر (از قبیل فریم ورک‌ها و ...) که میتوانند یک Docker Container Instance را تولید نمایند.
Container یک محیط امن و ایزوله است که به وسیله‌ی image ساخته شده است و میتواند اجرا، متوقف، منتقل و یا حذف شود (بطور قابل ملاحظه‌ای اجرا کردن و متوقف کردن آن سریع میباشد).


تفاوت Docker Containers و Virtual Machines

Virtual Machines همیشه بر روی Host Operation System اجرا میشوند (که می‌تواند بر روی ویندوز یا لینوکس باشد) و بعد از آن اجرای Guest OS بر روی سطحی به نام Hypervisor. پس میتوان گفت یک کپی کامل از سیستم عامل است که که بر روی hypervisor اجرا میشود و خودش نیز بر روی سخت افزار اجرا میشود. بنابراین میتوان مثل شکل زیر، یک App داشت که عملا یک سری باینری و کتابخانه است و اگر قرار باشد بر روی سیستم عامل‌های مختلفی کار کند، احتیاج به کپی کردن کل آن می‌باشد و بطور واضحی زمان و هزینه‌ی بیشتری برای بالا آوردن آن لازم است.
اما بر خلاف آن، داکر با استفاده از ابزاری به نام Docker Engine کار میکند که میتواند Container‌های مختلفی از OS‌های مختلف را اجرا نماید و نیازی به کپی گرفتن از کل سیستم عامل برای اجرای هر container نخواهد بود.


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


چطور Docker میتواند سریعتر از Virtual Machine‌ها عمل کند ؟

داکر از چیزی به نام Copy On Write استفاده میکند؛ به معنای کپی کردن همزمان با نوشتن. همانطور که گفته شد هر Container از یک Image ساخته میشود و عملا Imageها همان FileSystem‌های از قبل تولید شده هستند و هر کدام از لایه‌ای از کتابخانه‌ها استفاده میکنند که برای اجرای برنامه‌های کاربردی مورد استفاده قرار می‌گیرند. سرور آپاچی را در نظر بگیرید، به عنوان یک فایل image که FileSystem بر روی آن ذخیره شده‌است. با نصب Php یک لایه بر روی لایه دیگر ایجاد شده و فقط تغییرات جدید به آن اضافه خواهند شد و حال اگر بخواهید تغییری را بر روی source code خود بدهید، عملا فقط آن تغییر به Image و FileSystem اضافه خواهد شد. این معماری لایه لایه باعث تولید یک FileSystem بصورت read-only میشود که شامل لایه‌های متفاوتی است و سبب کم حجم شدن آن، بالا رفتن سرعت آن می‌شود و همچنین با استفاده از Caching، قدرت زیادی را بدان می‌بخشد.


پس همانطور که در شکل فوق مشاهده میکنید، هر image از لایه‌های مختلفی تشکیل شده است و توانایی به اشتراک گذاشتن این لایه‌های متمایز از یکدیگر در Container‌ها وجود دارد.


بنابراین طبق شکل فوق، بحث را اینگونه خلاصه میکنیم که هر Image از ترکیبی از لایه‌هایی از نوع read-only تشکیل شده است و با اضافه شدن Container، عملا یک لایه‌ی دیگری که قابلیت read/write را دارد بر روی آن اضافه میشود و درون آن source code میتواند قرار گیرد و اینکه بر مبنای شکل زیر میبینید که قابلیت به اشتراک گذاری Image layer‌ها به Container‌های مختلف تعبیه شده است که باعث میشود لایه‌ی نصب شده بر روی سیستم، بصورت اشتراکی قابل استفاده‌ی مجدد باشد و فضای دیسک کمتری، به علاوه سرعت اجرای بالاتری را داشته باشد. هر لایه یک مقدار هش شده‌ی یکتایی را در اختیار دارد تا از لایه‌های دیگر تمیز داده شود و قابل شناسایی باشد.




داکر در شبکه چگونه کار میکند؟

ضمنا نکته‌ی قابل توجه که در مقاله‌های بعدی به صورت عملی به آن میپردازیم این است که با استفاده از داکر میتوانیم وب سرورهایی را بر روی Container‌های مختلفی داشته باشیم که همگی بر روی پورت بطور مثال 80 هستند؛ طوری که درون هر Container بدلیل ایزوله بودن پروسس‌های مخصوص Container مربوط به خود، به پورت‌های باز داخل آن شبکه دسترسی دارند و میتوانند پورت در نظر گرفته شده‌ی درون Container را با پورت دیگری بیرون Container به اصطلاح Expose نمایند.
ضمن اینکه نکته‌ی دیگری که وجود دارد، ارتباط Container‌ها با یکدیگر است. برای مثال یک Container برای Database و دیگری برای WebApp میباشد که باید به همدیگر link شده تا قابل استفاده گردند و عملا نیازی به نوشتن ip یکدیگر در این حالت وجود ندارد. البته راه‌های دیگری از قبیل Compose کردن نیز وجود دارد که در ادامه بیشتر با آن‌ها آشنا خواهیم شد.


Docker Volume چیست؟

بحث دیگری که وجود دارد، Volumeها هستند که قسمتی از FileSystem‌ها میباشند و بصورت ساده، مثال کاربردی‌اش میتواند قسمتی از یک سیستم و دایرکتوری خاصی را بر روی Container خاصی Map کردن باشد و عملا داخل آن دایرکتوری میتواند source code بوده باشد (یکی از راه‌های ممکن برای map کردن source code به container) و بر روی Container ایجاد شود.
فوایدی که با استفاده از Volume‌ها میتوان به آن رسید از قبیل موارد زیر میباشند:
قابلیت به اشتراک گذاری یک Volume بین Container‌های مختلف که به شدت میتواند قابل استفاده باشد.
Data Volume‌ها ماندگار هستند. یعنی حتی بعد از اینکه Container مربوطه را حذف نمایید، volume مربوط به آن بطور اتوماتیک حذف نمیشود (مگر اینکه خودتان دستور حذف کردن آن را وارد نمایید). پس عملا قابلیت استفاده‌ی مجدد را نیز خواهد داشت.

طبق شکل فوق ما میتوانیم درون یک container یک volume داشته باشیم. وقتی ما چیزی را درون آن مینویسیم عملا داریم در قسمت خاصی به نام Docker Host عمل write کردن را انجام میدهیم که باعث میشود داکر متوجه آن شود. وقتی اسمی را به یک Volume انتساب میدهیم همانند /var/www، در واقع یک اسم مستعار (alias) میباشد که اشاره میکند به این Docker host موجود. در ادامه بیشتر با Volume‌ها آشنا خواهیم شد. 


DockerFile و ساخت image‌ها چگونه است؟

روش دیگر برای اجرای source code در داکر، ساخت یک image اختصاصی از آن و اجرا کردن آن بر روی یک container مجزا است.  با استفاده از DockerFile میتوانید image‌های خود را build کرده که عملا هر image در آخر باید به یک سیستم عامل برسد و همانطور که گفته شد به صورت لایه‌ای کار میکنند و مراتب اجرای آن از قبیل working directory و expose کردن بر روی پورتی خاص، همچنین استفاده از Environment Variable‌ها میباشد و همچنین با استفاده از DockerHub (که نسخه‌ی enterprise نیز دارد) میتوان image‌های ساخته شده را بر روی cloud نگه داشت و همه‌ی اعضای تیم از یک image بخصوص استفاده کنند؛ برای مثال همه‌ی اعضای تیم از یک نسخه‌ی Nodejs استفاده کنند و اشتباها بر روی ماشین‌های توسعه‌ی مختلف برنامه نویسان، از نسخه‌های مختلفی استفاده نشود و همچنین روند به‌روز رسانی به سادگی انجام گیرد.


مزایای Docker برای برنامه نویسان

فرض کنید که یک App Service از Azure تهیه کرده باشید. تست‌های unit, integration, acceptance را انجام داده و با خیال راحت Container خود را از طریق برای مثال Visual studio team service بر روی App service به صورت انتشار از طریق مدل Continuous Integration و  Continuous Deployment داشته باشید. پس عملا داکر به Devops بودن محیط و چابک بودن تیم توسعه کمک شایانی کرده و فرآیند‌های سخت و زمانبر انتقال Codeها از محیط توسعه به محیط انتشار را تسریع میبخشد.
بنابراین از داکر به راحتی میتوان در محیط Production نیز استفاده کرد و مزایای فوق العاده ای را برای برنامه نویسان ارائه کرده است. بطور مثال فرض کنید در تولید نرم‌افزار یک Web server ، تعدادی Database و یک Caching server که کانفیگ کردن، اجرا و ... به صورت عادی بسیار صعب و مشکل ساز بوده را به راحتی میتوان اجرا نمود. ضمن اینکه ممکن است هر کدام از ابزارهایی که استفاده شده، فقط مخصوص سیستم عاملی خاص باشد که قاعدتا احتیاج به بالا آوردن Virtual Machine خواهید بود و در سناریو‌های خاصی مثل سیستم هایی با معماری Microservice که هر کدام از این ریز سرویس‌ها ممکن است زبان، فریم ورک، دیتابیس و ... مخصوص به خود را داشته باشند، عملا کار بسیار سخت و پر هزینه خواهد بود (ضمن اینکه استفاده‌ی همزمان از چند Virtual Machine در کنار هم در محیط توسعه، حجم زیادی از memory و disk سیستم شما را خواهد گرفت و شما را مجبور به ارتقای سیستم خود خواهد کرد!).

مشکل دیگری که Docker آن را حل کرده، Conflict‌های ورژن‌های مختلف ابزار‌های مورد استفاده است. به راحتی میتوان Containerی از Image‌ها را به صورت ایزوله با ورژن‌های مختلفی ایجاد کرد تا بطور کامل برنامه نویسان را از مشکل همیشگی به‌روزرسانی‌ها و Role-back کردن‌ها آسوده خاطر نماید. 

از آنجایی که داکر قابلیت اجرای در محیط production را نیز دارد، عملا محیط Development با محیط Production تفاوتی ندارد و این جمله‌ی معروف که «در سیستم من کار میکند اما در نسخه‌ی انتشار داده شده خیر» دیگر اتفاق نخواهد افتاد.

به راحتی میتوانید از یک Image خاص، Containerهای ایزوله‌ی متفاوتی را ساخته و همگی آنها را در کنار هم اجرا نمود و مورد تست و ارزیابی قرار داد.


Dokcer hub

مخرنی است از هزاران Image آماده از قبیل سیستم عامل، فریم ورک و... که قابلیت استفاده‌ی مجدد خواهد داشت. همچنین شما میتوانید Image‌های خود را نیز بدان اضافه نموده تا دیگران از آن استفاده نمایند. استفاده از مخزن‌های public آن رایگان میباشد. از آنجایی که Docker یک محصول متن باز و رایگان است، یک بخش از درآمد‌های آن از فروش اختصاصی مخزن‌ها در DokcerHub میباشد (چیزی شبیه به Private Repository در Github).
بیشتر از این به مفاهیم نمیپردازیم. برای مطالعه‌ی بیشتر، کتاب فوق العاده‌ی Mastering Docker را پیشنهاد میکنم. 


شروع به کار با Docker

بعد از نصب کردن نسخه‌ی رسمی Docker و باز کردن ترمینال مربوطه، اولین دستوراتی را که باید با آن آشنا باشیم، شامل موارد زیر میباشد:

لیست Image‌های کش شده‌ی بر روی سیستم:
 docker images
لیست container‌های در حال اجرای بر روی ماشین محلی:
 docker ps
بعد از تست کردن دو دستور فوق مشاهده میکنید که هیچ image و containerی بر روی سیستم شما وجود ندارد.

برای آزمایش کردن و نصب اولین image، دستور زیر را وارد میکنیم (میتوانید اطلاعات بیشتری از imageها را در dockerHub پیدا کنید). من در اینجا  kitematic/hello-world-nginx را به عنوان image از مخزن dokcerhub، بر روی سیستم خود pull کرده‌ام (این یک نسخه‌ی بسیار سبک از کانتینر nginx میباشد).
 docker pull kitematic/hello-world-nginx
بعد از اجرای دوباره‌ی دستور docker images مشاهده میکنید که image مربوطه بر روی سیستم شما نصب شده است.
حال وقت اجرای این image و توزیع آن بر روی container میباشد که با استفاده از دستور زیر است:
 docker run -p 80:80 kitematic/hello-world-nginx
پرچم p- برای مقدار دهی پورت خارجی و داخلی میباشد و بعد از آن هم که نام image مربوطه برای اجرای container میباشد (فلگ‌های خیلی بیشتر و تخصصی‌تری در رابطه با اجرا وجود دارند که در ادامه بیشتر مورد بحث قرار می‌گیرند) .

بعد از اجرای این دستور میتوانید با وارد کردن ip مربوط به virtual machine ساخته شده بر روی سیستم خود (اگر از مک یا ویندوز استفاده میکنید احتمالا 192.168.99.100 خواهد بود) که البته با دستور docker-machine ip میتوانید آن را پیدا کنید و وارد کردن آن بر روی مرورگر خود، تصویری مثل زیر را مشاهده کنید:

بدین معناست که container شما اجرا شده و قابلیت مورد استفاده قرار گرفتن را خواهد داشت. حال اگر دستور docker ps را مجددا وارد نمایید، اطلاعات این container را از نوع id, status port و غیره، مشاهده خواهید کرد.
مطالب
ارتقاء به ASP.NET Core 1.0 - قسمت 1 - NET Core. چیست؟
NET Core. چیست؟

برای اغلب توسعه دهنده‌های دات نت (برنامه‌های وب و دسکتاپ) تنها یک دات نت فریم ورک شناخته شده وجود دارد: The `Full` .NET Framework
که تنها بر روی ویندوز قابل اجرا است و آخرین نگارش پایدار آن در زمان نگارش این مطلب، 4.6.1 است. این فریم ورک بزرگ، از اجزایی تشکیل شده‌است که در تصویر ذیل قابل مشاهده‌اند:


مهم‌ترین قسمت‌های این فریم ورک «بزرگ» شامل مواردی مانند CLR که کار تبدیل کدهای IL را به کدهای ماشین انجام می‌دهد، BCL که کلاس‌های پایه‌ای را جهت کار با  IO، Text و غیره، فراهم می‌کنند، هستند؛ به علاوه کتابخانه‌هایی مانند Windows Forms، WPF و ASP.NET که برفراز BCL و CLR کار می‌کنند.
هرچند تعدادی از توسعه دهنده‌های دات نت تنها با Full framework کار می‌کنند، اما در طی سال‌های اخیر انشعابات بسیار دیگری از آن به وجود آمده‌اند؛ مانند دات نت‌های ویژه‌ی ویندوزهای 8 و Universal Windows Platform، دات نت مخصوص ویندوز فون 8 و ویندوز فون مبتنی بر پلتفرم سیلورلایت، به علاوه دات نت پلتفرم زامارین برای توسعه‌ی برنامه‌های iOS و Android نیز هم اکنون وجود دارند (البته در اینجا Mono، دات نت میکرو و غیره را هم باید ذکر کرد). این فریم ورک‌ها و انشعابات، به همراه پیاده سازی یک سری موارد مشترک و مواردی کاملا اختصاصی هستند که به سایر پلتفرم‌های دیگر قابل انتقال نیستند.

با زیاد شدن تعداد انشعابات دات نت «بزرگ»، نوشتن کدی که قابل اجرای بر روی تمام پلتفرم‌های یاد شده باشد، مشکل شد. اینجا بود که مفهومی را به نام PCL یا Portable class libraries معرفی کردند:


هدف از PCLها، ساده سازی کامپایل و به اشتراک گذاری کد بین پلتفرم‌های مختلف بود و پشتیبانی قابل توجهی هم از آن در VS.NET وجود دارد. هرچند این روش نسبتا موفق بود اما مشکلاتی را هم به همراه داشت. برای مثال با ارائه‌ی یک انشعاب و پلتفرم دیگری از دات نت «بزرگ»، کتابخانه‌ی PCL موجود، باید برای این انشعاب جدید مجددا کامپایل شود. به علاوه در اینجا تنها محدود به انتخاب امکانات مشترک بین پلتفرم‌های مختلف هستید.

برای رفع این مشکلات در پایان سال 2014، یک «دات نت فریم ورک جدید» به نام NET Core. معرفی شد که سورس باز است و همچنین چندسکویی (از ویندوز، لینوکس و OSX پشتیبانی می‌کند).


هرچند پیشتر Windows Store و ASP.NET Core app به صورت پلتفرم‌هایی مجزا ارائه شده بودند، اما اکنون از یک BCL مشترک به نام CoreFX استفاده می‌کنند و نحوه‌ی توزیع آن‌ها صرفا از طریق نیوگت است. به عبارتی اینبار بجای دریافت یک فریم ورک «بزرگ»، تنها اجزایی را دریافت می‌کنید که از طریق نیوگت سفارش داده‌اید.
به این ترتیب نه تنها کار توزیع برنامه‌های مبتنی بر NET Core. با سهولت بیشتری انجام خواهد شد، بلکه به روز رسانی اجزای یک برنامه، تاثیری بر روی سایر برنامه‌ها نخواهد داشت و مشکلات جانبی را به وجود نمی‌آورد. به علاوه دیگر نیازی نیست تا منتظر یک نگارش «بزرگ» دیگر باشید تا بتوانید آخرین به روز رسانی‌ها را دریافت کنید. اینبار به روز رسانی بسته‌های نیوگت برنامه معادل هستند با به روز رسانی کل فریم ورک در نگارش‌های قبلی «بزرگ» آن. در اینجا حتی CoreCLR و NET Native runtime. که مربوط به Windows runtime است هم از طریق نیوگت به روز رسانی می‌شود.

البته NET Core. انتهای مسیر نیست و هم اکنون NETStandard نیز جهت رفع مشکلات کامپایل مجدد PCLها در حال توسعه است و پس از ارائه‌ی آن، PCLها منسوخ شده درنظر گرفته می‌شوند. در این حالت با انتخاب target platform ایی به نام NETStandard (بجای مثلا انتخاب دات نت 4.5 و ویندوز فون 8)، اینبار دات نت 4.5 و ویندوز فون 8 و تمام پلتفرم‌های دیگر، به صورت یکجا انتخاب می‌شوند و اگر پلتفرم جدیدی برای مثال از NETStandard نگارش 1.1 پشتیبانی کند، به این معنا است که کتابخانه‌ی شما هم اکنون با آن سازگار است و دیگر نیازی به کامپایل مجدد آن نخواهد بود.
به علاوه هر برنامه‌ای که بر اساس NETStandard تهیه شود، قابلیت اجرای بر روی NET Core. را نیز خواهد داشت. به عبارتی برنامه‌های NETStandard همان برنامه‌های مبتنی بر NET Core. هستند.


ASP.NET Core چیست؟

در زمان نگارش این مطلب، دو گزینه‌ی برنامه‌های وب ASP.NET Core 1.0 و همچنین Windows Store apps (مبتنی بر NET Native Runtime.) قابلیت استفاده‌ی از این پلتفرم جدید NET Core. دارند.
ASP.NET Core 1.0، که پیشتر با نام ASP.NET 5 معرفی شده بود، بازنویسی کامل ASP.NET است که با ایده‌ی کاملا ماژولار بودن، تهیه شده‌است و از طریق آن، قابلیت به روز رسانی منظم و توزیع آسان از طریق نیوگت، میسر خواهد شد. به علاوه در آن، بسیاری از الگوهای برنامه نویسی شیء‌گرا مانند تزریق وابستگی‌ها، به صورت توکار و از ابتدا پشتیبانی می‌شوند.
ASP.NET Core 1.0 از WebForms ، VB ، WebPages و SignalR پشتیبانی نمی‌کند. البته در این بین عدم پشتیبانی از «وب فرم‌ها» قطعی است؛ اما افزودن سه مورد دیگر یاد شده، جزو لیست کارهای پس از ارائه‌ی نگارش 1 این فریم ورک قرار دارند و به زودی ارائه خواهند شد.


اکنون وضعیت  ASP.NET MVC 5 و ASP.NET Web API 2 چگونه است؟

ASP.NET Core 1.0 مدل برنامه نویسی ASP.NET MVC و Web API را به صورت یکپارچه ارائه می‌دهد و دیگر خبری از ارائه‌ی مجزای این‌ها نخواهد بود و دقیقا بر مبنای مفاهیم برنامه نویسی این دو بنا شده‌است. به صورت خلاصه MVC + Web API + Web Pages = Core MVC 1.0
پیشتر فضای نام System.Web.MVC مخصوص ASP.NET MVC بود و فضای نام مجزای دیگری به نام System.Web.Http مخصوص ASP.NET Web API. اما اکنون تنها یک فضای نام مشترک و یکپارچه به نام Microsoft.AspNet.Mvc هر دوی این‌ها را پوشش می‌دهد.

در این نگارش جدید وابستگی از system.web مبتنی بر IIS حذف شده‌است و با استفاده از هاست جدید چندسکویی به نام Kesterl، به سرعتی 5 برابر سرعت NodeJS دست یافته‌اند.


آخرین تاریخ به روز رسانی ASP.NET MVC 5.x دوشنبه، 20 بهمن 1393 است (با ارائه نگارش 5.2.3 که آخرین نگارش رسمی و پایدار آن است) و آخرین تاریخ به روز رسانی ASP.NET Web API 2.x نیز همان روز است.
هرچند مایکروسافت عادت به اعلام رسمی پایان پشتیبانی از بسیاری از محصولات خود را ندارد اما تمام فناوری‌های «قدیمی» خودش را بر روی CodePlex نگهداری می‌کند و تمام فناوری‌های «جدید» را به GitHub منتقل کرده‌است. بنابراین اگر در مورد فناوری خاصی به Codeplex رسیدید، یعنی «دیگر ادامه‌ی رسمی نخواهد یافت» و حداکثر در حد رفع یک سری باگ‌ها و مشکلات گزارش شده باقی می‌مانند.
مثال 1: هم اکنون نگارش دوم ASP.NET Identity را بر روی Codeplex می‌توانید مشاهده کنید. نگارش سوم آن به GitHub منتقل شد‌ه‌است که این نگارش صرفا با ASP.NET Core 1.0 سازگار است. در مورد ASP.NET MVC و Web API نیز چنین حالتی رخ داده‌است. نگارش‌های 5 و 2 آن‌ها بر روی Codeplex موجود هستند و نگارش ششم که به ASP.NET Core 1.0 تغییر نام یافت و ترکیبی است از MVC و Web API، در GitHub توسعه می‌یابد.
مثال 2: WCF به علت پیچیدگی بیش از حد و مدرن نبودن طراحی آن، رقابت را به ASP.NET Web API 2.x واگذار کرد و مدل برنامه نویسی ASP.NET Web API 2.x نیز هم اکنون جزئی از ASP.NET Core 1.0 است. بنابراین اگر قصد ایجاد پروژه‌ی جدیدی را بر این مبنا دارید، بهتر است با APS.NET Core 1.0 کار را شروع کنید.


اما هنوز تعداد زیادی از کتابخانه‌های Full framework به NET Core. انتقال پیدا نکرده‌اند

برای نمونه هنوز EF Core 1.0 که پیشتر نام EF 7.x به آن داده شده بود، به مرحله‌ی نهایی تکمیل قابلیت‌های آن نرسیده‌است. اما باید دانست که ASP.NET Core 1.0 صرفا بر فراز NET Core. قابل اجرا نیست؛ بلکه قابلیت اجرای بر فراز NET 4.6. و یا همان دات نت «بزرگ و کامل» را نیز دارد. بنابراین به سادگی قابلیت اجرای EF 6.x و یا NHibernate را نیز دارا است. تنها مزیتی را که در اینجا از دست خواهید، قابلیت چندسکویی بودن ASP.NET Core 1.0 است؛ زیرا EF 6.x با چنین دیدی طراحی نشده‌است.



همانطور که ملاحظه می‌کنید، ASP.NET Core 1.0 قابلیت اجرای بر روی هر دوی NET Core 1.0. و NET 4.6. را دارا است. اما یکی، چندسکویی است و دیگری صرفا مختص به ویندوز.


فناورهای منسوخ شده‌ی در NET Core.

یکسری از فناوری‌ها و کتابخانه‌ها احتمالا هیچگاه قابلیت انتقال به NET Core. را نخواهند یافت و یا حداقل باید تا چندنگارش بعدی آن صبر کنند. فناوری‌های خاتمه یافته‌ی با NET Core. به شرح زیر هستند:
- Reflection: همانطور که عنوان شد، NET Core. بر فراز CoreCLR و همچنین NET Native runtime. اجرا می‌شود و تولید برنامه‌های native و static linking آن‌ها مانند برنامه‌های ++C، نیاز به دانستن اجزایی دارد که به صورت پویا فراخوانی نمی‌شوند و بلافاصله و در زمان کامپایل، توسط کامپایلر قابل تشخیص هستند. همین محدودیت سبب شده‌است که استفاده‌ی از Reflection در NET Core. به حداقل ممکن آن برسد. برای مثال در System.Object متد GetType آن تنها نام نوع را باز می‌گرداند و نه اطلاعات بیشتری را مانند  GetMembers سابق.
- App Domains: هرچند CoreCLR از App Domains پشتیبانی می‌کند اما NET Native runtime. خیر. به همین جهت برای ایزوله سازی برنامه‌ها توصیه شده‌است که از containerهایی مانند docker استفاده شود.
- Remoting: پیش از WCF جهت برقراری ارتباط بین برنامه‌ها مطرح شده بود و هم اکنون در دات نت کامل هم آنچنان استفاده‌ای از آن نمی‌شود.
- binary serialization: البته کتابخانه‌هایی مانند JSON.NET و امثال آن، نگارش NET Core. هم دارند؛ اما چون binary serialization نیاز به اطلاعات reflection قابل توجهی دارد دیگر پشتیبانی نخواهد شد.


فناور‌هایی که به زودی به NET Core. منتقل می‌شوند

یکسری از فناوری‌ها مانند XAML هنوز معادل NET Core. ندارند و لیست زیر قرار است از طرف مایکروسافت سورس باز شده و همچنین به NET Core. منتقل شود:
System.Data
System.DirectoryServices
System.Drawing
System.Transactions
System.Xml.Xsl and System.Xml.Schema
System.Net.Mail
System.IO.Ports
System.Workflow
System.Xaml


مراحل نصب ASP.NET Core 1.0

پیش از نصب نگارش 1.0 RTM باید به این نکته دقت داشت که نصاب آن، نگارش‌های آزمایشی قبلی را حذف و یا بازنویسی نمی‌کند و همین مساله ممکن است سبب بروز تداخل‌هایی و یا حتی از کار افتادن VS.NET شما شود. بنابراین اگر نگارش‌های RC یا بتا را پیشتر نصب کرده‌اید، به Add remove programs ویندوز مراجعه کرده و سه مورد ذیل را حتما حذف کنید (خیلی مهم):
- Preview Tooling (all versions)
- NET Core Runtime SDK (all versions).
- NET Core Runtime (all Versions).
پس از حذف بسته‌های قدیمی، برای نصب نگارش 1.0 RTM، ابتدا نیاز است Visual Studio 2015 Update 3 را نصب کنید و پس از آن با استفاده از NET Core for Visual Studio Official MSI Installer. کار نصب اجزای مورد نیاز آن انجام خواهد شد.


بررسی شماره نگارش 1.0 RTM

پس از نصب اجزای عنوان شده، خط فرمان را گشوده و دستور ذیل را صادر کنید:
 C:\Users\Vahid>dotnet --version
1.0.0-preview2-003121
همانطور که مشاهده می‌کنید، نگارش ذکر شده هنوز در مرحله‌ی preview است و صرفا مرتبط است به tooling و یا ابزارهای مرتبط با آن.
اگر یک پروژه‌ی خالی ASP.NET Core Web Application را نیز شروع کنید (با طی مراحل زیر جهت ایجاد یک پروژه‌ی جدید):
 .NET Core -> ASP.NET Core Web Application (.NET Core) -> Select `Empty` Template


در اینجا فایل جدیدی را به نام global.json مشاهده می‌کنید که محتوایات آن شامل دقیقا همین شماره نگارش است؛ به همراه معرفی پوشه‌های اصلی پروژه:
{
  "projects": [ "src", "test" ],
  "sdk": {
    "version": "1.0.0-preview2-003121"
  }
}