مسیرراه‌ها
React 16x
پیش نیاز ها
کامپوننت ها
ترکیب کامپوننت ها
طراحی یک گرید
مسیریابی 
کار با فرم ها
ارتباط با سرور
احراز هویت و اعتبارسنجی کاربران 
React Hooks  
توزیع برنامه

مدیریت پیشرفته‌ی حالت در React با Redux و Mobx   

       Redux
       MobX  

مطالب تکمیلی 
    اشتراک‌ها
    مسیر راه ASP.NET Core 2.1

    The following high-level features are planned for the ASP.NET Core 2.1 release:

    SignalR - Real-time web framework on ASP.NET Core (aspnet/SignalR#394)
    HTTPS - On by default and easy to configure (aspnet/Home#2308)
    GDPR compliance - Templates updated with new privacy features (aspnet/Security#1561, aspnet/Identity#1341)
    Web API conventions - Rich Swagger support without attributes (aspnet/Mvc#6870, aspnet/Mvc#6784)
    IHttpClientFactory - HttpClient as a service, handle cross-cutting concerns like caching, retry logic, timeouts and circuit breakers (aspnet/HttpClientFactory#42)
    ASP.NET Core Module in-proc hosting - 6x the throughput on IIS! Better startup error handling (aspnet/AspNetCoreModule#265)
    Razor pages improvements - Support for areas, ~/Pages/Shared (aspnet/Mvc#6926, aspnet/Mvc#7193)
    MVC functional test fixture - Easily test your MVC apps end-to-end (aspnet/Mvc#6233)
    Build-time Razor - Compile Razor pages and views as part of your build, improved startup performance (aspnet/Razor#1809)
    UI as a library - Package Razor pages and views as reusable libraries (aspnet/Razor#1809)
    Identity UI package and scaffolder - Add identity to any application (aspnet/Home#2311)
    WebHooks - Handle WebHook notifications from ASP.NET Core apps (aspnet/WebHooks#5)

    مسیر راه ASP.NET Core 2.1
    اشتراک‌ها
    مستندات ASP.NET 5
        Getting Started
            Installing ASP.NET 5 On Windows
            Installing ASP.NET 5 On Mac OS X
            Installing ASP.NET 5 On Linux
            Choosing the Right .NET For You on the Server
        Tutorials
            Your First ASP.NET 5 Application Using Visual Studio
            Your First ASP.NET 5 Application on a Mac
            Publish to an Azure Web App using Visual Studio
        Conceptual Overview
            Introduction to ASP.NET 5
            Introducing .NET Core
            DNX Overview
            Understanding ASP.NET 5 Web Apps
        Fundamentals
            Working with Static Files
            Routing
            Configuration
            Dependency Injection
            Diagnostics
            Working with Multiple Environments
            OWIN
        .NET Execution Environment (DNX)
            DNX Overview
            Creating a Cross-Platform Console App with DNX
            Working with DNX Projects
            Using Commands
        Publishing and Deployment
            Publish to a Docker Image
        Client-Side Development
            Grunt and Gulp: Task Runners
            Manage Client-Side Packages with Bower
            Building Beautiful, Responsive Sites with Bootstrap
            Knockout.js MVVM Framework
            Styling Applications with Less, Sass, and Font Awesome
        Security
            Enabling authenication using external providers
            Account Confirmation and Password Recovery with ASP.NET Identity
            Two-factor authenication with SMS using ASP.NET Identity
            Data Protection
        Extensibility
            Writing Middleware
    مستندات ASP.NET 5
    اشتراک‌ها
    18 روش خلاقانه استفاده از Docker در کارهای روزانه
    18 Innovative Ways to Use Docker Daily

    - Running GUI Applications in Docker Containers
    - How to Run a Web Browser Inside Docker
    - Using Obsidian Note-Taking App via Docker
    - LibreOffice in Docker: Open-Source Office Suite Containerized
    - Contribute to Science: Folding@Home with Docker
    - Effortless Docker Management with Docker Desktop and Portainer
    - Secure Document Handling Using Dangerzone and Docker
    - Containerizing CLI Tools for a Clean System
    - Enhance Docker Security with Docker Scout
    - Set Up an Isolated Hacking Lab with Docker and Kali Linux
    - Master Docker Networks for Container Isolation
    - Automate Your Docker Workflows with Docker Compose
    - Explore New Operating Systems Using Docker Containers
    - Running macOS in a Docker Container (Educational Purposes)
    - How to Run Raspberry Pi OS via Docker
    - Access a Suite of IT Tools Inside a Docker Container
    - Using Docker to Containerize AI Tools like Fabric
    - Safely Test Applications with Docker Containers
    - Building Custom Docker Images from Scratch
    - Unconventional Docker Use Cases You Need to Know
    - Daily Docker Tips and Tricks for Enhanced Productivity
    - Beginner's Guide to Advanced Docker Techniques
    - Boost Your IT Workflow with These Docker Hacks
    - Docker for Everyday Tasks: Beyond Development
    18 روش خلاقانه استفاده از Docker در کارهای روزانه
    مطالب
    React 16x - قسمت 24 - ارتباط با سرور - بخش 3 - نکات تکمیلی کار با Axios
    پس از آشنایی با مقدمات کار با Axios، در این قسمت امکانات پیشرفته‌تر آن‌را مانند خطایابی سراسری، interceptors  و ... بررسی می‌کنیم.


    به روز رسانی‌های خوشبینانه‌ی UI

    پیاده سازی اعمال CRUD توسط Axios در قسمت قبل، به همراه یک مشکل مهم است: اعمال کار با شبکه و سرور، زمانبر هستند و مدتی طول می‌کشد تا پاسخ عملیات از سمت سرور دریافت شود. در این بین اگر خطایی رخ دهد، مابقی کدهای نوشته شده‌ی در متدهایی مانند Update و Delete، اجرا نمی‌شوند. به این حالت «به روز رسانی بدبینانه‌ی UI» گفته می‌شود. در حالت خوشبینانه، فرض بر این است که در اکثر موارد، فراخوانی سرور با موفقیت به پایان می‌رسد. در یک چنین حالتی، ابتدا UI به روز رسانی می‌شود و سپس فراخوانی‌های سمت سرور صورت می‌گیرند. اگر این فراخوانی با شکست مواجه شد، مجددا UI را به حالت قبلی آن باز می‌گردانیم:
      handleDelete = async post => {
        const posts = this.state.posts.filter(item => item.id !== post.id);
        this.setState({ posts });
    
        await axios.delete(`${apiEndpoint}/${post.id}`);
      };
    در کدهای فوق، ابتدا UI به روز رسانی می‌شود (که بسیار سریع است)، سپس حذف سمت سرور صورت می‌گیرد. یک چنین پیاده سازی، به کاربر حس کار با یک برنامه‌ی بسیار سریع را القاء می‌کند؛ هرچند فراخوانی سمت سرور انجام شده، ممکن است مدتی طول بکشد.
    اما اگر در این بین خطایی رخ داد، چه باید کرد؟ باید آخرین تغییر انجام شده را به حالت اول باز گرداند. انجام یک چنین کاری در React ساده‌است. چون ما state را به صورت مستقیم ویرایش نمی‌کنیم، همیشه می‌توان ارجاعی را به state قبلی، ذخیره و سپس در صورت نیاز آن‌را بازیابی کرد:
      handleDelete = async post => {
        const originalPosts = this.state.posts;
    
        const posts = this.state.posts.filter(item => item.id !== post.id);
        this.setState({ posts }); // Optimistic Update
    
        try {
          await axios.delete(`${apiEndpoint}/${post.id}`);
        } catch (ex) {
          alert("An error occurred when deleting a post!");
          this.setState({ posts: originalPosts }); // Undo changes
        }
      };
    در اینجا در ابتدا توسط متغیر originalPosts، ارجاعی را به وضعیت قبلی آرایه‌ی posts موجود در state (وضعیت ابتدایی UI)، نگهداری می‌کنیم. سپس کار حذف بسیار سریع آیتم درخواستی را از UI انجام می‌دهیم. اکنون کار حذف اصلی رکورد را از سرور، درون یک try/catch انجام خواهیم داد. اگر خطایی رخ دهد، پیامی را به کاربر نمایش داده و سپس مجددا state را به همان originalPosts پیشین، باز خواهیم گرداند.


    مدیریت خطاهای رخ داده‌ی در حین فراخوانی سرور

    تا اینجا مشاهده کردیم که یک روش مدیریت خطاها در کدهای Axios، قرار دادن آن‌ها در یک قطعه کد try/catch است. در اینجا نیز باید بتوان بین خطاهای پیش بینی شده و نشده، تفاوت قائل شد.
    - خطاهای پیش بینی شده: برای مثال اگر درخواست حذف رکوردی را دادیم که در بانک اطلاعاتی موجود نیست، انتظار داریم سرور، خطای 404 یا return NotFound را بازگشت دهد و یا 400 که معادل bad request است و در حالت ارسال داده‌هایی غیرمعتبر، رخ می‌دهد. در این موارد بهتر است خطاهایی خاص را به کاربران نمایش داد؛ برای مثال رکورد درخواستی وجود ندارد یا پیشتر حذف شده‌است.
    - خطاهای پیش بینی نشده: این نوع خطاها نباید و یا قرار نیست در شرایط عادی رخ دهند. برای مثال اگر شبکه در دسترس نیست، امکان ارتباط با سرور نیز میسر نخواهد بود و یا حتی ممکن است خطایی در کدهای سمت سرور، سبب بروز خطایی شده باشد. این نوع خطاها ابتدا باید لاگ شوند تا با بررسی‌های آتی آن‌ها، بتوان مشکلات پیش بینی نشده را بهتر برطرف کرد. همچنین در یک چنین مواردی، باید یک پیام خطای خیلی عمومی را به کاربر نمایش داد؛ برای مثال «یک خطای پیش بینی نشده رخ داده‌است.».

    برای مدیریت این دو حالت باید به جزئیات شیء ex، در بدنه‌ی catch، دقت کرد که دارای دو خاصیت request و response است. اگر ex.response تنظیم شده بود، یعنی دریافت خروجی از سرور موفقیت آمیز بوده‌است. اگر سرور در دسترس نباشد و یا برنامه‌ی سمت سرور کرش کرده باشد، ex.response نال خواهد بود. اگر ex.request نال نبود، یعنی ارسال درخواست به سمت سرور با موفقیت انجام شده‌است. برای مثال جهت بررسی خطای مورد انتظار 404، می‌توان در قسمت catch(ex) به صورت زیر عمل کرد:
    try {
      await axios.delete(`${apiEndpoint}/${post.id}`);
    } catch (ex) {
      if (ex.response && ex.response.status === 404) {
         alert("This post has already been deleted!");
      } else {
         console.log("Error", ex);
         alert("An unexpected error occurred when deleting a post!");
      }
    
      this.setState({ posts: originalPosts }); // Undo changes
    }
    در اینجا ابتدا بررسی می‌شود که آیا شیء response نال است یا خیر؟ سپس خاصیت status آن‌را برای بررسی خطاهای پیش بینی شده، بررسی می‌کنیم. خطایی که در اینجا نمایش داده می‌شود، اختصاصی‌تر است. در غیراینصورت، ابتدا باید این خطا لاگ شود و سپس یک اخطار عمومی نمایش داده می‌شود. پس از بررسی هر دو حالت، باید UI را مجددا به حالت اول آن بازگشت داد.
    عموما خطاهای پیش‌بینی شده را لاگ نمی‌کنیم؛ چون ممکن است کاربر، یک صفحه را در چندین برگه باز کرده باشد و در یکی، رکوردی را حذف کند. در این حال، این رکورد هنوز در برگه‌های دیگر موجود است و اگر مجددا درخواست حذف آن‌را صادر کند، مشکل خاصی از دیدگاه برنامه رخ نداده‌است و نیازی به پیگیری‌های آتی را ندارد. یعنی صرفا یک client error است.


    مدیریت سراسری خطاهای رخ داده‌ی در حین فراخوانی سرور

    برای مدیریت خطاها، نیاز است یک چنین try/catchهایی را در تمام قسمت‌های برنامه که با سرور کار می‌کنند، قرار دهیم. برای کاهش این کدهای تکراری، از interceptors کتابخانه‌ی Axios استفاده می‌شود. در این کتابخانه می‌توان در جاهائیکه درخواستی به سمت سرور ارسال می‌شود و یا پاسخی از سمت سرور دریافت می‌شود، قطعه کدهایی سراسری را قرار داد و بر روی درخواست و یا پاسخ، تغییراتی را اعمال کرد و یا حتی اطلاعات مربوطه را لاگ کرد؛ به این نوع قطعه کدها، interceptor گفته می‌شود و برای تعریف آن‌ها می‌توان از axios.interceptors.request و یا axios.interceptors.response، خارج از کلاس جاری استفاده کرد. برای مثال بر روی شیء axios.interceptors.response، می‌توان متد use را فراخوانی کرد که دو پارامتر را که هر کدام یک callback function هستند، می‌پذیرد. اولی در صورت موفقیت آمیز بودن response فراخوانی می‌شود و دومی در صورت شکست آن. اگر نیازی به هر کدام نبود، می‌توان آن‌را به null مقدار دهی کرد. اگر مدیریت قسمت شکست علمیات مدنظر است، نیاز خواهد بود در پایان این callback function، یک Rejected Promise را بازگشت داد تا ادامه‌ی برنامه، به درستی مدیریت شود. در این حالت اگر خطایی رخ دهد، ابتدا این interceptor فراخوانی می‌شود و سپس کنترل به بدنه‌ی catch منتقل خواهد شد:
    import "./App.css";
    
    import axios from "axios";
    import React, { Component } from "react";
    
    axios.interceptors.response.use(null, error => {
      console.log("interceptor called.");
      return Promise.reject(error);
    });
    
    const apiEndpoint = "https://localhost:5001/api/posts";
    class App extends Component {
    اکنون می‌خواهیم قطعه کد نمایش خطاهای عمومی پیش بینی نشده را از تمام بدنه‌های catch حذف کرده و به یک interceptor منتقل کنیم:
    axios.interceptors.response.use(null, error => {
      const expectedError =
        error.response &&
        error.response.status >= 400 &&
        error.response.status < 500;
    
      if (!expectedError) {
        console.log("Error", error);
        alert("An unexpected error occurred when deleting a post!");
      }
    
      return Promise.reject(error);
    });
    خطاهای پیش بینی شده عموما در بازه‌ی 400 تا 500 قرار دارند. به همین جهت اگر یک چنین خطاهایی را دریافت کردیم، اخطاری را نمایش نداده و صرفا کنترل را به catch block منتقل می‌کنیم. اما اگر خطا، پیش بینی نشده بود، کار لاگ کردن خطا و همچنین نمایش اخطار را در اینجا انجام خواهیم داد.

    یک نکته: استفاده از try/catchها فقط برای بازگشت UI به حالت قبلی و یا نمایش خطایی خاص به کاربر توصیه می‌شوند. اگر از روش «به روز رسانی‌های خوشبینانه‌ی UI» استفاده نمی‌کنید و همچنین خطاهای ویژه‌ای بجز خطای عمومی لاگ شده‌ی در interceptor فوق مدنظر شما نیست، نیازی هم به try/catch نخواهد بود و پس از بروز خطا، قسمت‌های بعدی کد اجرا نمی‌شوند؛ اما خطای عمومی فوق نمایش داده خواهد شد.


    ایجاد یک HTTP Service با قابلیت استفاده‌ی مجدد

    تا اینجا تعریف interceptor را پیش از کلاس کامپوننت جاری قرار داده‌ایم که هم سبب شلوغی این ماژول شده‌است و هم در صورت نیاز به آن در سایر برنامه‌ها، باید همین قطعه کد را مجددا در آن‌ها کپی کرد. به همین جهت پوشه‌ی جدید src\services را ایجاد کرده و سپس فایل src\services\httpService.js را در آن با محتوای زیر ایجاد می‌کنیم:
    import axios from "axios";
    
    axios.interceptors.response.use(null, error => {
      const expectedError =
        error.response &&
        error.response.status >= 400 &&
        error.response.status < 500;
    
      if (!expectedError) {
        console.log("Error", error);
        alert("An unexpected error occurred when deleting a post!");
      }
    
      return Promise.reject(error);
    });
    
    export default {
      get: axios.get,
      post: axios.post,
      put: axios.put,
      delete: axios.delete
    };
    در اینجا علاوه بر انتقال interceptor تعریف شده، کار export متدهای axios نیز به صورت یک شیء جدید صورت گرفته‌است.
    سپس به app.js مراجعه کرده و این ماژول را با یک نام دلخواه import می‌کنیم:
    import http from "./services/httpService";
    در ادامه هرجائیکه ارجاعی به axios وجود دارد، آن‌را با http فوق جایگزین می‌کنیم. در این حالت می‌توان "import axios from "axios را نیز از ابتدای app.js حذف کرد. مزیت اینکار، مخفی کردن Axios، در پشت صحنه‌ی ماژول جدیدی است که ایجاد کردیم. به این ترتیب اگر در آینده خواستیم، Axios را با کتابخانه‌ی دیگری جایگزین کنیم، در کل برنامه تنها نیاز است این httpService.js جدید را تغییر دهیم.


    ایجاد یک ماژول Config

    بهبود دیگری را که می‌توانیم اعمال کنیم، انتقال const apiEndpoint تعریف شده، به یک ماژول مجزا است؛ تا اگر نیاز به استفاده‌ی از آن در قسمت‌های دیگری نیز وجود داشت، به سادگی بتوان آن‌را مدیریت کرد. به همین جهت فایل جدید src\config.json را با محتوای زیر ایجاد می‌کنیم:
    {
       "apiEndpoint" : "https://localhost:5001/api/posts"
    }
    سپس به فایل app.js بازگشته و ابتدا const apiEndpoint را حذف و سپس import زیر را به ابتدای فایل، اضافه می‌کنیم:
    import config from "./config.json";
    اکنون هر جائی در کدهای خود که apiEndpoint را داریم، تبدیل به config.apiEndpoint می‌کنیم.


    نمایش بهتر خطاها به کاربر توسط کتابخانه‌ی react-toastify

    بجای alert توکار مرورگرها، می‌توان یک صفحه‌ی دیالوگ زیباتر را برای نمایش خطاها درنظر گرفت. به همین جهت ابتدا کتابخانه‌ی react-toastify را نصب می‌کنیم:
    > npm i react-toastify --save
    سپس به فایل app.js مراجعه کرده و importهای لازم آن‌را اضافه می‌کنیم:
    import { ToastContainer } from "react-toastify";
    import "react-toastify/dist/ReactToastify.css";
    همچنین نیاز است ToastContainer را به ابتدای متد render نیز اضافه کرد:
      render() {
        return (
          <React.Fragment>
            <ToastContainer />
    اکنون به src\services\httpService.js مراجعه کرده و alert آن‌را به صورت زیر تغییر می‌دهیم:
    import { toast } from "react-toastify";
    // ...
    
    axios.interceptors.response.use(null, error => {
      // ...
      if (!expectedError) {
        // ...
        toast.error("An unexpected error occurrred.");
      }
    ابتدا، شیء toast آن import می‌شود و سپس توسط این شیء می‌توان از متد error آن، جهت نمایش خطاهایی شکیل‌تر استفاده کرد؛ با این خروجی:



    کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: sample-22-backend-part-03.zip و sample-22-frontend-part-03.zip
    مطالب
    React 16x - قسمت 25 - ارتباط با سرور - بخش 4 - یک تمرین
    همان مثال backend قسمت 22 را با افزودن وب سرویس‌هایی برای قسمت‌های نمایش لیست فیلم‌ها، ژانرها و سایر صفحات اضافه شده‌ی به برنامه‌ی تکمیل شده‌ی تا قسمت 21، توسعه می‌دهیم. کدهای کامل آن، به علت شباهت و یکی بودن نکات آن با مطلب 22، در اینجا تکرار نخواهند شد و می‌توانید کل پروژه‌ی آن‌را از پیوست انتهای بحث دریافت کنید. سپس فایل dotnet_run.bat آن‌را اجرا کنید تا در آدرس https://localhost:5001 قابل دسترسی شود.


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

    تا این قسمت، تمام اطلاعات نمایش داده شده‌ی در لیست فیلم‌ها، از سرویس درون حافظه‌ای src\services\fakeMovieService.js و لیست ژانرها از سرویس src\services\fakeGenreService.js، تامین می‌شوند. اکنون در ادامه می‌خواهیم این سرویس‌ها را با سرویس backend یاد شده، جایگزین کنیم تا این برنامه، اطلاعات خودش را از سرور دریافت کند. به همین جهت قبل از هر کاری، سرویس عمومی src\services\httpService.js را که در قسمت قبل توسعه دادیم، به برنامه‌ی نمایش لیست فیلم‌ها نیز اضافه می‌کنیم (فایل آن‌را از پروژه‌ی قبلی کپی کرده و در اینجا paste می‌کنیم)، تا بتوانیم از امکانات آن در اینجا نیز استفاده کنیم. فایل httpService.js، دارای وابستگی‌های خارجی react-toastify و axios است. به همین جهت برای افزودن آن‌ها مراحل زیر را طی می‌کنیم:
    - نصب کتابخانه‌های react-toastify و axios از طریق خط فرمان (با فشردن دکمه‌های ctrl+back-tick در VSCode):
    > npm i axios --save
    > npm i react-toastify --save
    سپس به فایل app.js مراجعه کرده و importهای لازم آن‌را اضافه می‌کنیم:
    import { ToastContainer } from "react-toastify";
    import "react-toastify/dist/ReactToastify.css";
    همچنین نیاز است ToastContainer را به ابتدای متد render نیز اضافه کرد:
      render() {
        return (
          <React.Fragment>
            <ToastContainer />


    دریافت اطلاعات لیست نمایش ژانرها از سرویس backend

    با فراخوانی آدرس https://localhost:5001/api/Genres، می‌توان لیست ژانرهای سینمایی تعریف شده‌ی در سرویس‌های backend را مشاهده کرد. اکنون قصد داریم از این اطلاعات، در برنامه استفاده کنیم. به همین جهت به فایل src\components\movies.jsx مراجعه کرده و تغییرات زیر را اعمال می‌کنیم:
    چون نمی‌خواهیم تغییراتی بسیار اساسی را در اینجا اعمال کنیم، قدم به قدم عمل کرده و سرویس قبلی fakeGenreService.js را با یک سرویس جدید که اطلاعات خودش را از سرور دریافت می‌کند، جایگزین می‌کنیم. بنابراین ابتدا فایل جدید src\services\genreService.js را ایجاد می‌کنیم. سپس آن‌را طوری تکمیل خواهیم کرد که اینترفیس آن، با اینترفیس fakeGenreService قبلی یکی باشد:
    import { apiUrl } from "../config.json";
    import http from "./httpService";
    
    export function getGenres() {
      return http.get(apiUrl + "/genres");
    }
    همچنین در اینجا import وابستگی config.json را نیز مشاهده می‌کنید که در قسمت قبل در مورد آن توضیح دادیم. به همین جهت برای تمیزتر شدن قسمت‌های مختلف برنامه، فایل config.json را در مسیر src\config.json ایجاد کرده و به صورت زیر تکمیل می‌کنیم:
    {
       "apiUrl": "https://localhost:5001/api"
    }
    apiUrl به ریشه‌ی URLهای ارائه شده‌ی توسط backend service ما، اشاره می‌کند.

    پس از تکمیل سرویس جدید src\services\genreService.js، به فایل src\components\movies.jsx بازگشته و سطر قبلی
    import { getGenres } from "../services/fakeGenreService";
    را با سطر جدید زیر، جایگزین می‌کنیم:
    import { getGenres } from "../services/genreService";
    تا اینجا اگر برنامه را ذخیره کرده و اجرا کنید، خطای زیر را مشاهده خواهید کرد:
    Uncaught TypeError: Object is not a function or its return value is not iterable
    علت اینجا است که سرویس قبلی fakeGenreService، دارای متد export شده‌ای به نام getGenres بود که یک آرایه‌ی معمولی را بازگشت می‌داد. اکنون این سرویس جدید نیز همان ساختار را دارد، اما اینبار یک Promise را بازگشت می‌دهد. به همین جهت متد componentDidMount را به صورت زیر اصلاح می‌کنیم:
      async componentDidMount() {
        const { data } = await getGenres();
        const genres = [{ _id: "", name: "All Genres" }, ...data];
        this.setState({ movies: getMovies(), genres });
      }
    متد getGenres باید await شود تا نتیجه‌ی آن توسط خاصیت data شیء بازگشتی از آن، قابل دسترسی شود. با این تغییر، نیاز است این متد را نیز به صورت async معرفی کرد.


    دریافت اطلاعات لیست فیلم‌ها از سرویس backend

    پس از دریافت لیست ژانرهای سینمایی از سرور، اکنون نوبت به جایگزینی src\services\fakeMovieService.js با یک نمونه‌ی متصل به backend است. به همین جهت ابتدا فایل جدید src\services\movieService.js را ایجاد کرده و سپس آن‌را به صورت زیر تکمیل می‌کنیم:
    import { apiUrl } from "../config.json";
    import http from "./httpService";
    
    const apiEndpoint = apiUrl + "/movies";
    
    function movieUrl(id) {
      return `${apiEndpoint}/${id}`;
    }
    
    export function getMovies() {
      return http.get(apiEndpoint);
    }
    
    export function getMovie(movieId) {
      return http.get(movieUrl(movieId));
    }
    
    export function saveMovie(movie) {
      if (movie.id) {
        return http.put(movieUrl(movie.id), movie);
      }
    
      return http.post(apiEndpoint, movie);
    }
    
    export function deleteMovie(movieId) {
      return http.delete(movieUrl(movieId));
    }
    سپس شروع به اصلاح کامپوننت movies می‌کنیم.
    ابتدا دو متد دریافت لیست فیلم‌ها و حذف یک فیلم را که در این کامپوننت استفاده شده‌اند، import می‌کنیم:
    import { getMovies, deleteMovie } from "../services/movieService";
    بعد متد getMovies پیشین، که یک آرایه را بازگشت می‌داد، توسط متد جدیدی که یک Promise را بازگشت می‌دهد، جایگزین می‌شود:
      async componentDidMount() {
        const { data } = await getGenres();
        const genres = [{ id: "", name: "All Genres" }, ...data];
    
        const { data: movies } = await getMovies();
        this.setState({ movies, genres });
      }
    همچنین مدیریت حذف رکوردها را نیز به صورت زیر با پیاده سازی «به‌روز رسانی خوشبینانه UI» که در قسمت قبل در مورد آن بیشتر بحث شد، تغییر می‌دهیم. در این حالت فرض بر این است که به احتمال زیاد،  await deleteMovie با موفقیت به پایان می‌رسد. بنابراین بهتر است UI را ابتدا به روز رسانی کنیم تا کاربر حس کار کردن با یک برنامه‌ی سریع را داشته باشد:
      handleDelete = async movie => {
        const originalMovies = this.state.movies;
    
        const movies = originalMovies.filter(m => m.id !== movie.id);
        this.setState({ movies });
    
        try {
          await deleteMovie(movie.id);
        } catch (ex) {
          if (ex.response && ex.response.status === 404) {
            console.log(ex);
            toast.error("This movie has already been deleted.");
          }
    
          this.setState({ movies: originalMovies }); //undo changes
        }
      };
    ابتدا ارجاعی را از state قبلی ذخیره می‌کنیم تا در صوت بروز خطایی در سطر await deleteMovie، بتوانیم مجددا state را به حالت اول آن بازگردانیم. به همین منظور پیاده سازی «به‌روز رسانی خوشبینانه UI»، حتما نیاز به درج صریح try/catch را دارد. برای نمایش خطاهای ویژه‌ی 404 نیز از یک toast استفاده شده که نیاز به import زیر را دارد:
    import { toast } from "react-toastify";
    سایر خطاهای رخ داده، توسط interceptor درج شده‌ی در سرویس http، به صورت خودکار پردازش می‌شوند.


    اتصال فرم ثبت و ویرایش یک فیلم به backend server

    تا اینجا اگر برنامه را اجرا کنیم، با کلیک بر روی لینک هر فیلم نمایش داده شده‌ی در صفحه، به صفحه‌ی not-found هدایت می‌شویم. برای رفع این مشکل، به فایل src\components\movieForm.jsx مراجعه کرده و ابتدا
    import { getGenres } from "../services/fakeGenreService";
    import { getMovie, saveMovie } from "../services/fakeMovieService";
    قبلی را با نمونه‌ها‌ی جدیدی که با سرور کار می‌کنند، جایگزین می‌کنیم:
    import { getGenres } from "../services/genreService";
    import { getMovie, saveMovie } from "../services/movieService";
    سپس ارجاعات به این سه متد import شده را با await، همراه کرده و متد اصلی را به صورت async معرفی می‌کنیم:
      async componentDidMount() {
        const { data: genres } = await getGenres();
        this.setState({ genres });
    
        const movieId = this.props.match.params.id;
        if (movieId === "new") return;
    
        const { data: movie } = await getMovie(movieId);
        if (!movie) return this.props.history.replace("/not-found");
    
        this.setState({ data: this.mapToViewModel(movie) });
      }
    البته می‌توان جهت بهبود کیفیت کدها، از متد componentDidMount، دو متد با مسئولیت‌های مجزای دریافت ژانرها (populateGenres) و سپس نمایش فرم اطلاعات فیلم (populateMovie) را استخراج کرد:
      async populateGenres() {
        const { data: genres } = await getGenres();
        this.setState({ genres });
      }
    
      async populateMovie() {
        try {
          const movieId = this.props.match.params.id;
          if (movieId === "new") return;
    
          const { data: movie } = await getMovie(movieId);
          this.setState({ data: this.mapToViewModel(movie) });
        } catch (ex) {
          if (ex.response && ex.response.status === 404)
            this.props.history.replace("/not-found");
        }
      }
    
      async componentDidMount() {
        await this.populateGenres();
        await this.populateMovie();
      }
    در متد populateMovie، اگر movieId اشتباهی وارد شود و یا کلا عملیات دریافت اطلاعات، با شکست مواجه شود، کاربر را به صفحه‌ی not-found هدایت می‌کنیم. یعنی وجود try/catch در اینجا ضروری است. چون اگر movieId اشتباهی وارد شود، اینبار دیگر خطوط بعدی اجرا نمی‌شوند و در همان سطر await getMovie، یک استثناء صادر شده و کار خاتمه پیدا می‌کند. بنابراین نیاز داریم بتوانیم این استثنای احتمالی را مدیریت کرده و کاربر را به صفحه‌ی not-found هدایت کنیم.
    پیشتر زمانیکه متد getMovie، یک شیء ساده را از fake service، بازگشت می‌داد، چنین مشکلی را نداشتیم؛ به همین جهت در سطر بعدی آن، هدایت کاربر در صورت نال بودن نتیجه، با یک return صورت می‌گرفت. اما اینجا بجای نال، یک استثناء را ممکن است دریافت کنیم.

    مرحله‌ی آخر اصلاح این فرم، اتصال قسمت ثبت اطلاعات آن است که با قرار دادن یک await، پیش از متد saveMovie و async کردن متد آن، انجام می‌شود:
      doSubmit = async () => {
        await saveMovie(this.state.data);
    
        this.props.history.push("/movies");
      };


    کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: sample-25-backend.zip و sample-25-frontend.zip
    مطالب
    روش نصب NET SDK. بر روی لینوکس Ubuntu

    اگر از آخرین نگارش Ubuntu استفاده می‌کنید، با توجه به همکاری مایکروسافت و شرکت پشتیبان آن، نصب دات‌نت به سادگی اجرای دستور زیر است:

    $ sudo apt update && sudo apt install -y dotnet-sdk-8.0
    $ dotnet --version

    و اگر می‌خواهید بدانید که چه ‌نگارشی از دات‌نت، به‌همراه مخازن استاندارد Ubuntu است، دستور زیر را صادر کنید:

    $ apt search dotnet-sdk*

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

    $ apt search dotnet-sdk*
    Sorting... Done
    Full Text Search... Done
    dotnet-sdk-8.0/noble-updates,noble-security 8.0.107-0ubuntu1~24.04.1 amd64
      .NET 8.0 Software Development Kit
    
    dotnet-sdk-8.0-source-built-artifacts/noble-updates,noble-security 8.0.107-0ubuntu1~24.04.1 amd64
      Internal package for building the .NET 8.0 Software Development Kit
    
    dotnet-sdk-dbg-8.0/noble-updates,noble-security 8.0.107-0ubuntu1~24.04.1 amd64
      .NET SDK debug symbols.

    نصب اسکریپتی آخرین نگارش دات‌نت بر روی تمام توزیع‌های لینوکسی

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

    پیش از هرکاری ابتدا مخزن‌های بسته‌ها و برنامه‌های مرتبط را یکبار به‌روز کرده و سیستم را ری‌استارت می‌کنیم:

    sudo apt update -q && sudo apt upgrade -y && reboot

    سپس دستور زیر را صادر می‌کنیم تا اسکریپت نصاب مخصوص دات‌نت خود مایکروسافت را دریافت کنیم :

    wget https://dot.net/v1/dotnet-install.sh -O dotnet-install.sh
    chmod +x ./dotnet-install.sh

    توسط این دو دستور، فایل dotnet-install.sh دریافت شده و همچنین دسترسی اجرایی بودن آن نیز تنظیم می‌شود.

    پس از آن، برای نصب آخرین نگارش دات‌نت SDK موجود، تنها کافی است دستور زیر را صادر کنیم:

    ./dotnet-install.sh --version latest

    و یا اگر فقط می‌خواهید runtime آن‌را نصب کنید، پارامترهای نصب، به صورت زیر تغییر می‌کنند:

    ./dotnet-install.sh --version latest --runtime aspnetcore

    همچنین اگر نگارش‌های پایین‌تر مدنظر شما هستند، می‌توانید کانال نصب را هم مشخص کنید:

    ./dotnet-install.sh --channel 7.0

    که یک نمونه خروجی اجرای دستور dotnet-install.sh --version latest/. آن به صورت زیر است:

    $ ./dotnet-install.sh --version latest
    dotnet-install: Attempting to download using aka.ms link https://dotnetcli.azureedge.net/dotnet/Sdk/8.0.303/dotnet-sdk-8.0.303-linux-x64.tar.gz
    dotnet-install: Remote file https://dotnetcli.azureedge.net/dotnet/Sdk/8.0.303/dotnet-sdk-8.0.303-linux-x64.tar.gz size is 223236164 bytes.
    dotnet-install: Extracting archive from https://dotnetcli.azureedge.net/dotnet/Sdk/8.0.303/dotnet-sdk-8.0.303-linux-x64.tar.gz
    dotnet-install: Downloaded file size is 223236164 bytes.
    dotnet-install: The remote and local file sizes are equal.
    dotnet-install: Installed version is 8.0.303
    dotnet-install: Adding to current process PATH: `/home/vahid/.dotnet`. Note: This change will be visible only when sourcing script.
    dotnet-install: Note that the script does not resolve dependencies during installation.
    dotnet-install: To check the list of dependencies, go to https://learn.microsoft.com/dotnet/core/install, select your operating system and check the "Dependencies" section.
    dotnet-install: Installation finished successfully.

    همانطور که مشاهده می‌کنید، دات‌نت 8.0.303، نصب شده‌است.

    پس از پایان نصب، دو دستور زیر را هم باید اجرا کنید تا بتوان در خط فرمان به NET CLI. دسترسی یافت:

    $ export DOTNET_ROOT=$HOME/.dotnet
    $ export PATH=$PATH:$DOTNET_ROOT:$DOTNET_ROOT/tools

    پس از این تنظیمات، امکان اجرای دو دستور آزمایشی زیر میسر می‌شوند:

    $ dotnet --version
    $ dotnet --list-sdks

    نصب دات‌نت بر روی نگارش‌های قدیمی‌تر Ubuntu

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

    ابتدا تمام وابستگی‌های احتمالی موجود را حذف می‌کنیم:

    $ sudo apt remove 'dotnet*' 'aspnet*' 'netstandard*'

    سپس فایل زیر را ایجاد کرده:

    sudo nano touch /etc/apt/preferences

    و آن‌را با محتوای زیر تکمیل می‌کنیم؛ تا فقط از مخازن خود مایکروسافت استفاده شود و سایر مخازن مرتبط در این حالت، اولویت کمتری داشته باشند:

    Package: dotnet* aspnet* netstandard*
    Pin: origin "archive.ubuntu.com"
    Pin-Priority: -10
    
    Package: dotnet* aspnet* netstandard*
    Pin: origin "security.ubuntu.com"
    Pin-Priority: -10

    پس از آن، دستورات زیر، کار افزودن مخازن بسته‌های مایکروسافت را انجام می‌دهند:

    # Get OS version info which adds the $ID and $VERSION_ID variables
    source /etc/os-release
    
    # Download Microsoft signing key and repository
    wget https://packages.microsoft.com/config/$ID/$VERSION_ID/packages-microsoft-prod.deb -O packages-microsoft-prod.deb
    
    # Install Microsoft signing key and repository
    sudo dpkg -i packages-microsoft-prod.deb
    
    # Clean up
    rm packages-microsoft-prod.deb
    
    # Update packages
    sudo apt update
    sudo apt upgrade -y

    بعد از این به‌روز رسانی‌ها، دستور متداول زیر، کار نصب آخرین نگارش NET SDK. را انجام می‌دهد:

    sudo apt-get install -y dotnet-sdk-8.0

    و همچنین هربار هم که سیستم را با دستورات sudo apt update -q && sudo apt upgrade -y به روز کنیم، در صورت وجود به‌روز رسانی دات‌نتی جدیدی، آن‌را به صورت خودکار دریافت و نصب می‌کند.

    اشتراک‌ها
    چک لیست امنیتی برنامه های مبتنی بر Blazor
    • Validate arguments from events.
    • Validate inputs and results from JS interop calls.
    • Avoid using (or validate beforehand) user input for .NET to JS interop calls.
    • Prevent the client from allocating an unbound amount of memory.
      • Data within the component.
      • DotNetObject references returned to the client.
    • Guard against multiple dispatches.
    • Cancel long-running operations when the component is disposed.
    • Avoid events that produce large amounts of data.
    • Avoid using user input as part of calls to NavigationManager.NavigateTo and validate user input for URLs against a set of allowed origins first if unavoidable.
    • Don't make authorization decisions based on the state of the UI but only from component state.
    • Consider using Content Security Policy (CSP) to protect against XSS attacks.
    • Consider using CSP and X-Frame-Options to protect against click-jacking.
    • Ensure CORS settings are appropriate when enabling CORS or explicitly disable CORS for Blazor apps.
    • Test to ensure that the server-side limits for the Blazor app provide an acceptable user experience without unacceptable levels of risk. 
    چک لیست امنیتی برنامه های مبتنی بر Blazor
    نظرات مطالب
    چک لیست ارتقاء به HTTPS مخصوص یک برنامه‌ی ASP.NET MVC 5x
    روشی بهتر برای مدیریت محتوای مخلوط (لینک به HTTP و HTTPS در یک صفحه) در مرورگرهای جدید

    در مورد محتوای مخلوط و «اصلاح تمام آدرس‌هایی که پیشتر توسط برنامه تولید شده‌اند» در مطلب فوق نکته‌ای عنوان شده‌است. روش دیگر مدیریت آن که نیازی به اصلاح محتوای صفحات سایت را ندارد، استفاده از هدر امنیتی «Content-Security-Policy: upgrade-insecure-requests» است:
    <system.webServer>
        <httpProtocol>
          <customHeaders>
            <add name="Strict-Transport-Security" value="max-age=16070400; includeSubDomains" />
            <add name="Content-Security-Policy" value="upgrade-insecure-requests;" />
          </customHeaders>
    </httpProtocol>
    کار این هدر ویژه ، ارتقاء خودکار لینک‌های درج شده‌ی در صفحات به نگارش‌های HTTPS آن‌ها است. برای مثال این تصویری است که پیش از اعمال این هدر جهت درخواست آدرس gravatar یک کاربر ارسال شده‌است و اخطار محتوای مخلوط را می‌دهد:


    و این تصویری است که پس از اعمال هدر upgrade-insecure-requests قابل مشاهده‌است که دیگر اخطار محتوای مخلوط را به همراه ندارد:
     

    نظرات اشتراک‌ها
    چگونه به مخازن کد GitHub، ابزار بررسی کیفیت کدهای CodeQL را اضافه کنیم؟
    به صورت خلاصه، دو فایل «codeql.yml » و « codeql-config.yml » را به پوشه‌ی github/workflows. اضافه کنید. نتیجه‌ی آن در برگه‌ی security، فقط برای صاحب مخزن کد قابل مشاهده خواهد بود:

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

      اگر خواستید از موردی صرفنظر کند، rule id فوق را به فایل codeql-config.yml، اضافه کنید.