مطالب
آشنایی با XSLT
XSLT در واقع یک StyleSheet یا یک راهنما در مورد تبدیل فایل‌های xml به انواع و یا ساختارهای دیگری چون فایل‌های html، فایل‌های متنی و ... است که توسط کنسرسیوم وب ارائه شده‌است. این فایل حاوی یک سری دستورالعمل برای برنامه‌های پردازشگر است که به آن‌ها می‌گوید چگونه این فایل را تبدیل کنند.

اساس کار XSLT
در تصویر زیر، فایل xml به همراه xslt، به تجزیه کننده یا تحلیل کننده داده میشوند. در این قسمت هر دو فایل منبع تحلیل شده و از روی فایل xml، درختی در حافظه تهیه می‌شود و از فایل xslt یک سری قوانین استخراج می‌شوند. بعد این دو محتوای جدید تولید شده، در اختیار XSL Processor قرار گرفته و از روی آن‌ها به ساخت درخت نتیجه (نوع درخواستی) در حافظه می‌رسد که در نهایت آن را به نام یک فایل مستند میکند.

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

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

ساختار فایل xml:
<Albums>
  <Album>
    <name>Modern Talking</name>
    <cover>http://album.com/a.jpg</cover>
    <Genres>
      <Genre>POP</Genre>
      <Genre>Jazz</Genre>
      <Genre>Classic</Genre>
    </Genres>
    <Description>
      this is a marvelous Album
    </Description>
    <price>
      25.99$
    </price>
  </Album>
</Albums>

ساختار فایل Html
در ابتدا فایل، برای معرفی فایل و رعایت قرارداد، فضای نام مربوطه را یا به شکل زیر
<xsl:stylesheet version="1.0"xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
یا بدین شکل
<xsl:transform version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
می‌نویسیم و سپس کل کدهای html را با تگ زیر محصور می‌کنیم:
<xsl:template match="/">
<html>
....
</html>
</xsl:template>
هر آدرسی که در match بنویسید، آدرس‌های دستورات داخلی در ادامه‌ی آن خواهند بود.

نمایش فیلدها
سپس در آن، کد html مورد نظر را وارد می‌کنیم و در میان این کدها، تگ‌های xslt را وارد می‌نماییم. کد زیر بخشی از صفحه هست که برای نمایش آلبوم‌ها استفاده می‌کنیم:
<div>
  <img src="" alt="image" />
  <h3>
    <xsl:value-of select="albums/album/name" />
  </h3>
  <h4>Artist Name 1</h4>
  <h5>POP</h5>
  <h6>25/5/2015</h6>
  <h7>this is description</h7>
  <div>
    <a href="#">More</a>
  </div>
</div>
تگ‌های xsl نشان دهنده دستورالعمل‌های این قالب هستند. این دستور وظیفه انتخاب یک آیتم و نمایش آن را دارد. در قسمت select، آدرس نام آلبوم را به صورت یک مسیر، از تگ والد به سمت پایین وارد کرده‌ایم. این دستور العمل با اولین گره که به آن برسد، نمایش می‌یابد ولی اگر بخواهیم کدهای بالا، به تعداد هر آیتم (آلبوم) تکرار شود، از دستور العمل حلقه استفاده می‌کنیم:
<xsl:for-each select="albums/album">
  <div>
    <img src="" alt="image" />
    <h3>
      <xsl:value-of select="name" />
    </h3>
    <h4>Artist Name 1</h4>
    <h5>POP</h5>
    <h6>25/5/2015</h6>
    <h7>this is description</h7>
    <div>
      <a href="#">More</a>
    </div>
  </div>
</xsl:for-each>
خطوط بالا، مرتبا گره‌های album را در گره Albums، یافته و همه تگ‌های داخلش را تکرار کرده و نام هر آلبوم را نیز چاپ می‌کند. موقعی که از حلقه استفاده می‌کنیم و مسیر گره والد در آن مشخص شده است، نیازی نیست که دیگر برای دریافت نام آلبوم، کل مسیر را ذکر کنید.

ایجاد ارتباط میان دو فایل XML و XSLT
برای اجرا و تست آن باید از طریق یک ابزار که توانایی تحلیل این دستورات را دارد، استفاده کنید. یکی از همین ابزارها، مرورگر شماست. برای اینکه به مرورگر ارتباط فایل xml و xsl را بفهمانیم، تکه کد زیر را در فایل xml جهت لینک شدن می‌نویسیم و سپس فایل xml را در مرورگر اجرا می‌کنیم:
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="index.xslt"?>
الان تصویر بدین شکل نمایش داده می‌شود:



ساخت المان یا تگ
در ادامه بقیه فیلدها را تکمیل میکنیم. فیلد بعدی تصویر است که تصویر دیگر مانند متن، بین تگ‌ها قرار نمی‌گیرد و باید داخل attribute تگ تصویر درج شود. برای نمایش تصویر می‌توانیم به دو شکل عمل کنیم. کد را به صورت زیر بنویسیم:
<img src="{cover}" width="200px" height="200px">
  <xsl:value-of select="cover" />
</img>
یا اینکه المان مورد نظر را توسط xsl ایجاد کنیم:
<xsl:element name="img">
  <xsl:attribute name="src">
    <xsl:value-of select="cover"/>
  </xsl:attribute>
  <xsl:attribute name="width">
    200px
  </xsl:attribute>
  <xsl:attribute name="height">
    200px
  </xsl:attribute>
  <xsl:value-of select="cover"/>
</xsl:element>
حال با یکی از کدهای بالا، مرورگر را جهت تست اجرا می‌کنیم:


دستور العمل‌های بیشتر (مرتب سازی)
دستورات xslt فقط به چند تگ بالا خلاصه نمی‌شود؛ بلکه دستورات شرطی ،تعریف متغیرها، توابع داخلی جهت کار با اعداد، رشته‌ها و ... هم در آن وجود دارند. یکی از همین دستورات جذاب، مرتب سازی است. دستورالعل sort به ما اجازه می‌دهد تا بر اساس یک فیلد، داده‌ها را مرتب کنیم. با افزودن کد زیر بعد از دستور حلقه، لیست را بر اساس نام آلبوم مرتب می‌کنیم:
<xsl:for-each select="Albums/Album">
<xsl:sort select="name"/>

دستورات شرطی
حال تصمیم می‌گیریم که آلبوم‌های با قیمت بالاتر از 10 دلار را 2 دلار تخفیف دهیم. برای اینکار نیاز است تا با تگ if آشنا شویم:
<xsl:variable name="numprice" select="number(substring(price,1,string-length(price)-1))" />

<xsl:if test="$numprice>=10">
  <h4 >
    <span style='text-decoration:line-through'>
      <xsl:value-of select="price" />
    </span>
    <b>
      <xsl:value-of select="$numprice -2" />$
    </b>
  </h4>
</xsl:if>
در اینجا کمی مثال را پیچیده‌تر و از چند عنصر جدید در آن استفاده کرده‌ایم. در خط اول یک متغیر با نام numprice تعریف کرده‌ایم. متغیرها بر دو نوعند: محلی یا عمومی. برای تعریف متغیر عمومی لازم است آن را در بالای سند، بعد از تگ template ایجاد کنید و متغیرهای محلی فقط در داخل همان تگی که تعریف کرده‌اید، اعتبار دارند. از آنجا که قیمت‌ها به صورت رشته‌ای هستند و در انتها هم حرف $ را دارند، بهتر است که قیمت را بدون $ به عدد تبدیل کنیم تا بتوانیم بعدا در شرط، به عنوان عدد، مقایسه و در صورت صحت شرط، دو عدد از آن کم کنیم. در اینجا ما از تابع substring برای جداسازی رشته و از تابع string-lentgh برای دریافت طول رشته و در نهایت از تابع number برای تبدیل رشته به عدد استفاده کردیم و آن‌را برای استفاده‌های بعدی، در متغیر ذخیره کردیم (برای آشنایی بیشتر با این توابع به این آدرس رجوع کنید). سپس تگ if را صدا زدیم و در صورتی که مقدار داخل متغیر (علامت متغیر، استفاده از عبارت $ قبل از نام آن است ) از 10 بیشتر یا برابر آن بود، دو واحد از آن کم می‌کنیم و روی قیمت قبلی خط می‌کشیم. نتیجه حاصله در تصویر زیر مشخص است:

در حال حاضر مشکلی که وجود دارد این است که ما برای قیمت‌های زیر 10 دلار هیچ شرطی نداریم و این دستور if کمی سطحی برخورد می‌کند و برای قیمت‌های زیر 10 دلار مجددا به یک if نیازمندیم. ولی دستور دیگری، مشابه دستور switch وجود دارد که استفاده از آن در شرایط دو شرط بالا مقرون به صرفه است. نام این دستور choose می‌باشد. خطوط بالا را به شکل زیر تغییر می‌دهیم:
<xsl:variable name="numprice" select="number(substring(price,1,string-length(price)-1))" />
<xsl:choose>
  <xsl:when test="$numprice>=10">
    <h4 >
      <span style='text-decoration:line-through;color:red'>
        <xsl:value-of select="price" />
      </span>
      <b>
        <xsl:value-of select="$numprice -2" />$
      </b>
    </h4>
  </xsl:when>

  <xsl:otherwise>
    <b>
      <xsl:value-of select="$numprice" />$
    </b>
  </xsl:otherwise>
</xsl:choose>
شما می‌توانید به تعداد زیادی از تگ when برای اعمال دیگر شرط‌ها همانند دستور switch ...case استفاده کنید. در اینجا اگر قیمت‌ها زیر 10 دلار باشند، تغییری در قیمت ایجاد نکرده و خودش را نشان می‌دهیم. ولی اگر از 10 دلار به بالا باشد، قیمت‌ها دو دلار تخفیف می‌خورند. شکل زیر نتیجه حاصل از اضافه شدن کد بالاست:


استفاده از template ها
برای خلاصه سازی کار و جمع و جور کردن کدها می‌توان از template‌ها استفاده کرد. شما هم در ابتدا، یک قالب یا template را برای کل سند ایجاد کردید. حالا سعی ما این است که اینبار، قالب‌های کوچکتر و جرئی‌تر و اختصاصی‌تر ساخته و آن‌ها را در قالب اصلی صدا بزنیم. برای ساخت قالب به ریشه xsl:stylesheet رفته و یک template جدید را به شکل زیر ایجاد میکنیم:
<xsl:template match="DateOfRelease">

  <xsl:variable name="date" select="."/>
  <xsl:variable name="day" select="substring($date,1,2)"/>
  <xsl:variable name="month" select="number(substring($date,4,2))"/>
  <xsl:variable name="year" select="substring($date,7,4)"/>
  Release Date:

  <xsl:choose>
    <xsl:when test="$month = 1">
      <xsl:value-of select="$day"/>,Jan/<xsl:value-of select="$year"/>
    </xsl:when>
    <xsl:when test="$month = 2">
      <xsl:value-of select="$day"/>,Feb/<xsl:value-of select="$year"/>
    </xsl:when>
    <xsl:when test="$month = 3">
      <xsl:value-of select="$day"/>,Mar/<xsl:value-of select="$year"/>
    </xsl:when>
    <xsl:when test="$month = 4">
      <xsl:value-of select="$day"/>,Apr/<xsl:value-of select="$year"/>
    </xsl:when>
    <xsl:when test="$month = 5">
      <xsl:value-of select="$day"/>,May/<xsl:value-of select="$year"/>
    </xsl:when>
    <xsl:when test="$month = 6">
      <xsl:value-of select="$day"/>,Jun/<xsl:value-of select="$year"/>
    </xsl:when>

    <xsl:when test="$month = 7">
      <xsl:value-of select="$day"/>,Jul/<xsl:value-of select="$year"/>
    </xsl:when>
    <xsl:when test="$month = 8">
      <xsl:value-of select="$day"/>,Aug/<xsl:value-of select="$year"/>
    </xsl:when>
    <xsl:when test="$month = 9">
      <xsl:value-of select="$day"/>,Sep/<xsl:value-of select="$year"/>
    </xsl:when>
    <xsl:when test="$month = 10">
      <xsl:value-of select="$day"/>,Oct/<xsl:value-of select="$year"/>
    </xsl:when>
    <xsl:when test="$month = 11">
      <xsl:value-of select="$day"/>,Nov/<xsl:value-of select="$year"/>
    </xsl:when>

    <xsl:otherwise>
      <xsl:value-of select="$day"/>,Dec/<xsl:value-of select="$year"/>
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>
در این قالب ما روی فیلد تاریخ کار میکنیم و قصد داریم تاریخ را در قالب نوشتاری دیگری درج کنیم. عبارت نقطه (.) در select خط اول به معنای گره جاری است. بعد از ساخت قالب جدید آن را در محل مورد نظر در قالب اصلی یا ریشه صدا می‌زنیم:
<xsl:for-each select="Albums/Album">
  <xsl:sort select="name"/>
  <div>
    <img src="{cover}" width="200px" height="200px">
      <xsl:value-of select="cover" />
    </img>
    <h3>
      <xsl:value-of select="name" />
    </h3>
    <h4>POP</h4>

    <xsl:variable name="numprice" select="number(substring(price,1,string-length(price)-1))" />
    <xsl:choose>
      <xsl:when test="$numprice>=10">
        <h4 >
          <span style='text-decoration:line-through;color:red'>
            <xsl:value-of select="price" />
          </span>
          <b>
            <xsl:value-of select="$numprice -2" />$
          </b>
        </h4>
      </xsl:when>

      <xsl:otherwise>
        <h4 >
          <b>
            <xsl:value-of select="$numprice -2" />$
          </b>
        </h4>
      </xsl:otherwise>
    </xsl:choose>
    <h4>
      <!-- محل صدا زدن قالب-->
      <xsl:apply-templates select="DateOfRelease"/>
    </h4>
    <h4>
      <xsl:value-of select="Description"/>
    </h4>
    <div>
      <a href="#">More</a>
    </div>
  </div>
</xsl:for-each>


فیلترسازی
یکی از خصوصیات دیگری که فایل XML داشت، فیلد ژانر موسیقی بود و قصد داریم با استفاده از فیلترسازی، تنها سبک خاصی مثل سبک پاپ را نمایش دهیم و موسیقی‌هایی را که خارج از این سبک هستند، از نتیجه حذف کنیم. به همین علت دستور for-each را به شکل زیر تغییر میدهیم:

<xsl:for-each select="Albums/Album[contains(Genres/Genre, 'POP')]">
دستور بالا میگوید که حلقه را آلبوم‌ها حرکت بده، به شرطی که در مسیر Genres/Genre مقداری برابر POP بیابی. حالا اگر بخواهیم این شرط را معکوس کنیم میتوانیم عبارت را به صورت زیر درج کنیم:
<xsl:for-each select="Albums/Album[not(contains(Genres/Genre, 'POP'))]">
برای شکیل شدن کار، بهتر است که نام سبک‌ها را در کنار تصویر هم درج کنیم. به همین علت دستور حلقه را مورد استفاده قرار می‌دهیم:
<xsl:for-each select="Genres/Genre">
<xsl:value-of select="."/>
<xsl:if test="position() != last()">
,
</xsl:if>
</xsl:for-each>
دستور بالا شامل دو تابع جدید هست که مقدار برگشتی آن‌ها اندیس گره فعلی و اندیس آخرین گره می‌باشد و در عبارت شرطی ما تعیین کرده‌ایم که بین هر سبکی که می‌نویسد، یک علامت جدا کننده , هم قرار گیرد؛ به جز آخرین گره که دیگر نیازی به علامت جدا کننده ندارد. تصویر زیر آلبوم هایی را نشان میدهد که در سبک پاپ قرار گرفته‌اند:


تبدیل xml و xsl در دات نت

string xmlfile = Application.StartupPath + "\\sampleXML.xml";

//بارگذاری فایل قوانین در حافظه
XslTransform xslt = new XslTransform();
xslt.Load("sample-stylesheet.xsl");

//XPath ایجاد یک سند جدید بر اساس استاندارد 
XPathDocument xpath = new XPathDocument(xmlfile);

//آماده سازی برای نوشتن فایل نهایی
XmlTextWriter xwriter = new XmlTextWriter(xmlfile + ".html", Encoding.UTF8);

//نوشتن فایل مقصد بر اساس قوانین مشخص شده
xslt.Transform(xpath, null, xwriter, null);
xwriter.Close();
در صورتی که فایل XSL نیازی به تگ‌هایی داشته باشد که در فایل xml نیست و خودتان قصد دارید که آن‌ها را از این طریق وارد کنید، می‌توانید خطوط زیر را به کد بالا اضافه کنید:
XsltArgumentList xslArgs = new XsltArgumentList();
xslArgs.AddParam("logo", "", logo);
xslArgs.AddParam("name", "", name); 
....
xslt.Transform(xpath, xslArgs, xwriter, null);

مطالب
طرحبندی صفحات وب با بوت استرپ 4 - قسمت سوم
قابلیت‌های یک Flex Container در بوت استرپ 4

یک Flex Container متداول به این صورت کار می‌کند:

این کلاس‌ها که موارد داخل پرانتز آن‌ها اختیاری است، المان را تبدیل به یک المان Flexbox می‌کنند. حالت نمایشی پیش‌فرض آن‌ها block است؛ اما اگر نیاز بود می‌توان آن‌ها را تبدیل به in-line نیز کرد. بنابراین ساده‌ترین Flex Container را می‌توان با افزودن کلاس d-flex ایجاد کرد.

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

به این ترتیب می‌توان آیتم‌ها را به صورت ردیف‌ها و یا ستون‌هایی، نمایش داد. مقدار row در اینجا به صورت پیش‌فرض اعمال می‌شود. بنابراین ذکر کلاس خالی flex به معنای قرار دادن المان‌ها در طی چندین ردیف در صفحه است. در اینجا استفاده‌ی از reverse، نمایش المان‌ها را از راست به چپ میسر می‌کند.

در یک Flex Container امکان تعیین ترتیب عناصر نیز وجود دارد:

این مورد را در مطلب «طرحبندی صفحات وب با بوت استرپ 4 - قسمت دوم » بررسی کردیم. کلاس order را علاوه بر ستون‌ها، بر روی هر دربرگیرنده‌ای که دارای کلاس d-flex است نیز می‌توان اعمال کرد.

همچنین امکان تنظیم فواصل بین آیتم‌ها نیز در یک Flex Container پیش بینی شده‌است:

برای مثال استفاده‌ی از مقدار تراز center، روش بسیار مناسبی برای قرار دادن عناصر، در میانه‌ی افقی صفحه است. این مورد را نیز در قسمت قبل بررسی کردیم.

می‌توان نحوه‌ی Wrap المان‌ها را بر اساس فضای خالی در یک Flex Container به صورت زیر تنظیم کرد:

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

برای تغییر تراز عمودی المان‌ها در یک Flex Container از کلاس align-content استفاده می‌شود:

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


یک مثال: بررسی ویژگی‌های یک Flex Container

<head>
    <style>
        .item {
          background: #f0ad4e;
          text-align: center;
          width: 150px;
          height: 30px;
          border: 1px solid white;
        }
    </style>
</head>

<body>
    <div class="container bg-danger">
        <div class="bg-info" style="height:100vh">
            <div class="item">Exotic</div>
            <div class="item">Grooming</div>
            <div class="item">Health</div>
            <div class="item">Nutrition</div>
            <div class="item">Pests</div>
            <div class="item">Vaccinations</div>
        </div>
    </div>
</body>
در اینجا ارتفاع container به 100vh تنظیم شده‌است تا کل view-port را پوشش دهد و رنگ آن نیز به bg-info تنظیم شده‌است تا بتوان تغییر محل تراز عمودی را بهتر مشاهده کرد.
در ابتدا کلاس d-flex را به div داخل container اضافه می‌کنیم:
<div class="bg-info d-flex" style="height:100vh">
بلافاصله مشاهده خواهیم کرد که عناصر تعریف شده در طی یک ردیف از چپ به راست نمایش داده می‌شوند:


و اگر جهت این Flex Container را به صورت صریح مشخص کنیم:
<div class="bg-info d-flex flex-column" style="height:100vh">
آیتم‌های درون آن به صورت یک ستون نمایش داده می‌شوند:


و یا اگر بخواهیم آیتم‌ها را از راست به چپ به صورت یک ردیف نمایش دهیم می‌توان از flex-row-reverse استفاده کرد:

<div class="bg-info d-flex flex-row-reverse" style="height:100vh">


و اگر بجای row در این حالت column را مقدار دهی کنیم:
<div class="bg-info d-flex flex-sm-column-reverse" style="height:100vh">


آیتم‌ها از پایین صفحه شروع خواهند شد. البته در این مثال break-point از نوع sm نیز ذکر شده‌است تا پس از گذر از این اندازه‌ی صفحه، چنین اتفاقی رخ دهد.

و یا اگر بخواهیم آیتم‌ها از راست به چپ در طی یک ردیف، پس از اندازه‌ی صفحه‌ی sm و همچنین در میانه‌ی صفحه ظاهر شوند، می‌توان از کلاس justify-content استفاده کرد:
<div class="bg-info d-flex flex-sm-row-reverse justify-content-center"
     style="height:100vh">
با این خروجی:


و اگر wrap را فعال کنیم:
<div class="bg-info d-flex flex-sm-row-reverse justify-content-center flex-wrap"
     style="height:100vh">


اگر آیتم‌ها با اندازه‌ی اصلی خودشان، در ردیف جاری جا نشدند، به سطرهای بعدی منتقل خواهند شد.
اگر nowrap را فعال کنیم:
<div class="bg-info d-flex flex-sm-row-reverse justify-content-center flex-nowrap"
     style="height:100vh">
سعی می‌کند در ردیف جاری، آیتم‌ها را تا حد ممکن کوچک کرده و نمایش دهد:


و با فعالسازی align-content-start، تمام آیتم‌ها را به سمت بالای صفحه هدایت می‌کند و align-content-end، آن‌ها را از پایین صفحه شروع خواهد کرد:
<div class="bg-info d-flex flex-sm-row-reverse justify-content-center flex-wrap align-content-start"
     style="height:100vh">



کنترل آیتم‌های قرار گرفته‌ی درون یک Flex Container در بوت استرپ 4

علاوه بر امکان کنترل ویژگی‌های یک Flex Container، اجزای قرار گرفته‌ی درون آن‌ها را نیز می‌توان کنترل کرد و اینکار توسط کلاس align-self میسر است:


این مورد نیز همانند توضیحات کلاس align-self اعمالی به ستون‌ها است که در قسمت قبل بررسی کردیم.

به علاوه در اینجا امکان تعریف floating elements نیز مسیر است که شبیه به دسترسی به امکانات CSS در بوت استرپ است با امکان تنظیم break-points:

فرض کنید به تمام آیتم‌های داخل Flex Container کلاس float-left را اضافه کرده‌ایم. در این حالت Container قابلیت ردیابی اندازه‌ی این آیتم‌ها را از دست می‌دهد. به همین جهت با اعمال کلاس clearfix بوت استرپ به container، مجددا امکان ردیابی این آیتم‌ها را پیدا می‌کند.


کلاس‌های تعریف margin و padding در بوت استرپ 4

در بوت استرپ 4 کلاس‌های ویژه‌ای برای ایجاد margin و padding بین عناصر در نظر گرفته شده‌اند که خلاصه‌ی آن‌ها فرمول زیر است:

ابتدا با تعریف یک خاصیت شروع می‌شود؛ مانند m یا p، برای کنترل margin و padding. سپس لبه‌ای که باید به آن اعمال شود بدون فاصله و یا - ذکر می‌شود؛ مانند mt به معنای margin-top. در این فرمول x به معنای اعمال همزمان به چپ و راست است و y به معنای اعمال همزمان به بالا و پایین و اگر می‌خواهید آیتم‌های کناری آیتم جاری را به دو طرف لبه‌ها هدایت کنید از mx-auto استفاده کنید.
در اینجا امکان اعمال یک break-point اختیاری نیز وجود دارد. در آخر اندازه ذکر می‌شود که بین 0 تا 5 متغیر است.

یک مثال: اعمال کلاس‌های padding و margin بوت استرپ 4

<head>
    <style>
        .item {
          background: #f0ad4e;
          text-align: center;
          border: 1px solid white;
        }
    </style>
</head>

<body>
    <div class="container bg-danger">
        <div class="bg-info d-flex">
            <div class="item">Exotic</div>
            <div class="item">Grooming</div>
            <div class="item bg-danger ml-3 my-sm-3 pb-3 pt-5">Health</div>
            <div class="item">Nutrition</div>
            <div class="item">Pests</div>
            <div class="item">Vaccinations</div>
        </div>
    </div>
</body>
در اینجا به آیتم Health یک margin-left با اندازه‌ی 3، یک margin بالا و پایین فعال شونده‌ی پس از sm با اندازه‌ی 3، یک padding پایین با اندازه‌ی 3 و یک padding بالا با اندازه‌ی 5 اضافه شده‌است؛ با این خروجی:



نمایش و مخفی سازی عناصر در بوت استرپ 4

کلاس invisible سبب می‌شود تا المانی در صفحه نمایش داده نشود، اما این المان همچنان فضای اختصاصی خود را خواهد داشت. کلاس visible به معنای نمایان بودن المانی تنها برای screen readers است (دستگاه‌های کمکی معلول‌ها).
اما روش اصلی نمایش و یا مخفی سازی عناصر در بوت استرپ 4، استفاده از خاصیت display است:

برای مثال با انتساب کلاس d-sm-none به المانی، می‌توان سبب مخفی شدن آن پس از گذر از sm شد.


امکان تعیین اندازه‌ی عناصر در بوت استرپ 4

بوت استرپ 4 تعدادی کلاس ویژه را برای تعیین اندازه‌ی عناصر نیز افزوده‌است:


در اینجا w=width، h=height، mw=max-height و mh=max-height است با مقادیر 25، 50، 75 و 100 و مقدار پیش‌فرض آن 100 است (یعنی پوشاندن کل container).


کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: Bootstrap4_06.zip
نظرات مطالب
معرفی ASP.NET Identity
این پروژه سورس باز هست. مشکلات آن‌را برای رفع در اینجا گزارش کنید. نحوه‌ی گزارش مشکل هم باید کمی فنی باشد. حداقل جزئیات exception و stack trace آن باید گزارش شوند.
مطالب
OpenCVSharp #18
ساخت یک OCR ساده تشخیص اعداد انگلیسی به کمک OpenCV

این مطلب را می‌توان به عنوان جمع بندی مطالبی که تاکنون بررسی شدند درنظر گرفت و در اساس مطلب جدیدی ندارد و صرفا ترکیب یک سری تکنیک است؛ برای مثال:
چطور یک تصویر را به نمونه‌ی سیاه و سفید آن تبدیل کنیم؟
کار با متد Threshold جهت بهبود کیفیت یک تصویر جهت تشخیص اشیاء
تشخیص کانتورها (Contours) و اشیاء موجود در یک تصویر
آشنایی با نحوه‌ی گروه بندی تصاویر مشابه و مفاهیمی مانند برچسب‌های تصاویر که بیانگر یک گروه از تصاویر هستند.


تهیه تصاویر اعداد انگلیسی جهت آموزش دادن به الگوریتم CvKNearest

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


مجموعه‌ای از تصاویر 0 تا 9 را جمع آوری کرده‌ایم. هر کدام از پوشه‌ها، بیانگر اعدادی از یک خانواده هستند. این تصویر را با فرمت ذیل جمع آوری می‌کنیم:
public class ImageInfo
{
    public Mat Image { set; get; }
    public int ImageGroupId { set; get; }
    public int ImageId { set; get; }
}
به این ترتیب
public IList<ImageInfo> ReadTrainingImages(string path, string ext)
{
    var images = new List<ImageInfo>();
 
    var imageId = 1;
    foreach (var dir in new DirectoryInfo(path).GetDirectories())
    {
        var groupId = int.Parse(dir.Name);
        foreach (var imageFile in dir.GetFiles(ext))
        {
            var image = processTrainingImage(new Mat(imageFile.FullName, LoadMode.GrayScale));
            if (image == null)
            {
                continue;
            }
 
            images.Add(new ImageInfo
            {
                Image = image,
                ImageId = imageId++,
                ImageGroupId = groupId
            });
        }
    }
 
    return images;
}
در متد خواندن تصاویر آموزشی، ابتدا پوشه‌های اصلی مسیر Numbers تصویر ابتدای بحث دریافت می‌شوند. سپس نام هر پوشه، شماره‌ی گروه تصاویر موجود در آن پوشه را تشکیل خواهد داد. به این نام در الگوریتم‌های machine leaning، کلاس هم گفته می‌شود. سپس هر تصویر را با فرمت سیاه و سفید بارگذاری کرده و به لیست تصاویر موجود اضافه می‌کنیم. در اینجا از متد processTrainingImage نیز استفاده شده‌است. هدف از آن بهبود کیفیت تصویر دریافتی جهت کار تشخیص اشیاء است:
private static Mat processTrainingImage(Mat gray)
{
    var threshImage = new Mat();
    Cv2.Threshold(gray, threshImage, Thresh, ThresholdMaxVal, ThresholdType.BinaryInv); // Threshold to find contour
 
    Point[][] contours;
    HiearchyIndex[] hierarchyIndexes;
    Cv2.FindContours(
        threshImage,
        out contours,
        out hierarchyIndexes,
        mode: ContourRetrieval.CComp,
        method: ContourChain.ApproxSimple);
 
    if (contours.Length == 0)
    {
        return null;
    }
 
    Mat result = null;
 
    var contourIndex = 0;
    while ((contourIndex >= 0))
    {
        var contour = contours[contourIndex];
 
        var boundingRect = Cv2.BoundingRect(contour); //Find bounding rect for each contour
        var roi = new Mat(threshImage, boundingRect); //Crop the image
 
        //Cv2.ImShow("src", gray);
        //Cv2.ImShow("roi", roi);
        //Cv.WaitKey(0);
 
        var resizedImage = new Mat();
        var resizedImageFloat = new Mat();
        Cv2.Resize(roi, resizedImage, new Size(10, 10)); //resize to 10X10
        resizedImage.ConvertTo(resizedImageFloat, MatType.CV_32FC1); //convert to float
        result = resizedImageFloat.Reshape(1, 1);
 
        contourIndex = hierarchyIndexes[contourIndex].Next;
    }
 
    return result;
}
عملیات صورت گرفته‌ی در این متد را با تصویر ذیل بهتر می‌توان توضیح داد:


ابتدا تصویر اصلی بارگذاری می‌شود؛ همان تصویر سمت چپ. سپس با استفاده از متد Threshold، شدت نور نواحی مختلف آن یکسان شده و آماده می‌شود برای تشخیص کانتورهای موجود در آن. در ادامه با استفاده از متد FindContours، شیء مرتبط با عدد جاری یافت می‌شود. سپس متد Cv2.BoundingRect مستطیل دربرگیرنده‌ی این شیء را تشخیص می‌دهد (تصویر سمت راست). بر این اساس می‌توان تصویر اصلی ورودی را به یک تصویر کوچکتر که صرفا شامل ناحیه‌ی عدد مدنظر است، تبدیل کرد. در ادامه برای کار با الگوریتم  CvKNearest نیاز است تا این تصویر بهبود یافته را تبدیل به یک ماتریس یک بعدی کردی که روش انجام کار توسط متد Reshape مشاهده می‌کنید.
از همین روش پردازش و بهبود تصویر ورودی، جهت پردازش اعداد یافت شده‌ی در یک تصویر با تعداد زیادی عدد نیز استفاده خواهیم کرد.


آموزش دادن به الگوریتم CvKNearest

تا اینجا تصاویر گروه بندی شده‌ای را خوانده و لیستی از آن‌ها را مطابق فرمت الگوریتم CvKNearest تهیه کردیم. مرحله‌ی بعد، معرفی این لیست به متد Train این الگوریتم است:
public CvKNearest TrainData(IList<ImageInfo> trainingImages)
{
    var samples = new Mat();
    foreach (var trainingImage in trainingImages)
    {
        samples.PushBack(trainingImage.Image);
    }
 
    var labels = trainingImages.Select(x => x.ImageGroupId).ToArray();
    var responses = new Mat(labels.Length, 1, MatType.CV_32SC1, labels);
    var tmp = responses.Reshape(1, 1); //make continuous
    var responseFloat = new Mat();
    tmp.ConvertTo(responseFloat, MatType.CV_32FC1); // Convert  to float
 
 
    var kNearest = new CvKNearest();
    kNearest.Train(samples, responseFloat); // Train with sample and responses
    return kNearest;
}
متد Train دو ورودی دارد. ورودی اول آن یک تصویر است که باید از طریق متد PushBack کلاس Mat تهیه شود. بنابراین لیست تصاویر اصلی را تبدیل به لیستی از Matها خواهیم کرد.
سپس نیاز است لیست گروه‌های متناظر با تصاویر اعداد را تبدیل به فرمت مورد انتظار متد Train کنیم. در اینجا صرفا لیستی از اعداد صحیح را داریم. این لیست نیز باید تبدیل به یک Mat شود که روش انجام آن در متد فوق بیان شده‌است. کلاس Mat سازنده‌ی مخصوصی را جهت تبدیل لیست اعداد، به همراه دارد. این Mat نیز باید تبدیل به یک ماتریس یک بعدی شود که برای این منظور از متد Reshape استفاده شده‌است.


انجام عملیات OCR نهایی

پس از تهیه‌ی لیستی از تصاویر و آموزش دادن آن‌ها به الگوریتم CvKNearest، تنها کاری که باید انجام دهیم، یافتن اعداد در تصویر نمونه‌ی مدنظر و سپس معرفی آن به متد FindNearest الگوریتم CvKNearest است. روش انجام اینکار بسیار شبیه است به روش معرفی شده در متد processTrainingImage که پیشتر بررسی شد:
public void DoOCR(CvKNearest kNearest, string path)
{
    var src = Cv2.ImRead(path);
    Cv2.ImShow("Source", src);
 
    var gray = new Mat();
    Cv2.CvtColor(src, gray, ColorConversion.BgrToGray);
 
    var threshImage = new Mat();
    Cv2.Threshold(gray, threshImage, Thresh, ThresholdMaxVal, ThresholdType.BinaryInv); // Threshold to find contour
 
 
    Point[][] contours;
    HiearchyIndex[] hierarchyIndexes;
    Cv2.FindContours(
        threshImage,
        out contours,
        out hierarchyIndexes,
        mode: ContourRetrieval.CComp,
        method: ContourChain.ApproxSimple);
 
    if (contours.Length == 0)
    {
        throw new NotSupportedException("Couldn't find any object in the image.");
    }
 
    //Create input sample by contour finding and cropping
    var dst = new Mat(src.Rows, src.Cols, MatType.CV_8UC3, Scalar.All(0));
 
    var contourIndex = 0;
    while ((contourIndex >= 0))
    {
        var contour = contours[contourIndex];
 
        var boundingRect = Cv2.BoundingRect(contour); //Find bounding rect for each contour
 
        Cv2.Rectangle(src,
            new Point(boundingRect.X, boundingRect.Y),
            new Point(boundingRect.X + boundingRect.Width, boundingRect.Y + boundingRect.Height),
            new Scalar(0, 0, 255),
            2);
 
        var roi = new Mat(threshImage, boundingRect); //Crop the image
 
        var resizedImage = new Mat();
        var resizedImageFloat = new Mat();
        Cv2.Resize(roi, resizedImage, new Size(10, 10)); //resize to 10X10
        resizedImage.ConvertTo(resizedImageFloat, MatType.CV_32FC1); //convert to float
        var result = resizedImageFloat.Reshape(1, 1);
 
 
        var results = new Mat();
        var neighborResponses = new Mat();
        var dists = new Mat();
        var detectedClass = (int)kNearest.FindNearest(result, 1, results, neighborResponses, dists);
 
        //Console.WriteLine("DetectedClass: {0}", detectedClass);
        //Cv2.ImShow("roi", roi);
        //Cv.WaitKey(0);
 
        //Cv2.ImWrite(string.Format("det_{0}_{1}.png",detectedClass, contourIndex), roi);
 
        Cv2.PutText(
            dst,
            detectedClass.ToString(CultureInfo.InvariantCulture),
            new Point(boundingRect.X, boundingRect.Y + boundingRect.Height),
            0,
            1,
            new Scalar(0, 255, 0),
            2);
 
        contourIndex = hierarchyIndexes[contourIndex].Next;
    }
 
    Cv2.ImShow("Segmented Source", src);
    Cv2.ImShow("Detected", dst);
 
    Cv2.ImWrite("dest.jpg", dst);
 
    Cv2.WaitKey();
}
این عملیات به صورت خلاصه در تصویر ذیل مشخص شده‌است:


ابتدا تصویر اصلی که قرار است عملیات OCR روی آن صورت گیرد، بارگذاری می‌شود. سپس کانتورها و اعداد موجود در آن تشخیص داده می‌شوند. مستطیل‌های قرمز رنگ در برگیرنده‌ی این اعداد را در تصویر دوم مشاهده می‌کنید. سپس این کانتور‌های یافت شده را که شامل یکی از اعداد تشخیص داده شده‌است، تبدیل به یک ماتریس یک بعدی کرده و به متد FindNearest ارسال می‌کنیم. خروجی آن نام گروه یا پوشه‌ای است که این عدد در آن قرار دارد. در همینجا این خروجی را تبدیل به یک رشته کرده و در تصویر سوم با رنگ سبز رنگ نمایش می‌دهیم.
بنابراین در این تصویر، پنجره‌ی segmented image، همان اشیاء تشخیص داده شده‌ی از تصویر اصلی هستند.
پنجره‌ی با زمینه‌ی سیاه رنگ، نتیجه‌ی نهایی OCR است که نسبتا هم دقیق عمل کرده‌است.


کدهای کامل این مثال را از اینجا می‌توانید دریافت کنید.
نظرات مطالب
Extension Methods
لطفا این بحث رو ادامه ندید. در یک سری از دسترسی‌های نویسنده‌ها به زودی تجدید نظر خواهد شد. چه کسانی که ابراز تمایل کرده بودند و فقط وقت من رو جهت ثبت آن‌ها نصف روزی تلف کردند؛ چه کسانی که مطالب سطحی ارسال می‌کنند.
با تشکر

ضمن اینکه یک مطلب را هم مد نظر داشته باشید. اینجا هدف بیشتر ذکر یک سری نکته است به همین جهت اسم سایت tips دارد (نکات ریز).
اسم سایت encyclopedia نیست که برای هر مطلبی قرار باشد کتاب نوشته شود. اگر نوشته شد، چقدر خوب؛ اگر نه ... یک نکته ریز جدید یاد گرفتید. این هم خوب.
همچنین هدف از این سایت خواننده عام با سطح مطالعه و اطلاعات صفر نیست و نبوده. از روز اولش اینطور نبوده و نخواهد بود.

یک مطالب رو هم فراموش نکنید. به قول مولوی: «گر تو بهتر میزنی بستان بزن»
در طی یک ماهی که این سایت در حالت تعلیق بود ... من هر چقدر سایت‌های فارسی زبان فنی برنامه نویسی رو گشتم که دوتا مطلب به درد بخور پیدا کنم ... چیزی نیافتم. هیچی! واقعا دریغ از یک مطلب فنی به درد بخور که منتشر شده باشد. حالا همین عده منتظرند سریع بریزند سر یک نفر شروع کنند به داد و قال.
باز تکرار می‌کنم: «گر تو بهتر میزنی بستان بزن»


نظرات مطالب
غیرمعتبر کردن توکن و یا کوکی سرقت شده در برنامه‌های مبتنی بر ASP.NET Core
یک نکته‌ی تکمیلی
« fingerprintjs » کتابخانه‌ای است برای انتساب یک device-id به کاربر (بر اساس user-agent، اندازه‌ی صفحه، فونت‌های نصب شده، پلاگین‌های در حال استفاده و غیره). برای استفاده‌ی از آن، ابتدا اسکریپت آن باید اضافه شود:
<script src="https://cdn.jsdelivr.net/npm/@fingerprintjs/fingerprintjs@3.12.1/dist/fingerprint2.min.js"></script>
سپس یک کوکی از نتیجه‌ی آن تهیه می‌شود:
string fingerprint = "";
string fingerprintScript = @"<script>
    const fpPromise = FingerprintJS.load();
    fpPromise.then(fp => {
        fp.get().then(result => {
            const values = result.values;
            const fingerprint = values.join('');
            document.cookie = 'DeviceFingerprint=' + fingerprint + '; path=/';
        });
    });
</script>";
Response.Write(fingerprintScript);
و این کوکی که با نام DeviceFingerprint ذخیره شده، به صورت زیر قابل خواندن است:
string fingerprint = Request.Cookies["DeviceFingerprint"]?.Value;
if (fingerprint == null)
{
    // Device fingerprint cookie not found
}
مطالب
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 هیچ ضرری نخواهد داشت و جای دوری نخواهد رفت!



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


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

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

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

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

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

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


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

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

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


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

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

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


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

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

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

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

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


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


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

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

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

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

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


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

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



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

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

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


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

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


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


خلاصه‌ی بحث

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