نظرات مطالب
سفارشی سازی ASP.NET Core Identity - قسمت پنجم - سیاست‌های دسترسی پویا
فقط موردی هست اینکه به چه روشی میشه Identity Core رو به Identity Server متصل کرد براساس مستندات ارائه شده در بخش
نظرات مطالب
سفارشی سازی ASP.NET Core Identity - قسمت چهارم - User Claims
همان قسمت «چگونه به اطلاعات User Claims در سرویس‌های برنامه دسترسی پیدا کنیم؟ » مطلب جاری است:
IHttpContextAccessor را باید در لایه سرویس تزریق کنید: «بررسی روش دسترسی به HttpContext در ASP.NET Core»  
نظرات مطالب
ASP.NET MVC #23
بسته‌های کامل MVC نصب هستند روی سرور؟
ELMAH  را به برنامه اضافه کنید تا جزئیات خطاها را بهتر بتوانید لاگ و مشاهده کنید (مهم).
همچنین این خطاها در قسمت لاگ‌های سرور هم ثبت می‌شوند که بهتر است جزئیات آن‌ها را بررسی کنید.
نظرات مطالب
تبدیل صفحات یک فایل PDF به تصویر، با استفاده از Acrobat SDK
- اگر سرور دار خودتون هستید و می‌تونید روی سرور وابستگی‌های مربوط به نگارش کامل Adobe Acrobat را نصب کنید و همچنین برنامه در حالت Full trust اجرا می‌شود؛ بله.
- راه حل‌های دیگری هم هستند که در قسمت اشتراک‌های سایت مطرح شدند.
نظرات مطالب
یکدست کردن «ی» و «ک» در ASP.NET Core با پیاده‌سازی یک Model Binder سفارشی
+ متد ApplyCorrectYeKe کتابخانه‌ی « DNTPersianUtils.Core » فراتر است از روش‌های متداول موجود.  
مطالب
استفاده از Froala WYSIWYG Editor در ASP.NET
چندی قبل، معرفی ادیتور سبک وزن و مناسبی را تحت عنوان RedActor، در این سایت ملاحظه کردید. زمانیکه این‌کار انجام شد، این ادیتور هم رایگان بود و هم سورس آخرین نگارش آن به سادگی در دسترس. بعد از مدتی، هر دو ویژگی یاد شده‌ی RedActor حذف شدند. پس از آن ادیتور مدرن و بسیار مناسب دیگری به نام Froala منتشر شد که هرچند نگارش‌های تجاری هم دارد، اما سورس آخرین نگارش آن برای عموم قابل دریافت است. در ادامه مروری خواهیم داشت بر نحوه‌ی یکپارچه سازی آن با ASP.NET MVC و همچنین ASP.NET Web forms.


دریافت آخرین نگارش Froala WYSIWYG Editor
برای دریافت فایل‌های آخرین نگارش این ادیتور وب می‌توانید به سایت آن، قسمت دریافت فایل‌ها مراجعه نمائید.
http://editor.froala.com/download
و یا به این آدرس مراجعه کنید:
https://github.com/froala/wysiwyg-editor/releases 


ساختار پروژه و نحوه‌ی کپی فایل‌های آن
در هر دو مثالی که فایل‌های آن‌را از انتهای بحث می‌توانید دریافت کنید، این ساختار رعایت شده است:


فایل‌های CSS و فونت‌های آن، در پوشه‌ی Content قرار گرفته‌اند.
فایل‌های اسکریپت و زبان آن (که دارای زبان فارسی هم هست) در پوشه‌ی Scripts کپی شده‌اند.

یک نکته
فایل font-awesome.css را نیاز است کمی اصلاح کنید. مسیر پوشه‌ی فونت‌های آن اکنون با fonts شروع می‌شود.


تنظیمات اولیه

تفاوتی نمی‌کند که از وب فرم‌ها استفاده می‌کنید یا MVC، نحوه‌ی تعریف و افزودن پیش نیازهای این ادیتور به نحو ذیل است:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>

    <link href="Content/font-awesome.css" rel="stylesheet" />
    <link href="Content/froala_editor.css" rel="stylesheet" />

    <script src="Scripts/jquery-1.10.2.min.js"></script>
    <script src="Scripts/froala_editor.min.js"></script>
    <script src="Scripts/langs/fa.js"></script>
</head>
<body>
    <form id="form1" runat="server">
    </form>
</body>
</html>
دو فایل CSS دارد (آیکن‌های آن و همچنین شیوه نامه‌ی اصلی ادیتور) به همراه سه فایل JS (جی‌کوئری، ادیتور و فایل زبان فارسی آن) که باید در فایل master یا layout سایت اضافه شوند.


استفاده از Froala WYSIWYG Editor در ASP.NET MVC

در ادامه نحوه‌ی فعال سازی ادیتور وب Froala را در یک View برنامه‌های ASP.NET MVC ملاحظه می‌کنید:
@{
    ViewBag.Title = "Index";
}

<style type="text/css">
    /*تنظیم فونت پیش فرض ادیتور*/
    .froala-element {
    }
</style>

@using (Html.BeginForm(actionName: "Index", controllerName: "Home"))
{
    @Html.TextArea(name: "Editor1")
    <input type="submit" value="ارسال" />
}

@section Scripts
{
    <script type="text/javascript">
        $(function () {
            $('#Editor1').editable({
                buttons: ["bold", "italic", "underline", "strikeThrough", "fontFamily",
                    "fontSize", "color", "formatBlock", "align", "insertOrderedList",
                    "insertUnorderedList", "outdent", "indent", "selectAll", "createLink",
                    "insertImage", "insertVideo", "undo", "redo", "html", "save", "inserthorizontalrule"],
                inlineMode: false,
                inverseSkin: true,
                preloaderSrc: '@Url.Content("~/Content/img/preloader.gif")',
                allowedImageTypes: ["jpeg", "jpg", "png"],
                height: 300,
                language: "fa",
                direction: "rtl",
                fontList: ["Tahoma, Geneva", "Arial, Helvetica", "Impact, Charcoal"],
                autosave: true,
                autosaveInterval: 2500,
                saveURL: '@Url.Action("FroalaAutoSave", "Home")',
                saveParams: { postId: "123" },
                spellcheck: true,
                plainPaste: true,
                imageButtons: ["removeImage", "replaceImage", "linkImage"],
                borderColor: '#00008b',
                imageUploadURL: '@Url.Action("FroalaUploadImage", "Home")',
                imageParams: { postId: "123" },
                enableScript: false
            });
        });
    </script>
}
اگر می‌خواهید فونت پیش فرض آن را تنظیم کنید، باید مطابق کدهای ابتدای فایل، ویژگی‌های froala-element را تغییر دهید.
سپس این ادیتور را بر روی المان TextArea قرار گرفته در صفحه، فعال می‌کنیم.
در قسمت مقادیر buttons، تمام حالات ممکن پیش بینی شده‌اند. هر کدام را که نیاز ندارید، حذف کنید.
نحوه‌ی تعریف زبان و راست به چپ بودن این ادیتور را با مقدار دهی پارامترهای language و direction ملاحظه می‌کنید.

پارامترهای autosave، saveURL و saveParams کار تنظیم ارسال خودکار محتوای ادیتور را جهت ذخیره‌ی آن در سرور به عهده دارند. بر اساس مقدار autosaveInterval می‌توان مشخص کرد که هر چند میلی ثانیه یکبار این‌کار باید انجام شود.
        /// <summary>
        /// ذخیره سازی خودکار
        /// </summary>
        [HttpPost]
        [ValidateInput(false)]
        public ActionResult FroalaAutoSave(string body, int? postId) // نام پارامتر بادی را تغییر ندهید
        {
            //todo: save body ...
            return new EmptyResult();
        }
در قسمت سمت سرور هم می‌توان این مقادیر ارسالی را در اکشن متدی که ملاحظه می‌کنید، دریافت کرد.
چون قرار است تگ‌های HTML به سرور ارسال شوند، ویژگی ValidateInput به false تنظیم شده‌است.
saveParams آن، برای مقدار دهی پارامترهای اضافی است که نیاز می‌باشند تا به سرور ارسال شوند. مثلا شماره مطلب جاری نیز به سرور ارسال گردد.
در اینجا نام پارامتری که ارسال می‌گردد، دقیقا مساوی body است. بنابراین آن‌را تغییر ندهید.

پارامترهای imageUploadURL و imageParams برای فعال سازی ذخیره تصاویر آن در سرور کاربرد دارند.
اکشن متد مدیریت کننده‌ی آن به نحو ذیل می‌تواند تعریف شود:
        // todo: مسایل امنیتی آپلود را فراموش نکنید
        /// <summary>
        /// ذخیره سازی تصاویر ارسالی
        /// </summary>
        [HttpPost]
        public ActionResult FroalaUploadImage(HttpPostedFileBase file, int? postId) // نام پارامتر فایل را تغییر ندهید
        {
            var fileName = Path.GetFileName(file.FileName);
            var rootPath = Server.MapPath("~/images/");
            file.SaveAs(Path.Combine(rootPath, fileName));
            return Json(new { link = "images/" + fileName }, JsonRequestBehavior.AllowGet);
        }
در اینجا نام پارامتری که به سرور ارسال می‌گردد، دقیقا معادل file است. بنابراین آن‌را تغییر ندهید.
خروجی آن برای مشخص سازی محل ذخیره سازی تصویر در سرور باید یک خروجی JSON دارای خاصیت و پارامتر link به نحو فوق باشد (این مسیر، یک مسیر نسبی است؛ نسبت به ریشه سایت).
imageParams آن برای مقدار دهی پارامترهای اضافی است که نیاز می‌باشند تا به سرور ارسال شوند. مثلا شماره مطلب جاری نیز به سرور ارسال گردد.


استفاده از Froala WYSIWYG Editor در ASP.NET Web forms
تمام نکاتی که در قسمت تنظیمات ASP.NET MVC در مورد ویژگی‌های سمت کلاینت این ادیتور ذکر شد، در مورد وب فرم‌ها نیز صادق است. فقط قسمت مدیریت سمت سرور آن اندکی تفاوت دارد.
<%@ Page Title="" Language="C#" MasterPageFile="~/Site.Master"
    ValidateRequest="false"
    EnableEventValidation="false"
    AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="FroalaWebFormsTest.Default" %>

<%--اعتبارسنجی ورودی غیرفعال شده چون باید تگ ارسال شود--%>
<%--همچنین در وب کانفیگ هم تنظیم دیگری نیاز دارد--%>

<asp:Content ID="Content1" ContentPlaceHolderID="head" runat="server">
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="ContentPlaceHolder1" runat="server">
    <%--حالت کلاینت آی دی بهتر است تنظیم شود در اینجا--%>
    <asp:TextBox ID="txtEditor" ClientIDMode="Static"
        runat="server" Height="199px" TextMode="MultiLine" Width="447px"></asp:TextBox>
    <br />
    <asp:Button ID="btnSave" runat="server" OnClick="btnSave_Click" Text="ارسال" />

    <style type="text/css">
        /*تنظیم فونت پیش فرض ادیتور*/
        .froala-element {
        }
    </style>

    <script type="text/javascript">
        $(function () {
            $('#txtEditor').editable({
                buttons: ["bold", "italic", "underline", "strikeThrough", "fontFamily",
                    "fontSize", "color", "formatBlock", "align", "insertOrderedList",
                    "insertUnorderedList", "outdent", "indent", "selectAll", "createLink",
                    "insertImage", "insertVideo", "undo", "redo", "html", "save", "inserthorizontalrule"],
                inlineMode: false,
                inverseSkin: true,
                preloaderSrc: 'Content/img/preloader.gif',
                allowedImageTypes: ["jpeg", "jpg", "png"],
                height: 300,
                language: "fa",
                direction: "rtl",
                fontList: ["Tahoma, Geneva", "Arial, Helvetica", "Impact, Charcoal"],
                autosave: true,
                autosaveInterval: 2500,
                saveURL: 'FroalaHandler.ashx',
                saveParams: { postId: "123" },
                spellcheck: true,
                plainPaste: true,
                imageButtons: ["removeImage", "replaceImage", "linkImage"],
                borderColor: '#00008b',
                imageUploadURL: 'FroalaHandler.ashx',
                imageParams: { postId: "123" },
                enableScript: false
            });
        });
    </script>
</asp:Content>
همانطور که ملاحظه می‌کنید،  ValidateRequest صفحه به false تنظیم شده و همچنین در وب کانفیگ httpRuntime requestValidationMode به نگارش 2 تنظیم گردیده‌است تا بتوان توسط این ادیتور تگ‌های ارسالی را به سرور ارسال کرد.
به علاوه ClientIDMode=Static نیز تنظیم شده‌است، تا بتوان از ID تکست باکس قرار گرفته در صفحه، به سادگی در کدهای سمت کاربر جی‌کوئری استفاده کرد.
اگر دقت کرده باشید، save urlها اینبار به فایل FroalaHandler.ashx اشاره می‌کنند. محتوای این Genric handler را ذیل مشاهده می‌کنید:
using System.IO;
using System.Web;
using System.Web.Script.Serialization;

namespace FroalaWebFormsTest
{
    public class FroalaHandler : IHttpHandler
    {
        //todo: برای اینکارها بهتر است از وب ای پی آی استفاده شود
        //todo: یا دو هندلر مجزا یکی برای تصاویر و دیگری برای ذخیره سازی متن

        public void ProcessRequest(HttpContext context)
        {
            var body = context.Request.Form["body"];
            var postId = context.Request.Form["postId"];
            if (!string.IsNullOrWhiteSpace(body) && !string.IsNullOrWhiteSpace(postId))
            {
                //todo: save changes

                context.Response.ContentType = "text/plain";
                context.Response.Write("");
                context.Response.End();
            }

            var files = context.Request.Files;
            if (files.Keys.Count > 0)
            {
                foreach (string fileKey in files)
                {
                    var file = context.Request.Files[fileKey];
                    if (file == null || file.ContentLength == 0)
                        continue;

                    //todo: در اینجا مسایل امنیتی آپلود فراموش نشود
                    var fileName = Path.GetFileName(file.FileName);
                    var rootPath = context.Server.MapPath("~/images/");
                    file.SaveAs(Path.Combine(rootPath, fileName));


                    var json = new JavaScriptSerializer().Serialize(new { link = "images/" + fileName });
                    // البته اینجا یک فایل بیشتر ارسال نمی‌شود
                    context.Response.ContentType = "text/plain";
                    context.Response.Write(json);
                    context.Response.End();
                }
            }

            context.Response.ContentType = "text/plain";
            context.Response.Write("");
            context.Response.End();
        }

        public bool IsReusable
        {
            get { return false; }
        }
    }
}
در اینجا نحوه‌ی مدیریت سمت سرور auto save و همچنین ارسال تصاویر ادیتور Froala ، ذکر شده‌اند. با استفاده از context.Request.Form می‌توان به عناصر ارسالی به سرور دسترسی پیدا کرد. همچنین توسط context.Request.Files، اگر فایلی ارسال شده بود، ذخیره شده و نهایتا خروجی JSON مدنظر بازگشت داده می‌شود.


یک نکته‌ی امنیتی مهم
<location path="upload">
  <system.webServer>
    <handlers accessPolicy="Read" />
  </system.webServer>
</location>
تنظیم فوق را در web.config سایت، جهت Read only کردن پوشه‌ی ارسال تصاویر، حتما مدنظر داشته باشید. در اینجا فرض شده‌است که پوشه‌ی uploads قرار است قابلیت اجرای فایل‌های پویا را نداشته باشد.


کدهای کامل این مطلب را در ادامه می‌توانید دریافت کنید
Froala-Sample
 
نظرات مطالب
شروع به کار با AngularJS 2.0 و TypeScript - قسمت هشتم - دریافت اطلاعات از سرور
با سلام. در یک پروژه Angular 5 داده‌های ComboBox‌ها باید در سمت کلاینت کش شوند. بنده در ngOnInit مربوط به app.component عملیات دریافت این داده‌ها را با http.get انجام میدهم و داده‌ها در یک پروپرتی Observable کش می‌شوند. حالا در فرم هایی که به این داده‌ها نیاز دارم در متد ngOnInit اقدام به دریافت داده‌ها از کش میکنم اما هنوز داده‌ها دریافت نشده اند (مشکل همزمانی).
همچنین بنده lifecycle Angular رو بررسی کردم. از ngAfterContentInit هم برای دریافت داده‌ها در فرم‌ها استفاده کردم اما باز هم موقع اجرای این متد(ngAfterContentInit   ) هنور متد subscribe مربوط به http.get اجرا نشده است.
 لطفا در صورت امکان راهنمایی کنید. با تشکر
نظرات مطالب
انجام کارهای زمانبندی شده در برنامه‌های ASP.NET توسط DNT Scheduler
- بجای اینکار (اضافه و حذف کردن سرویس‌ها در زمان اجرای برنامه که با ری‌استارت برنامه در سمت سرور غیرمعتبر می‌شود)، بهتر است با توجه به اینکه این وظایف به صورت یک سرویس ثبت می‌شوند، یک سرویس سفارشی فعال یا غیرفعالسازی را تعریف کنید و آن‌را به سازنده‌ی این وظایف تزریق و استفاده کنید. سپس زمانیکه حلقه‌ی انجام وظایف به RunAsync رسید، در اینجا فرصت خواهید داشت تا سرویس سفارشی جدید تزریق شده را بررسی کرده و از فعال بودن یا نبودن این وظیفه مطلع شوید (برای مثال این سرویس بر اساس نامی که به آن ارسال می‌کنید، به بانک اطلاعاتی مراجعه کرده و روشن و یا خاموش بودن آن‌را بررسی کند. تنظیم بانک اطلاعاتی آن‌را هم واگذار کنید به قسمت مدیریتی برنامه).  
+ این نوع Taskهای پویا را باید در ابتدای کار برنامه و در کلاس آغازین آن، به صورت زیر معرفی کنید:
services.AddTransient<DoEnableProductTasks>();
مطالب
React 16x - قسمت 15 - مسیریابی - بخش 1 - تعریف و تنظیم مسیریابی‌ها
React برخلاف Angular، دارای قابلیت‌های توکار مسیریابی نیست و تنها کاری را که انجام می‌دهد همان رندر View است که تا اینجا بررسی کردیم. به همین جهت در این قسمت ابتدا یک برنامه‌ی عمومی و ساده را با نصب کتابخانه‌ی ثالثی برای توضیح مفاهیم مسیریابی، شامل کار با پارامترهای مسیریابی، کوئری استرینگ‌ها، هدایت کاربران به صفحات دیگر، مدیریت صفحات یافت نشده و مسیریابی تو در تو، بررسی می‌کنیم. سپس به عنوان تمرین، همان برنامه‌ی طراحی گریدی را که تا قسمت 14 تکمیل کردیم، با معرفی مسیریابی بهبود خواهیم بخشید.


برپایی پیش‌نیازها

در اینجا برای بررسی مسیریابی، یک پروژه‌ی جدید React را ایجاد می‌کنیم.
> create-react-app sample-15
> cd sample-15
> npm start
در ادامه توئیتر بوت استرپ 4 را نیز نصب می‌کنیم. برای این منظور پس از باز کردن پوشه‌ی اصلی برنامه توسط VSCode، دکمه‌های ctrl+` را فشرده (ctrl+back-tick) و دستور زیر را در ترمینال ظاهر شده وارد کنید:
> npm install --save bootstrap
سپس برای افزودن فایل bootstrap.css به پروژه‌ی React خود، ابتدای فایل index.js را به نحو زیر ویرایش خواهیم کرد:
import "bootstrap/dist/css/bootstrap.css";
این import به صورت خودکار توسط webpack ای که در پشت صحنه کار bundling & minification برنامه را انجام می‌دهد، مورد استفاده قرار می‌گیرد.

همچنین کتابخانه‌ی ثالث بسیار معروف react-router-dom را نیز نصب می‌کنیم:
> npm i react-router-dom --save
نگارش dom آن مخصوص کار با مرورگر است و نگارش native آن (react-router-native)، مخصوص React Native و تولید برنامه‌های موبایل می‌باشد که مبحث دیگری است.


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

پس از نصب کتابخانه‌ی react-router-dom، برای افزودن آن به برنامه و فعالسازی مسیریابی، به فایل index.js مراجعه کرده و import آن‌را به ابتدای فایل اضافه می‌کنیم:
import { BrowserRouter } from "react-router-dom";
سپس کامپوننت App را داخل BrowserRouter، محصور می‌کنیم:
ReactDOM.render(
  <BrowserRouter>
    <App />
  </BrowserRouter>,
  document.getElementById("root")
);
کار BrowserRouter، محصور سازی مدیریت تاریخچه‌ی مرور صفحات در مرورگر و انتقال آن به درخت کامپوننت‌های React است. به این ترتیب در هر قسمتی از درخت کامپوننت‌های برنامه می‌توان از History object مرورگر استفاده کرد.


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

در ادامه باید مسیریابی‌های خود را ثبت کنیم؛ به این معنا که بر اساس URL خاصی، چه کامپوننتی باید رندر شود. به همین جهت پوشه‌ی جدید src\components را ایجاد کرده و کامپوننت src\components\navbar.jsx را که یک کامپوننت تابعی بدون حالت است، در آن تعریف می‌کنیم:
import React from "react";

const NavBar = () => {
  return (
    <nav className="navbar bg-dark navbar-dark navbar-expand-sm">
      <div className="navbar-nav">
        <a className="nav-item nav-link" href="/">
          Home
        </a>
        <a className="nav-item nav-link" href="/products">
          Products
        </a>
        <a className="nav-item nav-link" href="/posts/2018/06">
          Posts
        </a>
        <a className="nav-item nav-link" href="/admin">
          Admin
        </a>
      </div>
    </nav>
  );
};

export default NavBar;
کار آن نمایش منوی بالای صفحه است.


سپس به فایل app.js مراجعه کرده و ساختار آن‌را به صورت زیر، جهت درج این NavBar، ویرایش می‌کنیم تا سبب رندر و نمایش منوی راهبری در مرورگر شود:
import "./App.css";

import React, { Component } from "react";

import NavBar from "./components/navbar";

class App extends Component {
  render() {
    return (
      <div>
        <NavBar />
      </div>
    );
  }
}

export default App;
در ادامه در کامپوننت App، یک container را اضافه می‌کنیم که قرار است در آن بر اساس URL رسیده، محتوای کامپوننت خاصی رندر شود. به همین جهت کامپوننت Route را در اینجا قرار می‌دهیم و در آن یک یا چند Route را ثبت می‌کنیم:
import "./App.css";

import React, { Component } from "react";
import { Route } from "react-router-dom";

import Dashboard from "./components/admin/dashboard";
import Home from "./components/home";
import NavBar from "./components/navbar";
import Posts from "./components/posts";
import Products from "./components/products";

class App extends Component {
  render() {
    return (
      <div>
        <NavBar />
        <div className="container">
          <Route path="/products" component={Products} />
          <Route path="/posts" component={Posts} />
          <Route path="/admin" component={Dashboard} />
          <Route path="/" component={Home} />
        </div>
      </div>
    );
  }
}

export default App;
Route نیز یک کامپوننت است؛ همانند تمام کامپوننت‌هایی که تاکنون تعریف کردیم و دارای چند ویژگی است که به صورت props به آن منتقل می‌شوند. برای نمونه خاصیت path آن به مسیر products/ در مرورگر اشاره می‌کند و سبب رندر کامپوننت جدید Products که در بالای این ماژول نیز import شده، می‌شود. در اینجا سه مسیریابی دیگر را نیز ثبت کرده‌ایم که کامپوننت‌های جدید متناظر با آن‌ها به صورت زیر تعریف می‌شوند:

کامپوننت جدید src\components\products.jsx جهت رندر لیست آرایه‌ی اشیاء product:
import React, { Component } from "react";

class Products extends Component {
  state = {
    products: [
      { id: 1, name: "Product 1" },
      { id: 2, name: "Product 2" },
      { id: 3, name: "Product 3" }
    ]
  };

  render() {
    return (
      <div>
        <h1>Products</h1>
        <ul>
          {this.state.products.map(product => (
            <li key={product.id}>
              <a href={`/products/${product.id}`}>{product.name}</a>
            </li>
          ))}
        </ul>
      </div>
    );
  }
}

export default Products;

کامپوننت بدون حالت تابعی src\components\home.jsx با این محتوا:
import React from "react";

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

export default Home;

کامپوننت بدون حالت تابعی src\components\posts.jsx با این محتوا:
import React from "react";

const Posts = () => {
  return (
    <div>
      <h1>Posts</h1>
      Year: , Month:
    </div>
  );
};

export default Posts;

کامپوننت بدون حالت تابعی src\components\admin\dashboard.jsx در پوشه‌ی جدید admin با این محتوا:
import React from "react";

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

export default Dashboard;
تا اینجا اگر برنامه را اجرا کنیم، در اولین بار نمایش آن، شاهد رندر کامپوننت Home خواهیم بود. اما اگر در همین حالت بر روی لیست products، در منوی بالای صفحه کلیک کنیم، هم کامپوننت products و هم کامپونت home، هر دو با هم رندر شده‌اند. یک چنین رفتاری را در سایر صفحات نیز می‌توان مشاهده کرد:



معرفی کامپوننت Switch

<div className="container">
  <Route path="/products" component={Products} />
  <Route path="/posts" component={Posts} />
  <Route path="/admin" component={Dashboard} />
  <Route path="/" component={Home} />
</div>
الگوریتم تطابق کامپوننت Route، ابتدا بررسی می‌کند که آیا برای مثال URL ای با path مساوی products/ شروع شده‌است؟ اگر اینطور است، کامپوننت متناظر با آن را که برای نمونه در اینجا Products است، رندر می‌کند. این حالت جهت مسیری مانند products/new/ نیز صدق می‌کند؛ چون این URL نیز با products/ شروع شده‌است. همچنین این تطابق‌گر، مسیر ثبت شده‌ی برای کامپوننت Home را نیز چون با / شروع شده‌است و جزء ابتدایی مسیر products/ است هم رندر می‌کند. به همین جهت است که وقتی مسیر products/ را درخواست می‌دهیم، در صفحه دو کامپوننت products و home، با هم رندر می‌شوند.
یک روش حل این مشکل، استفاده از ویژگی exact است:
<Route path="/" exact component={Home} />
به این ترتیب اگر مسیر درخواستی دقیقا مساوی / بود، کامپوننت Home را رندر خواهد کرد. با این تغییر، با مراجعه‌ی به آدرس products/، دیگر رندر کامپوننت home را شاهد نخواهیم بود:


راه دوم رفع این مشکل، استفاده از کامپوننت Switch است. به همین جهت ابتدا این کامپوننت را import می‌کنیم:
import { Route, Switch } from "react-router-dom";
سپس تمام Routeهای تعریف شده را داخل Switch محصور خواهیم کرد:
class App extends Component {
  render() {
    return (
      <div>
        <NavBar />
        <div className="container">
          <Switch>
            <Route path="/products" component={Products} />
            <Route path="/posts" component={Posts} />
            <Route path="/admin" component={Dashboard} />
            <Route path="/"  component={Home} />
          </Switch>
        </div>
      </div>
    );
  }
}
Switch اولین مسیریابی را که با URL داده شده تطابق داشته باشد، رندر می‌کند. همچنین در اینجا دیگر نیازی به ذکر ویژگی exact نیز وجود ندارد. بنابراین با استفاده از Switch اگر مسیر داده شده، products/ باشد، مسیریابی تعریف شده‌ی با آن یافت می‌شود که در اینجا اولین Route تعریف شده‌است. سپس کار رندر کامپوننت آن‌را انجام داده و از مابقی مسیریابی‌های تعریف شده، صرفنظر می‌کند.
بنابراین هنگام کار با Switch، ترتیب مسیریابی‌های تعریف شده مهم است و باید از یک مسیریابی ویژه شروع شده و به یک مسیریابی عمومی مانند / ختم شود.


معرفی کامپوننت Link

تا اینجا اگر برنامه را اجرا کرده باشید و پیشتر سابقه‌ی کار با برنامه‌های SPA یا Single page applications را داشته باشید، یک مشکل دیگر را نیز احساس کرده‌اید: سیستم مسیریابی که تا کنون تعریف کرده‌ایم، به صورت SPA عمل نمی‌کند. یعنی به ازای هربار کلیک بر روی لینک‌های منوی راهبری سایت، یکبار دیگر به طور کامل برنامه از صفر بارگذاری مجدد می‌شود و تمام اسکریپت‌های آن مجددا از سرور دریافت شده و رندر خواهند شد. این مورد را در برگه‌ی network ابزارهای توسعه دهندگان مرورگر خود بهتر می‌توانید مشاهده کنید. به ازای هر درخواست نمایش کامپوننتی، تعدادی درخواست HTTP به سمت سرور ارسال می‌شوند که برای دریافت صفحه‌ی index و bundle.js برنامه هستند. اما در برنامه‌های SPA، مانند جمیل، با هربار کلیک بر روی لینکی، شاهد ریفرش و بارگذاری مجدد کل آن صفحه نیستیم و تنها اطلاعات موجود در قسمت container به روز می‌شوند.

یک نکته: در اینجا ممکن است دو درخواست websocket و info را نیز مشاهده کنید. این دو مرتبط به hot module reloading هستند که با ذخیره‌ی برنامه در ادیتور VSCode، بلافاصله سبب به روز رسانی و ریفرش برنامه در مرورگر می‌شوند.

برای رفع مشکل SPA نبودن برنامه، باید به کامپوننت NavBar مراجعه کرده و تمام anchor‌های استاندارد تعریف شده‌ی در آن‌را با کامپوننت Link جایگزین کنیم:
import React from "react";
import { Link } from "react-router-dom";

const NavBar = () => {
  return (
    <nav className="navbar bg-dark navbar-dark navbar-expand-sm">
      <div className="navbar-nav">
        <Link className="nav-item nav-link" to="/">
          Home
        </Link>
        <Link className="nav-item nav-link" to="/products">
          Products
        </Link>
        <Link className="nav-item nav-link" to="/posts/2018/06">
          Posts
        </Link>
        <Link className="nav-item nav-link" to="/admin">
          Admin
        </Link>
      </div>
    </nav>
  );
};

export default NavBar;
در اینجا ابتدا کامپوننت Link را در ابتدای ماژول، import کردیم. سپس تمام anchorها را یافته و تبدیل به کامپوننت Link نمودیم. همچنین href آن‌ها را نیز به ویژگی to تغییر دادیم.
با این تغییرات اگر برنامه را اجرا کنیم، اینبار با کلیک بر روی هر لینک، دیگر شاهد بارگذاری کامل صفحه در مرورگر نخواهیم بود؛ بلکه تنها قسمت container ای که کامپوننت Route مسیریابی در آن درج شده‌است، به روز رسانی می‌شود و این عملیات نیز بسیار سریع است؛ از این جهت که محتوای این کامپوننت‌ها از همان bundle.js حاوی تمام کدهای برنامه تامین می‌شود و این فایل تنها یکبار در آغاز برنامه از سرور خوانده شده و سپس توسط مرورگر پردازش می‌شود. بنابراین در برنامه‌های SPA، برخلاف برنامه‌های وب معمولی، هربار که کاربر آدرس متفاوتی را انتخاب می‌کند، بارگذاری مجدد برنامه و خوانده شدن محتوای متناظر از سرور صورت نمی‌گیرد؛ این محتوا هم اکنون در bundle.js برنامه مهیا است و قابلیت استفاده‌ی آنی را دارد.

اما کامپوننت Link چگونه کار می‌کند؟
کامپوننت لینک در نهایت همان anchor‌های استاندارد را رندر می‌کند؛ اما به هر کدام یک onClick را نیز اضافه می‌کند که سبب جلوگیری از رفتار پیش‌فرض anchor می‌شود. به همین جهت مرورگر درخواست اضافه‌ای را به سمت سرور ارسال نمی‌کند. در اینجا مدیریت کننده‌ی onClick، تنها Url بالای صفحه را در مرورگر تغییر می‌دهد. اکنون که Url تغییر کرده‌است، یکی از مسیریابی‌های تعریف شده، با این Url تطابق یافته و سپس کامپوننت متناظر با آن‌را رندر می‌کند.


بررسی Route props


اگر بر روی لینک نمایش products در منوی راهبری سایت کلیک کرده و سپس به خروجی افزونه‌ی react developer tools دقت کنیم (تصویر فوق)، مشاهده می‌کنیم که این کامپوننت هم اکنون تعدادی خاصیت را به صورت props در اختیار دارد؛ مانند history (امکان هدایت کاربر را به صفحه‌ای دیگر دارد)، location (آدرس جاری برنامه) و match (اطلاعاتی در مورد الگوریتم تطابق مسیر). کار تنظیم این props، توسط کامپوننت Route ای که کار ثبت مسیریابی‌ها را انجام می‌دهد، صورت می‌گیرد. به عبارتی کامپوننت Route، محصور کننده‌ی کامپوننتی است که آن‌را به عنوان پارامتر، دریافت و در صورت تطابق با مسیر جاری، آن‌را رندر می‌کند. همچنین در این بین کار تزریق خواص props یاد شده را نیز انجام می‌دهد.


ارسال props سفارشی در حین مسیریابی به کامپوننت‌ها

همانطور که بررسی کردیم، کامپوننت Route، حداقل سه خاصیت props را به کامپوننت‌هایی که رندر می‌کند، تزریق خواهد کرد. اما در اینجا برای تزریق خواص سفارشی چگونه باید عمل کرد؟
در حین کار با کامپوننت Route، برای ارسال props اضافی، بجای استفاده از ویژگی component آن، باید از ویژگی render استفاده کرد:
<Route
  path="/products"
  render={() => <Products param1="123" param2="456" />}
/>
در اینجا کار با تعریف یک arrow function شروع می‌شود که در نهایت المان کامپوننت مدنظر را همانند روش متداولی که برای تعریف تمام کامپوننت‌های React و تنظیم ویژگی‌های آن‌ها استفاده می‌شود، بازگشت می‌دهد که تاثیر آن‌را در خروجی افزونه‌ی react developer tools بهتر می‌توان مشاهده کرد:


البته اگر به تصویر فوق دقت کنید، سایر خواص پیشینی که تزریق شده بودند مانند history، location و match، دیگر در اینجا حضور ندارند. برای رفع این مشکل باید تعریف arrow function انجام شده را به صورت زیر تغییر داد:
<Route
  path="/products"
  render={props => (
    <Products param1="123" param2="456" {...props} />
  )}
/>
ابتدا پارامتر arrow function را به همان props تنظیم می‌کنیم. سپس با استفاده از spread operator، این props را در المان JSX تعریف شده، گسترده و تزریق می‌کنیم؛ با این خروجی:



کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: sample-15-part-01.zip
مطالب
AngularJS #1
پیش از اینکه آموزش AngularJs را شروع کنیم بهتر است با مفهوم برنامه‌های تک صفحه ای وب  و یا  Single Page Web Applications آشنا شویم؛ چرا که AngularJS برای توسعه هر چه ساده‌تر و قوی‌تر این گونه برنامه‌ها متولد شده است.

Single Page Application
برای درک چگونگی کارکرد این برنامه ها، مثالی را میزنیم که هر روزه با آن سرو کار دارید، یکی از نمونه‌های کامل و قدرتمند برنامه‌های Single Page Application و یا به اختصار SPA، سرویس پست الکترونیکی Google و یا همان Gmail است.
اجازه بدهید تا ویژگی‌های SPA را با بررسی Gmail انجام دهم، تا به درک روشنی از آن برسید:

Reload نشدن صفحات
در Gmail هیچ گاه صفحه Reload و یا اصطلاحا بارگیری مجدد نمی‌شود. برای مثال، وقتی شما لیست ایمیل‌های خود را مشاهده میکنید و سپس بر روی یکی از آن‌ها کلیک میکنید، بدون اینکه به صفحه ای دیگر هدایت شوید؛ایمیل مورد نظر را میبینید. در حقیقت تمامی اطلاعات در همان صفحه نمایش داده می‌شوند و بر عکس وب سایت‌های معمول است که از صفحه ای به صفحه‌ی دیگر هدایت میشوید ، در یک صفحه تمام کارهای مورد نیاز خود را انجام می‌دهید و احتیاجی به بارگیری مجدد صفحات نیست. با توجه به این صحبت‌ها برای توسعه دهنده‌های وب آشکار است که تکنیک AJAX، نقشی اساسی در این گونه برنامه‌ها دارد، چون کلیه‌ی عملیات ارتباط با سرور در پشت زمینه انجام می‌شوند.

تغییر URL در نوار آدرس مرورگر
وقتی شما بر روی یک ایمیل کلیک می‌کنید و آن ایمیل را بدون Reload شدن مجدد صفحه مشاهده می‌کنید، آدرس صفحه در مرورگر نیز تغییر می‌کند. خب مزیت این ویژگی چیست؟ مزیت این ویژگی در این است که هر ایمیل شما دارای یک آدرس منحصر به فرد است و به شما امکان Bookmark کردن آن لینک، باز کردن آن در یک Tab جدید و یا حتی ارسال آن به دوستان خود را دارید. حتی اگر این مطلب را جدا از Gmail در نظر بگیریم، به موتور‌های جست و جو کمک می‌کند، تا هر صفحه را جداگانه Index کنند؛ جدا از اینکه وبسایت ما SPA است. همچنین این کار یک مزیت مهم دیگر نیز دارد؛ و آن کار کردن کلیدهای back و forward مرورگر، برای بازگشت به صفحات پیمایش شده قبلی است.
شاید قبل از بیان این ویژگی با خود گفته باشید که پیاده سازی Reload نشدن صفحات با AJAX آن چنان کار پیچیده ای نیست. بله درست است، اما آیا شما قبل از این راه حلی برای تغییر URL اندیشیده بودید؟ مطمئنا شما هم صفحات وب زیادی را دیده اید که همه‌ی صفحات آن دارای یک URL در نوار آدرس مرورگر هستند و هیچگاه تغییر نمی‌کنند و با باز کردن یک لینک در یک Tab جدید، باز همان صفحه‌ی تکراری را مشاهده می‌کنند! و یا بدتر از همه که دکمه‌ی back مرورگر غیر عادی عمل می‌کند. بله، اینها تنها تعدادی از صدها مشکلات رایج سیستم‌های نوشته شده ای است که سعی کردند همه‌ی کارها در یک صفحه انجام شود.

Cache شدن اطلاعات دریافتی
شاید خیلی‌ها ویژگی‌های فوق را برای یک SPA کافی بدانند، اما تعدادی هم مانند نگارنده وجود یک کمبود را حس می‌کنند و آن کش شدن اطلاعات دریافتی در مرورگر است. Gmail این امکان را به خوبی پیاده سازی کرده است. لیست ایمیل‌های دریافتی در بار اول از سرور دریافت می‌شود، سپس شما بر روی یک ایمیل کلیک و آن را مشاهده می‌کنید. حال به لیست ایمیل‌های دریافتی بازگردید، آیا رفت و برگشتی به سرور انجام می‌شود؟ مسلما خیر. حتی اگر دوباره بر روی آن ایمیل مشاهده شده ، کلیک کنید، بدون رفت و برگشتی به سرور آن ایمیل را مشاهده می‌کنید.
کش شدن اطلاعات سبب می‌شود که بار سرور خیلی کاهش یابد و رفت و آمدهای بیهوده صورت نگیرد. کش شدن داده‌ها یک مزیت دیگر نیز دارد و آن تبدیل برنامه‌های معمول وب stateless به برنامه‌های شبه دسکتاپ state full است. 
  
تکنیک AJAX در پیاده سازی امکانات فوق نقشی اساسی را بازی می‌کند. کمی به عقب برمیگردیم یعنی زمانی که AJAX برای اولین بار مطرح شد و هدف اصلی به وجود آمدن آن پیاده سازی برنامه‌های وب به شکل دسکتاپ بود و این کار از طریق انجام تمامی ارتباطات سرور با  XMLHttpRequest  امکان پذیر می‌شد. شاید آن زمان با توجه به محدودیت تکنولوژی‌ها موجود این کار به صورت تمام و کمال امکان پذیر نبود، اما امروزه به بهترین شکل ممکن قابل پیاده سازی است.

 شاید اکنون این سوال پیش بیاید که چرا باید وبسایت خود را به شکل SPA طراحی کنیم؟
برای پاسخ دادن به این سوال باید گفت که سیستم‌های وب  امروزی به دو دسته‌ی زیر تقسیم می‌شوند:
Web Documents و یا همان وب سایت‌های معمول
Web Applications و یا همان Single Page Web Applications
اگر هدف شما طراحی یک وب سایت معمول است که هدف آن، نمایش یک سری اطلاعات است و به قولی دارای محتواست، مطمئنا پیاده سازی این سیستم به صورت SPA کاری بیهوده به نظر می‌آید؛ ولی اگر هدفتان نوشتن سیستم هایی مثل Gmail، Google Maps، Azure، Facebook و ... است، پیاده سازی آن‌ها به صورت وب سایت‌های معمولی، غیر معقول به نظر می‌آید. حتی بخش‌های مدیریتی یک وبسایت هم می‌تواند به خوبی توسط SPA پیاده سازی شود، چرا که واقعا برای مدیریت اطلاعات یک وب سایت احتیاجی نیست، که  از این صفحه به آن صفحه جا به جا شد.

معرفی کتابخانه‌ی AngularJS
AngularJS فریم ورکی متن باز و نوشته شده به زبان جاوا اسکریپت است. هدف از به وجود آمدن این فریم ورک، توسعه هر چه ساده‌تر SPA‌ها با الگوی طراحی MVC و تست پذیری هر چه آسان‌تر آن‌ها است. این فریم ورک توسط یکی از محققان Google در سال 2009 به وجود آمد. بعد‌ها این فریم ورک تحت مجوز MIT به صورت متن باز در آمد و اکنون گوگل آن را حمایت می‌کند و توسط هزاران توسعه دهنده در سرتاسر دنیا، توسعه داده می‌شود.
قبل از اینکه به بررسی ویژگی‌های Angular بپردازم، بهتر است ابتدا مطلبی درباره‌ی به کارگیری Angular از Brad Green که کارمند گوگل است، بیان کنم.
در سال 2009 تیمی در گوگل مشغول انجام پروژه ای به نام Google Feedback بودند. آن‌ها سعی داشتند تا در طی چند ماه، به سرعت کد‌های خوب و تست پذیر بنویسند. پس از 6 ماه کدنویسی، نتیجه‌ی کار 17000 خط کد شد. در آن موقع یکی از اعضای تیم به نام Misko Hevery، ادعا کرد که می‌تواند کل این پروژه را در دو هفته به کمک کتابخانه‌ی متن بازی که در اوقات فراغت توسعه داده است، بازنویسی کند. Misko نتوانست در دو هفته این کار را انجام دهد. اما پس از سه هفته همه‌ی اعضای تیم را شگفت زده کرد. نتیجه‌ی کار تنها 1500 خط بود! همین باعث شد که ما بفهمیم که، Misko بر روی چیزی کاری میکند که ارزش دنبال کردن دارد.
پس از آن قضیه Misko و Brad بر روی Angular کار کردند و اکنون هم Angular توسط تیمی در گوگل و هزاران توسعه دهنده‌ی متن باز حرفه ای در سرتاسر جهان، درحال توسعه است.
فکر کنم همین داستان ذکر شده، قدرت فوق العاده زیاد این فریم ورک را برای همگان آشکار سازد.

ویژگی‌های AngularJS:

قالب‌های سمت کاربر (Client Side Templates): انگولار دارای یک template engine قدرتمند برای تعریف قالب است.

پیروی از الگوی طراحی MVC: انگولار، الگوی طراحی MVC را برای توسعه پیشنهاد می‌دهد و امکانات زیادی برای توسعه هر چه راحت‌تر با این الگو فراهم کرده است.

Data Binding: امکان تعریف انقیاد داده دوطرفه (Two-Way Data Binding) در این فریم ورک به راحتی هرچه تمام، امکان پذیر است.

Dependency Injection: این فریم ورک برای دریافت وابستگی‌های تعریف شده، دارای یک سیستم تزریق وابستگی توکار است.

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

تعریف Directive‌های سفارشی: یکی از جذاب‌ترین و قدرتمند‌ترین امکانات این فریم ورک، تعریف Directive‌های سفارشی است.  Directive ها، امکان توسعه HTML را فراهم کرده اند. توسعه‌ی HTML اکنون در قالب Web Components‌ها فراهم شده است، اما هنوز هم خیلی از مرورگر‌های جدید نیز از آن پشتیبانی نمی‌کنند.

فرمت کردن اطلاعات با استفاده از فیلترهای سفارشی: با استفاده از فیلترها میتوانید چگونگی الحاق شدن اطلاعات را برای نمایش به کاربر تایین کنید ؛ انگولار همراه با فیلترهای گوناگون مختلفی عرضه میشود که میتوان برایه مثال به فیلتر currency ، date ،uppercase کردن رشته‌ها و .... اشاره کرد همچنین شما محدود به فیلترهای تعریف شده در انگولار نیستید و آزادید که فیلترهای سفارشی خودتان را نیز تعریف کنید.

سیستم Routing: دارا بودن سیستم Routing  قدرتمند، توسعه SPA‌ها را بسیار ساده کرده است.

سیستم اعتبار سنجی: Angular دارای سیستم اعتبار سنجی توکار قدرتمند برای بررسی داده‌های ورودی است.

سرویس تو کار برای ارتباط با سرور: Angular دارای سرویس پیش فرض ارتباط با سرور به صورت AJAX است.

تست پذیری: Angular دارای بستری آماده برای تست کردن برنامه‌های نوشته شده است و از Unit Tests و Integrated End-to-End Test هم پشتیبانی می‌کند.

جامعه‌ی متن باز بسیار قوی
   
این‌ها فقط یک مرور کلی بر توانایی‌های این فریم ورک بود و در ادامه هر کدام از این ویژگی را به صورت دقیق بررسی خواهیم کرد.
در مقاله‌ی بعدی، به چگونگی نصب AngularJS خواهیم پرداخت. سپس، اولین کد خود را با استفاده از آن خواهیم نوشت و مطالب Client Side Templates و MVC را دقیق‌تر بررسی خواهیم کرد.