مطالب
خلاصه اشتراک‌های روز یک شنبه 1390/07/03
مطالب
اعتبارسنجی درخواست های http$ با استفاده از یک Interceptor
پیش نیاز :

هدایت خودکار کاربر به صفحه لاگین در حین اعمال Ajax ایی  
 Angular Interceptors



ابتدا مشکل و هدف را بیان می‌کنیم:

مشکل: کاربر در صفحه‌ای حضور دارد که نیاز به اعتبارسنجی داشته و مدت اعتبار کاربر نیز تمام شده است، ولی هنوز در صفحه‌ای که نباید حضور داشته باشد، حضور دارد و بدتر از آن این است که می‌تواند درخواست‌های بی نتیجه‌ای را نیز ارسال کند.
هدف: کاربر را سریعا به صفحه‌ای که به آن تعلق دارد هدایت کنیم ( یعنی صفحه‌ی ورود به سیستم ). 
 
و حالا از ابتدا پروسه را دنبال می‌کنیم. یک Controller سمت سرور داریم به این صورت :
   [Authorize(Roles = AuthorizeRole.SuperAdministrator)]
    public partial class HomeController : Controller
    {
        [HttpPost]
        [AngularValidateAntiForgeryToken]
        public virtual JsonResult GetUserInfo()
        {
            var userInfoViewModel = _applicationUserManager.GetUserInfoById(User.Identity.GetUserId());
            return Json(userInfoViewModel);
        }
    }
همانطور که مشاهده می‌کنید از Authorize  پیش فرض استفاده کرده‌ایم. حالا سمت کلاینت با استفاده از HTTP می‌خواهیم درخواستی را ارسال کنیم.

یعنی با کدی همانند کد زیر:
  $scope.getUserInfo = function() {
            $http({
                    method: 'POST',
                    url: 'Home/GetUserInfo',
                    headers: $scope.getHeaders()
                }).
                success(function(data, status, headers, config) {
                    $scope.userInfo = data;
                }).
                error(function(data, status, headers, config) {

                }).then(function(res) {

                });
        }

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


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

نکته : زمانیکه status برابر با 200 هست، یعنی درخواست OK می‌باشد. ( در پیوست ، لیست تمامی کدها قرار داده شده است )

حالا فرض کنید کاربر در صفحه حضور دارد و به هر دلیلی اعتبار حضور کاربر منقضی شده است و حالا پس از مدتی کاربر درخواستی را به سرور ارسال می‌کند و می‌خواهد اطلاعات خودش را مشاهده کند.

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

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


راه حل چیست ؟

ابتدا باید برای درخواست‌های Ajax ایی اعتبارسنجی را اعمال کنیم. برای این کار باید یک Attribute جدید بسازیم. یعنی به این صورت :

  [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = true)]
    public class MyCustomAuthorize : AuthorizeAttribute
    {
        protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
        {
            if (filterContext.HttpContext.Request.IsAjaxRequest())
            {
                // یعنی اعتبارسنجی نشده است
                filterContext.HttpContext.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
                filterContext.HttpContext.Response.End();
            }

            base.HandleUnauthorizedRequest(filterContext);
        }
    }
با استفاده از Attribute  بالا ما درخواست‌های ای‌جکسی را نیز اعتبارسنجی می‌کنیم.

توجه : کد کامل‌تر همراه با توضیحات در بخش پیش نیازها آمده است.

حالا در سمت کلاینت نیز به این صورت عمل می‌کنیم :

       $http({
                    method: 'POST',
                    url: 'Home/GetUserInfo',
                    headers: $scope.getHeaders()
                }).
                success(function(data, status, headers, config) {
                    $scope.userInfo = data;
                }).
                error(function(data, status, headers, config) {
                    if (status === 401) {
                        // you are not authorized
                    }
                }).then(function(res) {

                });




حالا دیگر متوجه خواهیم شد که کاربر اعتبارسنجی نشده است، یا اعتبار آن منقضی شده است و می‌توانیم کاربر را به مسیر "ورود به سیستم" هدایت کنیم.

در console مرورگر نیز خطای زیر رخ می‌دهد :

POST http://localhost:000000/Administrator/Home/GetUserInfo 401 (Unauthorized)
اکنون به هدف خودمان رسیده‌ایم. اما برای هر درخواست باید این دستوارت را تکرار کنیم ؟! قطعا خیر.

حالا باید از یک Interceptor استفاده و درخواست‌های HTTP خودمان را مدیریت کنیم. برای این منظور ما یک Interceptor جدید را همانند کدهای زیر می‌نویسیم:
factory('AuthorizedInterceptor', function ($q) {
        return {
            response: function(response) {
                return response || $q.when(response);
            },
            responseError: function(rejection) {
                if (rejection.status === 401) {
                    // you are not authorized
                }
                return $q.reject(rejection);
            }
        };
    })

همانطور که مشاهده می‌کنید کدهای خطای درخواست http$ را در Interceptor  قرار دادیم. حالا کاربر درخواستی را ارسال می‌کند و ما با توجه به این Interceptor متوجه خواهیم شد که آیا اعتبار آن منقضی شده است یا خیر و در صورت منقضی شدن، کاربر را به صفحه‌ی ورود هدایت خواهیم کرد. یعنی بدین صورت :

 


در خروجی بالا می‌بینید که کاربر، اعتبارسنجی نشده است و آن را به مسیر ورود هدایت خواهیم کرد.

با Interceptor بالا دیگر نیازی نیست برای هر درخواستی این وضعیت را چک کنیم و به صورت خودکار این چک کردن رخ خواهد داد.


پیوست : http_status_codes.rar

مطالب
اجزاء معماری سیستم عامل اندروید (قسمت اول معماری امنیتی اندروید) :: بخش سوم
همانطور که در مطالب قبلی توضیح داده شد، اندروید بر روی هسته‌ی لینوکس نگارش 2.6 به بالا اجرا می‌شود. در این مطلب قصد داریم تا نگاهی به معماری امنیتی اندروید داشته باشیم.
Privilege Separation به چه معناست؟
هسته‌ی اندروید یک مدل جداسازی امتیازی (Privilege separation) را در زمان اجرای برنامه‌های کاربردی اجرا می‌کند. این مورد به این معنا است که مانند سیستم یونیکس، سیستم‌عامل اندروید به یک برنامه نیاز دارد تا تنها با شناسه‌ی کاربر خود و شناسه‌ی گروه خاص خودش اجرا شود. بخش‌های معماری سیستم اندروید به این روش از هم جدا شده‌اند. Privilege Separation یک ویژگی امنیتی مهم است؛ زیرا رایج‌ترین انواع حملات را انکار می‌کند و در نتیجه یکی از اولویت‌های یک برنامه می‌باشد. از آنجایی که عملیات جداسازی امتیازی را هسته انجام می‌دهد، لذا این مورد یکی از مباحث پر اهمیت در طراحی هسته می‌باشد که به شدت مورد توجه خواهد بود. تنها فلسفه پشت این طرح این است که اطمینان حاصل شود، هیچ برنامه‌ی کاربردی نمی‌تواند به کد یا داده‌های دیگر برنامه‌ها و یا به دستگاه کاربر و یا خود سیستم نفوذ کند. یک برنامه ممکن است مستقیما از فهرست تماس دستگاه یا تقویم خوانده نشود! این ویژگی همچنین به عنوان sandboxing شناخته می‌شود.
Permissions چگونه کار می‌کند؟
اجازه دهید با یک مثال ساده توضیح دهم: ما برنامه‌ای را داریم که صدا را ضبط کرده و آن‌را از طریق میکروفن دستگاه پخش می‌کند. برای اینکه برنامه به‌درستی کار کند، توسعه دهنده باید یک درخواست دسترسی به RECORD_AUDIO را برای اجازه دسترسی به ضبط صدا، در فایل AndroidManifest.xml بفرستد. این کار به برنامه ما اجازه می‌دهد تا از مؤلفه‌های سیستم که ضبط صدا را کنترل می‌کنند، استفاده کنیم. یک سؤال اینجا مطرح می‌شود که «چه کسی تصمیم می‌گیرد که این دسترسی داده شود یا اینکه تکذیب شود؟»
فقط و فقط هسته‌ی اندروید به کاربر نهایی اجازه می‌دهد تا این فرآیند تایید نهایی را انجام دهد؛ نه هیچ سیستم دیگر یا کاربر دیگری! زمانیکه کاربر، برنامه ما را نصب می‌کند، این کار او را تشویق می‌کند تا این عملیات را انجام دهد. به این نکته دقت کنید که عجله ای برای این دسترسی وجود ندارد و هر زمانیکه برنامه نصب شد، اتفاق خواهد افتاد و این مجوز دسترسی تنها در زمان نصب به کاربر داده می‌شود نه قبل از آن. (به تصویر زیر دقت کنید)

 اگر ما درخواست خود را برای دسترسی به RECORD_AUDIO ثبت نکنیم یا اگر صاحب دستگاه تائید نکند که پس از درخواست ما اجازه استفاده از آن را بدهد، آنگاه یک استثناء به حافظه‌ی مجازی (VM) داده خواهد شد و برنامه با شکست روبرو می‌شود. این مفهوم برای توسعه دهنده‌است که بداند در خواستی را صادر کرده است یا خیر؟ پس برای درخواست مجوز مورد نظر باید برچسب زیر، در فایل AndroidManifest.xml درج شود.
<uses-permission android:name="android.permission.RECORD_AUDIO" />

Application Code Signing
هر برنامه‌ی کاربردی که بر روی سیستم‌عامل اندروید اجرا می‌شود، باید امضاء شود. اندروید از گواهی توسعه دهندگان فردی برای شناسایی آن‌ها و ایجاد روابط اعتمادسازی استفاده می‌کند که در میان کاربردهای مختلفی که در سیستم‌عامل اجرا می‌شوند پیاده می‌شود. 
سیستم‌عامل مجاز به اجرای برنامه‌ای بدون امضاء نخواهد بود. 
استفاده‌ی از یک مرجع، برای صدور امضای یک برنامه لازم نیست؛ چرا که سیستم امنیتی اندروید تنها از یک امضای تک گانه که توسعه دهنده در برنامه قرار داده‌است پیروی می‌کند! مجوزهای کنترلی برنامه مانند امضاء در طول نصب برنامه از طریق هسته‌ی اندروید بررسی شده و ضمانت و تایید خواهند شد. تمامی این فرآیندها به صورت خودکار و بلادرنگ در پشت پرده به سرعت اتفاق می‌افتند! در این صورت اگر گواهی اعتماد (certificate) توسط توسعه دهنده منقضی شود و یا به نحوی مورد اطمینان برای دستگاه نباشد، تمامی عملیات‌های ذکر شده بر روی برنامه به صورت عادی، ادامه خواهند یافت.
مطالب
آموزش Prism #1
امروزه تقریبا تمام کسانی که پروژه‌های WPF یا Silverlight رو توسعه می‌دهند با مدل برنامه نویسی MVVM آشنایی دارند. فریم ورک‌های مختلفی برای توسعه پروژه‌ها به صورت MVVM وجود دارد. نظیر:
  • MVVM Light
  • Prism
  • Caliburn
  • Cinch
  • WAF
  • Catel
  • Onyx
  • MVVM helpers
  • و...

هر کدوم از فریم ورک‌های بالا مزایا، معایب و طرفداران خاص خودشون رو دارند(^) ولی به جرات می‌تونیم Prism رو به عنوان قوی‌ترین فریم ورک برای پیاده سازی پروژهای بزرگ و قوی و ماژولار با تکنولوژی WPF یا Silverlight بنامیم. در این پست به معرفی و بررسی مفاهیم اولیه Prism خواهیم پرداخت و در پست‌های دیگر به پیاده سازی عملی همراه با مثال می‌پردازیم.

*اگر به هر دلیلی مایل به یادگیری و استفاده از Prism نیستید، بهتون پیشنهاد می‌کنم از WAF استفاده کنید.

پیش نیازها:

برای یادگیری PRISM ابتدا باید با مفاهیم زیر در WPF یا Silverlight آشنایی داشته باشید.(فرض بر این است که به UserControl و Xaml و Dependency Properties، تسلط کامل دارید)

  • Data binding 
  • Resources
  • Commands
  • Behaviors

چرا Prism ؟

  • Prism به صورت کامل از Modular Programming برای پروژه‌های WPF و Silverlight پشتیانی می‌کند*
  • از Prism هم می‌توانیم در پروژه‌های WPF استفاده کنیم و هم Silverlight.
  • Prism به صورت کامل از الگوی MVVM برای پیاده سازی پروژه‌ها پشتیبانی می‌کند.
  • پیاده سازی مفاهیمی نظیر Composite Command و Command Behavior و Asynchronous Interacion  به راحتی در Prism امکان پذیر است.
  • مفاهیم تزریق وابستگی به صورت توکار در Prism فراهم است که برای پیاده سازی این مفاهیم  به طور پیش فرض امکان استفاده از UnityContainer و MEF در Prism تدارک دیده شده است.
  • پیاده سازی Region navigation در Prism به راحتی امکان پذیر است.
  • به وسیله امکان Event Aggregation به راحتی می‌توانیم بین ماژول‌های مختلف ارتباط برقرار کنیم.

*توضیح درباره برنامه‌های ماژولار

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

Prism امکاناتی رو برای طراحی و توسعه این گونه پروژه‌ها به صورت ماژولار فراهم کرده است:

  • ابتدا باید نام و مکان هر ماژول رو به Prism معرفی کنیم که می‌تونیم اون‌ها رو در کد یا Xaml یا Configuration File تعریف کنیم.
  • با استفاده از Metadata باید وابستگی‌ها و مقادیر اولیه برای هر ماژول مشخص شود.
  • با کمک تزریق وابستگی‌ها ارتباطات بین ماژول‌ها میسر می‌شود.
  • ماژول مورد نظر به دو صورت OnDemand و Available  لود خواهد شد.

در شکل زیر مراحل بالا قابل مشاهده است:

Bootstrapper چیست؟

در هر پروژه ماژولار (مختص Prism نیست) برای اینکه ماژول‌های مختلف یک پروژه، قابلیت استفاده به صورت یک پارچه رو در یک Application  داشته باشند باید مفهومی به نام Bootstapper رو پیاده سازی کنیم که وظیفه اون شناسایی و پیکربندی و لود ماژول هاست. در Prism دو نوع Bootstrapper پیش فرض وجود دارد.

  1. MefBootstrapper : کلاس پایه Bootstrapper که مبنای آن MEF است. اگر قصد استفاده از MEF رو در پروژه‌های خود دارید(^) Bootstrapper شما باید از این کلاس ارث ببرد.
  2. UnityBootstrapper : کلاس پایه Bootstrapper که مبنای آن UnityContainer است. اگر قصد استفاده از UnityContainer یا Service Locator (^) رو در پروژه‌های خود دارید Bootstrapper شما باید از این کلاس ارث ببرد. 

تصویری از ارتباط Bootstrapper با ماژول‌های سیستم

مفهوم Shell

در پروژه‌های WPF، در فایل App.xaml توسط یک Uri نقطه شروع پروژه را تعیین می‌کنیم. در پروژه‌های Silverlight به وسیله خاصیت RootVisual نقطه شروع سیستم تعیین می‌شود. در Prism نقطه شروع پروژه توسط bootsrapper تعیین می‌شود. دلیل این امر این است که Shell در پروژه‌های مبتنی بر Prism متکی بر Region Manager است. از Region  برای لود و نمایش ماژول‌ها استفاده خواهیم کرد.

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

مطالب
خواندنی‌های 27 فروردین

  • اولین محصول نگارش 2010 مایکروسافت برای دریافت، exchange server 2010 beta (میل سرور)
مطابق سایت رسمی فوق اینطور به نظر میرسه که مایکروسافت قصد جدا سازی برنامه‌ها و مفهوم Office Web applications را از SharePoint دارد (هر چند مانند سابق توانایی یکپارچگی بی‌نظیری بین آفیس و شپرپوینت وجود خواهد داشت)
سایر اولین سری محصولات 2010 ، شامل Microsoft Office 2010 ، Microsoft Visio 2010 و Microsoft Project 2010 خواهند بود.
  • لیستی از 70+145 برگه مرجعه (+ و +)
  • مجموعه انتشارات مایکروسافت 25 امین سالگرد تاسیس خود را جشن می‌گیرد. به همین منظور دو کتاب زیر را به رایگان برای دریافت قرار داده‌اند:

عنوانی رو که براش انتخاب کردن جالب است "the world's most advanced open source database"
مطالب
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 2.0 و TypeScript - قسمت هشتم - دریافت اطلاعات از سرور
اغلب برنامه‌های AngularJS 2.0، اطلاعات خود را از طریق پروتکل HTTP، از سرور دریافت می‌کنند. برنامه یک درخواست Get را صادر کرده و سپس سرور پاسخ مناسبی را ارائه می‌دهد.


مقدمه‌ای بر RxJS

اگر به پیشنیازهای نصب AngularJS 2.0 در قسمت اول این سری دقت کرده باشید، یکی از موارد آن، RxJS است:
"dependencies": {
    "rxjs": "5.0.0-beta.2"
 },
یک Observable، آرایه‌ای است که اعضای آن به صورت غیر همزمان (asynchronously) در طول زمان دریافت می‌شوند. برای مثال پس از شروع یک عملیات async، ابتدا عنصر اول آرایه دریافت می‌شود، پس از مدتی عنصر دوم و در آخر عنصر سوم آن. به همین جهت از Observable‌ها برای مدیریت داده‌های async مانند دریافت اطلاعات از یک وب سرور، استفاده می‌شود.
قرار است Observableها به ES 2016 یا نگارش پس از ES 6 اضافه شوند و یکی از پیشنهادات آن هستند. اما هم اکنون AngularJS 2.0 از این امکان، توسط یک کتابخانه‌ی ثالث، به نام reactive extensions یا Rx، استفاده می‌کند. از RxJS در سرویس HTTP و همچنین مدیریت سیستم رخدادهای AngularJS 2.0 استفاده می‌شود. Observableها امکانی را فراهم می‌کنند تا به ازای دریافت هر اطلاعات async از سرور، بتوان توسط رخداد‌هایی از وقوع آن‌ها مطلع شد.

در نگارش قبلی AngularJS از Promises برای مدیریت اعمال غیرهمزمان استفاده می‌شد. Observableها تفاوت‌های قابل ملاحظه‌ای با Promises دارند:
- یک Promise تنها یک مقدار، یا خطا را بر می‌گرداند؛ اما یک Observable چندین مقدار را در طول یک بازه‌ی زمانی باز می‌گرداند.
- برخلاف Promises، می‌توان عملیات یک Observable را لغو کرد.
- Observableها از عملگرهایی مانند map، reduce، filter و غیره نیز پشتیبانی می‌کنند.

البته باید عنوان کرد که هنوز هم می‌توان از Promises در صورت تمایل در AngularJS 2.0 نیز استفاده کرد.


تنظیمات اولیه‌ی کار با RxJS در AngularJS 2.0

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

ب) اکنون فایل app.component.ts را گشوده و سرویس HTTP را به آن اضافه می‌کنیم. با نحوه‌ی ثبت سرویس‌ها در قسمت قبل آشنا شدیم:
import { Component } from 'angular2/core';
import { HTTP_PROVIDERS } from 'angular2/http';
import 'rxjs/Rx';   // Load all features
 
import { ProductListComponent } from './products/product-list.component';
import { ProductService } from './products/product.service';
 
@Component({
    selector: 'pm-app',
    template:`
    <div><h1>{{pageTitle}}</h1>
        <pm-products></pm-products>
    </div>
    `,
    directives: [ProductListComponent],
    providers: [
        ProductService,
        HTTP_PROVIDERS
    ]
})
export class AppComponent {
    pageTitle: string = "DNT AngularJS 2.0 APP";
}
از آنجائیکه می‌خواهیم سرویس HTTP، در تمام کامپوننت‌های برنامه در دسترس باشد، آن‌را در بالاترین سطح سلسه مراتب کامپوننت‌های موجود، یا همان کامپوننت ریشه‌ی سایت، ثبت و معرفی می‌کنیم. بنابراین سرویس توکار HTTP یا HTTP_PROVIDERS به لیست پروایدرها، اضافه شده‌است.

ج) پس از آن نیاز است importهای متناظر نیز به ابتدای ماژول فعلی، جهت شناسایی این سرویس و همچنین امکانات rx.js اضافه شوند.
تعریف 'import 'rxjs/Rx به این شکل، به module loader اعلام می‌کند که این کتابخانه را بارگذاری کن، اما چیزی را import نکن. هنگامیکه این کتابخانه بارگذاری می‌شود، کدهای جاوا اسکریپتی آن اجرا شده و سبب می‌شوند که عملگرهای ویژه‌ی Observable آن مانند map و filter نیز در دسترس برنامه قرار گیرند.


ساخت یک سرویس سمت سرور بازگشت لیست محصولات به صورت JSON

چون در ادامه می‌خواهیم لیست محصولات را از سرور دریافت کنیم، برنامه‌ی ASP.NET MVC فعلی را اندکی تغییر می‌دهیم تا این لیست را به صورت JSON بازگشت دهد.
بنابراین ابتدا کلاس مدل محصولات را به نحو ذیل به پوشه‌ی Models اضافه کرده:
namespace MVC5Angular2.Models
{
    public class Product
    {
        public int ProductId { set; get; }
        public string ProductName { set; get; }
        public string ProductCode { set; get; }
        public string ReleaseDate { set; get; }
        public decimal Price { set; get; }
        public string Description { set; get; }
        public double StarRating { set; get; }
        public string ImageUrl { set; get; }
    }
}
و سپس اکشن متد Products، لیست محصولات فرضی این سرویس را بازگشت می‌دهد:
using System.Collections.Generic;
using System.Text;
using System.Web.Mvc;
using MVC5Angular2.Models;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
 
namespace MVC5Angular2.Controllers
{
    public class HomeController : Controller
    {
        // GET: Home
        public ActionResult Index()
        {
            return View();
        }
 
        public ActionResult Products()
        {
            var products = new List<Product>
            {
               new Product
               {
                    ProductId= 2,
                    ProductName= "Garden Cart",
                    ProductCode= "GDN-0023",
                    ReleaseDate= "March 18, 2016",
                    Description= "15 gallon capacity rolling garden cart",
                    Price= (decimal) 32.99,
                    StarRating= 4.2,
                    ImageUrl= "app/assets/images/garden_cart.png"
               },
               new Product
               {
                    ProductId= 5,
                    ProductName= "Hammer",
                    ProductCode= "TBX-0048",
                    ReleaseDate= "May 21, 2016",
                    Description= "Curved claw steel hammer",
                    Price= (decimal) 8.9,
                    StarRating= 4.8,
                    ImageUrl= "app/assets/images/rejon_Hammer.png"
               }
            };
 
            return new ContentResult
            {
                Content = JsonConvert.SerializeObject(products, new JsonSerializerSettings
                {
                    ContractResolver = new CamelCasePropertyNamesContractResolver()
                }),
                ContentType = "application/json",
                ContentEncoding = Encoding.UTF8
            };
        }
    }
}
در اینجا از JSON.NET جهت بازگشت camel case نام خواص مورد نیاز در سمت کاربر، استفاده شده‌است.
برای مطالعه‌ی بیشتر:
«استفاده از JSON.NET در ASP.NET MVC»
«تنظیمات و نکات کاربردی کتابخانه‌ی JSON.NET»

به این ترتیب، آدرس http://localhost:2222/home/products، خروجی JSON سرویس لیست محصولات را در مثال جاری، ارائه می‌دهد.


ارسال یک درخواست HTTP به سرور توسط AngularJS 2.0

اکنون پس از تنظیمات ثبت و معرفی سرویس HTTP و همچنین برپایی یک سرویس سمت سرور ارائه‌ی لیست محصولات، می‌خواهیم سرویس ProductService را که در قسمت قبل ایجاد کردیم (فایل product.service.ts)، جهت دریافت لیست محصولات از سمت سرور، تغییر دهیم:
import { Injectable } from 'angular2/core';
import { IProduct } from './product';
import { Http, Response } from 'angular2/http';
import { Observable } from 'rxjs/Observable';
 
@Injectable()
export class ProductService {
    private _productUrl = '/home/products';
 
    constructor(private _http: Http) { }
 
    getProducts(): Observable<IProduct[]> {
        return this._http.get(this._productUrl)
                         .map((response: Response) => <IProduct[]>response.json())
                         .do(data => console.log("All: " + JSON.stringify(data)))
                         .catch(this.handleError);
    }
 
    private handleError(error: Response) {
        console.error(error);
        return Observable.throw(error.json().error || 'Server error');
    }
}
از آنجائیکه این سرویس دارای یک وابستگی تزریق شده‌است، ذکر ()Injectable@، پیش از تعریف نام کلاس، ضروری است.
در سازنده‌ی کلاس ProductService، کار تزریق وابستگی سرویس Http انجام شده‌است. به این ترتیب با استفاده از متغیر خصوصی http_، می‌توان در کلاس جاری به امکانات این سرویس دسترسی یافت (همان «تزریق سرویس‌ها به کامپوننت‌ها» در قسمت قبل).
سپس متد get آن، یک درخواست HTTP از نوع GET را به آدرس مشخص شده‌ی در متغیر productUrl_ ارسال می‌کند (یا همان سرویس سمت سرور برنامه).
سرویس Http و همچنین شیء Response آن در ماژول‌های Http و Response قرار دارند که در ابتدای صفحه import شده‌اند.

متد http get یک Observable را بازگشت می‌دهد که در نهایت خروجی این متد نیز به همان <[]Observable<IProduct، تنظیم شده‌است. Observable یک شیء جنریک است و در اینجا نوع آن، آرایه‌ای از محصولات درنظر گرفته شده‌است.
اکنون که امضای این متد تغییر یافته است (پیش از این صرفا یک آرایه‌ی ساده از محصولات بود)، استفاده کننده (در کلاس ProductListComponent) باید به تغییرات آن از طریق متد subscribe گوش فرا دهد.
فعلا در کلاس جاری، پس از پایان کار دریافت اطلاعات از سرور، اطلاعات نهایی در متد map در دسترس قرار می‌گیرد (که یکی از عملگرهای RxJs است). کار متد map، اصطلاحا projection است. این متد، هر عضو دریافتی از خروجی سرور را به فرمتی جدید نگاشت می‌کند.
هر درخواست HTTP، در اصل یک عملیات async است. یعنی در اینجا توالی که در اختیار Observable ما قرار می‌گیرد، تنها یک المان دارد که همان شیء HTTP Response است.
بنابراین کار متد map فوق، تبدیل شیء خروجی از سرور، به آرایه‌ای از محصولات است.
در اینجا یک سری کدهای مدیریت استثناءها را نیز در صورت بروز مشکلی می‌توان تعریف کرد. برای مثال در اینجا متد catch، کار پردازش خطاهای رخ داده را انجام می‌دهد.
از متد do جهت لاگ کردن عملیات رخ داده و داده‌های دریافتی در کنسول developer tools مرورگرها استفاده شده‌است.

یک نکته:
اگر خروجی JSON از سرور، برای مثال داخل خاصیتی به نام data محصور شده بود، بجای ()response.json می‌بایستی از response.json().data استفاده می‌شد.


گوش فرا دادن به Observable دریافتی از سرور

تا اینجا یک درخواست HTTP GET را به سمت سرور ارسال کردیم و خروجی آن به صورت Observable در اختیار ما است. اکنون نیاز است کدهای ProductListComponent را جهت گوش فرا دادن به این Observable تغییر دهیم. برای این منظور فایل product-list.component.ts را گشوده و تغییرات ذیل را به آن اعمال کنید:
errorMessage: string;
ngOnInit(): void {
    //console.log('In OnInit');
    this._productService.getProducts()
                        .subscribe(
                              products => this.products = products,
                              error => this.errorMessage = <any>error);
}
در کلاس ProductListComponent، در متد ngOnInit که در آن کار آغاز و مقدار دهی وابستگی‌های کامپوننت انجام می‌شود، متد ()productService.getProducts_ فراخوانی شده‌است. این متد یک Observable را بر می‌گرداند. بنابراین برای پردازش نتیجه‌ی آن نیاز است متد subscribe را در ادامه‌ی آن، زنجیر وار ذکر کرد.
اولین پارامتر متد subscribe، کار دریافت نتایج حاصل را به عهده دارد. برای مثال اگر حاصل عملیات در طی سه مرحله صورت گیرد، سه بار نتیجه‌ی دریافتی را می‌توان در اینجا پردازش کرد. البته همانطور که عنوان شد، یک عملیات غیرهمزمان HTTP، تنها در طی یک مرحله، HTTP Response را دریافت می‌کند؛ بنابراین، پارامتر اول متد subscribe نیز تنها یکبار اجرا می‌شود. در اینجا فرصت خواهیم داشت تا آرایه‌ی دریافتی حاصل از متد map قسمت قبل را به خاصیت عمومی products کلاس جاری نسبت دهیم.
پارامتر دوم متد subscribe در صورت شکست عملیات فراخوانی می‌شود. در اینجا حاصل آن به خاصیت جدید errorMessage نسبت داده شده‌است.


اکنون برنامه را مجددا اجرا کنید، هنوز باید لیست محصولات، مانند قبل نمایش داده شود.


یک نکته
اگر برنامه را اجرا کردید و خروجی مشاهده نشد، به کنسول developer tools مرورگر مراجعه کنید؛ احتمالا خطای ذیل در آن درج شده‌است:
 EXCEPTION: No provider for Http!
به این معنا که پروایدر HTTP یا همان HTTP_PROVIDERS، جایی معرفی نشده‌است. البته مشکلی از این لحاظ در برنامه وجود ندارد و این پروایدر در بالاترین سطح ممکن و در فایل app.component.ts ثبت شده‌است. مشکل اینجا است که مرورگر، فایل قدیمی http://localhost:2222/app/app.component.js را کش کرده‌است (به همراه تمام اسکریپت‌های دیگر) و این فایل قدیمی، فاقد تعریف سرویس HTTP است. بنابراین با حذف کش مرورگر و دریافت فایل‌های js جدید، مشکل برطرف خواهد شد.


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


خلاصه‌ی بحث

برای کار با سرور و ارسال درخواست‌های HTTP، ابتدا نیاز است مدخل تعریف http.dev.js به index.html اضافه شود و سپس HTTP_PROVIDERS را در بالاترین سطح کامپوننت‌های تعریف شده، ثبت و معرفی کرد. پس از آن نیاز است RxJs را نیز import کرد. در ادامه، سرویس دریافت لیست محصولات، وابستگی سرویس HTTP را توسط سازنده‌ی خود دریافت کرده و از آن برای صدور یک فرمان HTTP GET استفاده می‌کند. سپس با استفاده از متد map، کار نگاشت شیء Response دریافتی، به فرمت مناسب مدنظر، صورت می‌گیرد.
در ادامه هر کلاسی که نیاز دارد با این کلاس سرویس دریافت اطلاعات کار کند، متد subscribe را فراخوانی کرده و نتیجه‌ی عملیات را پردازش می‌کند.
نظرات مطالب
خلاصه‌ای کوتاه در مورد WinRT
- بحث وب که سرجای خودش همانند سابق هست و هیچ فرقی نمی‌کند. برنامه‌های ASP.NET روی سرور اجرا می‌شوند و عموما روی سرور بجز یک سری سرویس‌های ویندوز NT‌ ، هیچ نرم افزار دیگری نصب نخواهد شد. مثلا IIS یا مثلا SQL Server و در همین حد. حتی عموما سرورها حتی مونیتور هم ندارند و با ریموت دسکتاپ یک سری کارهای مدیریتی آن‌ها را انجام می‌دهند و این کارها هم طوری نیست که هر روز تغییر کند. یکبار سرور تنظیم می‌شود که حداقل یکسال یا بیشتر کار کند. این مورد اصلا تغییری نخواهد داشت. بحث سمت سرور است. بنابراین سرمایه گذاری روی ASP.NET خوب است و شامل این بحث ویندوز 8 یا ویندوزهای بعدی نمی‌شود؛ چون این‌ها (WinRT) سمت کاربر محسوب می‌شوند.
- از این جهت که رابط‌های کاربری مبتنی بر WinRT ، یا بر پایه XAML است یا HTML/CSS ، یادگیری WPF و یا سیلورلایت (که قسمتی از WPF را به ارث برده) مفید خواهند بود؛ از این لحاظ که پایه رابط کاربری هر دوی این‌ها هم XAML است و اساسا طراحی XAML از اینجا به WinRT منتقل شده.

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

- یادگیری سی++ همیشه مفید است. حتی در کره مریخ هم تاجایی که اطلاع دارم (!) یک کامپایلر سی++ وجود دارد و می‌شود با آن برنامه‌ی Hello world را کامپایل کرد. اگر باور ندارید از این لینوکسی‌ها بپرسید!
مطالب
کار با Docker بر روی ویندوز - قسمت اول - Container چیست؟
نصب بسیاری از نرم افزارها، کاری مشکل است

فرض کنید می‌خواهید یک فایل ویدیویی با قالب m4v را بر روی تلویزیون خود نمایش دهید؛ اما تلویزیون شما تنها از فایل‌های mp4، پشتیبانی می‌کند. برای رفع این مشکل نیاز به یک نرم افزار تبدیل کننده‌ی فرمت‌های ویدیویی را داریم و یکی از قوی‌ترین‌های آن‌ها، FFmpeg است. اگر به سایت آن مراجعه کنید، لینک دانلود آن به یک فایل tar.bz2 ختم می‌شود که حاوی سورس آن است! هرچند در قسمتی از آن، فایل‌های نهایی کامپایل شده‌ی مخصوص سیستم عامل‌های مختلف را نیز می‌توانید پیدا کنید، اما باز هم با انبوهی از لینک‌ها مواجه خواهید شد که دقیقا مشخص نیست کدام را باید دریافت کرد و آیا نگارش دریافت شده، با سیستم عامل فعلی سازگار است یا خیر.
همانطور که مشاهده می‌کنید، هنوز هم شروع به کار با نرم افزارهای مختلف برای بسیاری از کاربران، کاری مشکل و طاقت‌فرسا است. در اینجا شاید این سؤال مطرح شود که این موضوع چه ربطی به Docker (Docker) و کانتینرها (Containers) دارد؟ تمام هیاهویی که پیرامون Docker ایجاد شده‌است، در اصل جهت ساده سازی نصب، راه اندازی و تعامل با نرم افزارهای مختلف است.


چالش‌های پیش روی یافتن نرم افزارهای مناسب


این روزها بیشتر نرم افزارهای مورد نیاز خود را از اینترنت تهیه می‌کنیم. اولین مرحله‌ی آن و اولین چالشی که در اینجا وجود دارد، یافتن نرم افزاری با مشخصات مدنظر است. برای نمونه حتی اگر با FFmpeg آشنا نیز باشید، به سادگی مشخص نیست که برای سیستم عامل و معماری خاص پردازنده‌ی آن، دقیقا کدام نگارش آن‌را از چه آدرسی می‌توان دریافت کرد. پس از یافتن نرم افزار و نگارش مدنظر، مرحله‌ی بعد، استخراج محتویات آن از یک فایل zip و یا اجرای برنامه‌ی نصاب آن است و مرحله‌ی آخر، اجرای این برنامه می‌باشد.
بنابراین اولین چالش، یافتن محلی برای دریافت نرم افزار است:
-  این روزها برای بعضی از سکوهای کاری، App Storeهایی وجود دارند که می‌توان از آنجا شروع کرد؛ اما چنین قابلیتی برای تمام سکوهای کاری پیش بینی نشده‌است.
- در لینوکس قابلیت دیگری به نام Package manager وجود دارد که کار یافتن و نصب نرم افزارها را ساده می‌کند؛ اما گاهی از اوقات اطلاعات آن، آنچنان به روز نیست. همچنین اگر بسته‌ای برای توزیع خاصی از لینوکس وجود داشته باشد، الزاما به این معنا نیست که این بسته، قابلیت استفاده‌ی در سایر توزیع‌های لینوکس را نیز به همراه دارد. در ویندوز نیز وضعیت مشخص است! فاقد یک Package manager توکار و استاندارد است. هرچند یک App Store برای آن از طرف مایکروسافت ارائه شده‌است، اما آنچنان محبوبیتی پیدا نکرده‌است.
- و روش متداول دیگری که وجود دارد، مراجعه‌ی مستقیم به سایت اصلی سازنده‌ی نرم افزار است.

- علاوه بر این‌ها داشتن یک سری متادیتا و آمار نیز در مورد نرم افزارها بسیار مفید هستند تا بتوانند در مورد تصمیم به استفاده‌، یا عدم استفاده‌ی از نرم افزار، راهنمای کاربران باشند؛ مانند میزان محبوبیت، تعداد بار دریافت، تعداد مشکلاتی که کاربران با آن داشته‌اند و آخرین باری که نرم افزار به روز شده‌است. اما با توجه به پراکندگی روش‌های دریافت نرم افزار که ذکر شدند، عموما یک چنین آمارهایی را مشاهده نمی‌کنیم.
- چالش دیگر، مشکل سخت اطمینان کردن به روش‌های مختلف توزیع نرم افزارها است. آیا سایتی که این نرم افزار را ارائه می‌دهد، واقعا مرتبط با نویسنده‌ی اصلی آن است؟ همچنین آیا خود نرم افزار مشکلات امنیتی را به همراه ندارد؟ چه کاری را انجام می‌دهد؟
- مشکل بعدی، در دسترس بودن سایت توزیع کننده‌ی نرم افزار است. آیا زمانیکه به برنامه‌ای نیاز داریم، پهنای باند سایت توزیع کننده‌ی آن تمام نشده‌است و می‌توان به آن دسترسی داشت؟
- چالش دیگر، چگونگی پرداخت مبلغی برای دسترسی به نرم افزار است. به نظر تا به اینجا تنها App Storeها موفق شده‌اند روشی یک دست را برای خرید برنامه‌ها و همچنین ارائه‌ی مجوزی برای استفاده‌ی از آن‌ها، ارائه دهند.


چالش‌های پیش روی نصب نرم افزارها

زمانیکه به مرحله‌ی نصب نرم افزار می‌رسیم، هر نرم افزار، روش نصب و تنظیمات آغازین خاص خودش را دارد.
- اولین چالش پس از دریافت نرم افزار، بررسی سازگاری آن با سیستم عامل و پردازنده‌ی فعلی است. شاید این مسایل برای توسعه دهندگان نرم افزارها پیش‌پا افتاده به نظر برسند، اما برای عموم کاربران، چالشی جدی به شما می‌روند.
- پس از مشخص شدن سازگاری یک نرم افزار با سیستم فعلی، قالب ارائه‌ی آن نرم افزار نیز می‌توان مشکل‌زا باشد. بعضی از برنامه‌ها صرفا از طریق سورس کد منتشر می‌شوند. بعضی از آن‌ها توسط یک فایل exe متکی به خود ارائه می‌شوند و بعضی دیگر به همراه یک فایل exe و تعدادی dll به همراه آن‌ها. گاهی از اوقات این برنامه‌ها نیاز به نصب جداگانه‌ی NET Runtime. و یا Java Runtime را برای اجرا دارند و یا وابستگی آن‌ها صرفا به نگارش خاصی از این کتابخانه‌ها و فریم ورک‌های ثالث است. هرچند اگر برنامه‌ای به همراه بسته‌ی نصاب آن باشد، به احتمال زیاد این وابستگی‌ها را نیز نصب می‌کند؛ اما تمام برنامه‌ها اینگونه ارائه نمی‌شوند. به علاوه خیلی‌ها علاقه‌ای به کار با برنامه‌های نصاب ندارند و از ایجاد تغییرات بسیاری که آن‌ها در سیستم ایجاد می‌کنند، خشنود نیستند.
- پس از نصب نرم افزار، مشکل بعدی، نحوه‌ی به روز رسانی آن‌ها است. چگونه باید اینکار انجام شود؟ (تمام مراحل و چالش‌هایی را که تاکنون بررسی کردیم، یکبار دیگر از ابتدا مرور کنید!)

بنابراین همانطور که مشاهده می‌کنید، نصب، راه اندازی و به روز رسانی نرم افزارها این روزها بسیار پیچیده شده‌اند و بسیاری از کاربران به سادگی از عهده‌ی آن‌ها بر نمی‌آیند.


چالش‌های پیش روی کار با نرم افزارها

مرحله‌ی بعد، نیاز به مستندات کافی برای کار با برنامه است. این مستندات را از کجا می‌توان تهیه کرد؟ آخرین باری که به روز شده، چه زمانی بوده‌است؟ بسیاری از اوقات بین مستندات تهیه شده و آخرین نگارش نرم افزار، ناسازگاری وجود دارد و به سختی قابل استفاده‌است. آیا نیاز است برنامه را به PATH اضافه کرد؟ آیا نیاز است به صورت سرویس نصب شود؟ اگر بله، چگونه باید این مراحل را انجام داد؟ مجوز کار کردن با آن‌ها چگونه است؟
مشکل مهم دیگری که حین کار با نرم افزارها، در حالت متداول آن‌ها وجود دارد، دسترسی کامل آن‌ها به تمام اجزای سیستم و شبکه است و درون یک sandbox (قرنطینه) امنیتی اجرا نمی‌شوند.
مشکل بعدی، به روز رسانی اجزای ثالث سیستم و یا حتی خود سیستم عامل، مانند به روز رسانی OpenSSL نصب شده و پس از آن، از کار افتادن برنامه‌ای خاص است که وابستگی به نگارشی خاص از این کتابخانه را دارد.


کانتینرها در مورد برنامه‌ها هستند و نه مجازی سازی

خوب، تا اینجا دریافتیم که مدیریت توزیع، نصب و استفاده‌ی از برنامه‌ها، کار ساده‌ای نیست. اما این‌ها چه ارتباطی با Docker دارند؟ در بسیاری از اوقات، زمانیکه صحبت از Docker می‌شود، تصور بسیاری از آن، ارائه‌ی جایگزینی برای ماشین‌های مجازی است. اما ... اینگونه نیست. کانتینرها در مورد نرم افزارها هستند. برای مثال در آینده در مورد ایمیج‌های (Images) کانتینرها بیشتر بحث خواهیم کرد. این ایمیج‌ها در اصل یک بسته‌ی حاوی برنامه‌ها هستند. بنابراین بیشتر شبیه به فایل zip ای است که از یک وب سایت دریافت می‌کنیم (در قسمت یافتن نرم افزار).


یک کانتینر (Container) چیست؟

برای درک بهتر مواردی که تاکنون بحث شدند و همچنین بررسی مفهوم Containers، ابتدا MonogoDB را به صورت معمول نصب می‌کنیم. سپس نحوه‌ی نصب آن‌را درون یک Container بررسی خواهیم کرد. البته هدف اصلی در اینجا، بررسی مفهومی این مراحل و مقایسه‌ی آن‌ها با هم هستند و در قسمت‌های بعدی کار نصب و استفاده‌ی از Docker را قدم به قدم بررسی خواهیم کرد.
 
مراحل نصب محلی MongoDB به صورت متداول:
- ابتدا برای مثال به سایت گوگل مراجعه کرده و mongodb را جستجو می‌کنیم تا بتوانیم به سایت اصلی و محل دریافت بسته‌ی آن، هدایت شویم.
- پس از ورود به سایت mongodb، در بالای صفحه اصلی آن، لینک به صفحه‌ی دریافت بسته‌ی mongodb را می‌توان مشاهده کرد.
- با انتخاب آن، به صفحه‌ی دریافت بسته‌ی mongodb بر اساس سیستم عامل‌های مختلفی هدایت می‌شویم. برای مثال در ویندوز، بسته‌ی msi آن‌را دریافت می‌کنیم.
- به نظر می‌رسد که بسته‌ی نصاب msi آن تمام کارهای لازم برای راه اندازی اولیه‌ی mongodb را انجام می‌دهد. به همین جهت آن‌را اجرا کرده و پس از چندبار انتخاب گزینه‌ی next، نصب آن به پایان می‌رسد.
- پس از پایان نصب، ابتدا به کنسول service.msc ویندوز مراجعه می‌کنیم تا مطمئن شویم که سرویس آن، توسط نصاب msi نصب شده‌است یا خیر؟ و ... خیر! این نصاب، سرویس آن‌را نصب نکرده‌است.
- به همین جهت به مستندات نصب آن در سایت mongodb مراجعه می‌کنیم (لینک Installation instructions در همان صفحه‌ی دریافت بسته‌ی msi وجود دارد). پس از پایان مراحل نصب، عنوان کرده‌است که باید دستور md \data\db را اجرا کنید تا مسیر پیش فرض اطلاعات آن به صورت دستی ایجاد شود. اما ... این مسیر دقیقا به کجا اشاره می‌کند؟ چون شبیه به مسیرهای ویندوزی نیست.
- در ادامه برای آزمایش، به پوشه‌ی program files ویندوز رفته، monogodb نصب شده را یافته و سپس فایل mongod.exe را از طریق خط فرمان اجرا می‌کنیم (برنامه‌ی سرور). اگر این کار را انجام دهیم، این پروسه با نمایش خطای یافت نشدن مسیر c:\data\db، بلافاصله خاتمه پیدا می‌کند. به همین جهت در همین مسیری که در خط فرمان قرار داریم (جائیکه فایل mongod.exe قرار دارد)، دستور md \data\db را اجرا می‌کنیم. اجرای این دستور در این حالت، همان پوشه‌ی c:\data\db را ایجاد می‌کند. نکته‌ای که شاید خیلی‌ها با آن آشنایی نداشته باشند.
- اکنون اگر مجددا فایل mongod.exe را اجرا کنیم، اجرای آن موفقیت آمیز خواهد بود و پیام منتظر دریافت اتصالات بودن از طریق پورت 27017 را نمایش می‌دهد.
- مرحله‌ی بعد، اجرای فایل mongo.exe است تا به این دیتابیس سرور در حال اجرا متصل شویم (برنامه‌ی کلاینت). در اینجا برای مثال می‌توان دستور show dbs را اجرا کرد تا لیست بانک‌های اطلاعاتی آن‌را نمایش دهد.
 

مراحل نصب MongoDB به صورت Container توسط Docker:
- ابتدا برای مثال به سایت گوگل مراجعه کرده و اینبار mongodb docker را جستجو می‌کنیم تا بتوانیم به محل دریافت image آن هدایت شویم. با ورود به آن، در بالای صفحه عنوان شده‌است که official repository است که سبب اطمینان از بسته‌ی ارائه شده‌ی توسط آن می‌شود. بنابراین در اینجا بجای مراجعه به سایت متکی به خود mongodb، به docker hub برای دریافت آن مراجعه کرده‌ایم. در اینجا با جستجوی یک برنامه، متادیتا و اطلاعات آماری بسیاری را نیز می‌توان در مورد برنامه‌های مختلف، مشاهده کرد که در سایت متکی به خود نرم افزارهای مختلف، در دسترس نیستند. همچنین در اینجا اگر بر روی برگه‌ی Tags یک مخزن کلیک کنید، مشاهده می‌کنید که تمام فایل‌های موجود در آن توسط docker hub از لحاظ مشکلات امنیتی پیشتر اسکن شده‌اند و گزارش آن‌ها قابل مشاهده‌است. علاوه بر این‌ها docker hub به همراه یک docker store برای برنامه‌های غیر رایگان نیز هست و این مورد فرآیند کار با نرم افزارهای تجاری را یک دست می‌کند.
- مرحله‌ی بعد، دریافت یک کپی از mongodb از docker hub است. اینبار بجای دریافت مستقیم یک فایل zip یا msi، از دستور docker pull mongo استفاده می‌شود که یک image را در نهایت دریافت می‌کند. این image، حاوی برنامه‌ی مدنظر و تمام وابستگی‌های آن است.
- پس از دریافت image، مرحله‌ی بعد، اجرای mongodb به همراه آن است. در حالت متداول، ابتدا نرم افزار داخل فایل zip یا msi استخراج شده و سپس بر روی سیستم اجرا می‌شوند، اما در اینجا مفهوم معادل نصب نرم افزار دریافت شده‌ی از بسته‌ی zip همراه آن، یک container است. یک container دقیقا مانند یک نرم افزار از پیش نصب شده، عمل می‌کند و معادل اجرای فایل exe مانگو دی بی در اینجا، اجرای container آن است. بنابراین docker، از image دریافت شده، یک container را ایجاد می‌کند که دقیقا معادل یک نرم افزار از پیش نصب شده، رفتار خواهد کرد.
- پس از دریافت image، جهت اجرای آن به عنوان یک container، برای استفاده از نرم افزاری که دریافت کرده‌ایم، تنها یک دستور است که باید با آن آشنا باشیم: docker run mongo. این دستور را در همان صفحه‌ی docker hub مربوطه نیز می‌توانید مشاهده کنید. پس از اجرای این دستور، دقیقا همان خروجی و پیام منتظر دریافت اتصالات بودن از طریق پورت 27017 را مشاهده خواهیم کرد. برای اجرای کلاینت آن نیز دستور docker exec -it 27 mongo را می‌توان اجرا کرد. docker exec کار اجرای چندباره‌ی یک نرم افزار نصب شده را انجام می‌دهد.
این فرآیند در مورد تمام containerها یکی است و به این ترتیب به ازای هر نرم افزار مختلف، شاهد روش نصب متفاوتی نخواهیم بود.
- اجرای دستور docker stop نیز سبب خاتمه‌ی تمام این‌ها می‌شود.


در این تصویر مقایسه‌ای را بین مراحل متداول یافتن، دریافت، نصب و اجرای برنامه‌ها را در دو حالت متداول و همچنین استفاده‌ی از docker، مشاهده می‌کنید.

همچنین نکته‌ی جالبی که در مورد docker وجود دارد این است که اگر به task manager ویندوز مراجعه کنیم:


تمام پروسه‌هایی که با job id مساوی 172 در اینجا اجرا شده‌اند، متعلق به docker بوده و آن‌ها دقیقا مانند یک پروسه‌ی معمولی سیستم عامل جاری، در کنار سایر پروسه‌های موجود، اجرا می‌شوند. بنابراین برنامه‌ای که از طریق docker اجرا می‌شود، هیچ تفاوتی با اجرای متداول آن بر روی سیستم عامل، از طریق روش مراجعه‌ی مستقیم به فایل exe مرتبط و اجرای مستقیم آن ندارد. همانطور که پیش‌تر نیز عنوان شد، containerها در مورد نرم افزارها هستند و نه مجازی سازی و یک container در حال اجرا، حاوی تعدادی برنامه‌ی در حال اجرای بر روی سیستم عامل جاری، در کنار سایر برنامه‌های آن می‌باشد.
البته containers به همراه ایزوله سازی‌های بسیاری اجرا می‌شوند. برای مثال به روز رسانی یک کتابخانه‌ی ثالث بر روی سیستم عامل، سبب از کار افتادن برنامه‌ی اجرای شده‌ی توسط یک container نمی‌شود.


در قسمت بعد، نحوه‌ی نصب Docker را بر روی ویندوز، بررسی می‌کنیم.
نظرات مطالب
ارتقاء به ASP.NET Core 1.0 - قسمت 7 - کار با فایل‌های config
ارتقاء به ASP.NET Core 2.1

قابلیتی به نام end-to-end in-memory test به ASP.NET Core 2.1 اضافه شده‌است که برای اجرای آن نیاز به وجود یک چنین امضایی در فایل Program.cs هست:
public static IWebHostBuilder CreateWebHostBuilder(string [] args)
بنابراین برای ارتقاء برنامه‌های ASP.NET Core 2.0 به 2.1 این مراحل باید طی شوند:
الف) فایل Program.cs را باز کنید.
ب) متد BuildWebHost قبلی را به CreateWebHostBuilder تغییر نام دهید. همچنین در این حالت نوع خروجی آن‌را به IWebHostBuilder تنظیم کرده و فراخوانی ()Build. را از انتهای آن حذف کنید.
ج) اکنون بدنه‌ی متد Main آن چنین شکلی را پیدا می‌کند:
CreateWebHostBuilder(args).Build().Run();
و یا به صورت خلاصه فایل ابتدایی یک قالب ASP.NET Core 2.1 به این صورت است:
    public class Program
    {
        public static void Main(string[] args)
        {
            CreateWebHostBuilder(args).Build().Run();
        }

        public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
            WebHost.CreateDefaultBuilder(args)
                .UseStartup<Startup>();
    }