نظرات مطالب
دریافت و نمایش فایل‌های PDF در برنامه‌های Blazor WASM
به روز رسانی به Blazor 6x

اگر مثال فوق را بر اساس Blazor 6x اجرا کنید، با خطای زیر مواجه خواهید شد:
Microsoft.JSInterop.JSException: Failed to execute 'atob' on 'Window': The string to be decoded is not correctly encoded. 
Error: Failed to execute 'atob' on 'Window': The string to be decoded is not correctly encoded. at Object.createBlobUrl
علت اینجا است که در جهت بالا رفتن کارآیی برنامه‌های Blazor، دیگر یک byte array به صورت base64 به سمت کدهای جاوااسکریپتی ارسال نمی‌شود و مستقیما به صورت Uint8Array ارسال خواهد شد. اطلاعات بیشتر
بنابراین در کدهای فوق، قطعه کد قبلی زیر:
window.JsBinaryFilesUtils = {
  createBlobUrl: function (byteArray, contentType) {
    // The byte array in .NET is encoded to base64 string when it passes to JavaScript.
    const numArray = atob(byteArray)
      .split("")
      .map((c) => c.charCodeAt(0));
    const uint8Array = new Uint8Array(numArray);
    const blob = new Blob([uint8Array], { type: contentType });
    return URL.createObjectURL(blob);
  },
به صورت زیر ساده می‌شود و دیگر نیازی به encoding و decoding ندارد:
window.JsBinaryFilesUtils = {
  createBlobUrl: function (byteArray, contentType) {
    const blob = new Blob([byteArray], { type: contentType });
    return URL.createObjectURL(blob);
  },
نظرات مطالب
Blazor 5x - قسمت 13 - کار با فرم‌ها - بخش 1 - کار با EF Core در برنامه‌های Blazor Server
پیاده سازی دیتابیس در blazor server با blazor wasm تفاوت دارد. تا آنجایی که من اطلاع دارم شما در یک پروژه blazor webassembly نمیتوانید دستور ef migrations را اجرا کنید. چون wasm اصلا فایل startup.cs را ندارد (بررسی ساختار پروژه ) و این نوع پروژه‌ها بیشتر در پروژه‌های کلاینتی استفاده میشوند و شما برای کار با امکانات سروری asp.net core باید از api استفاده کنید و راه حلی که من پیدا و پیاده سازی کردم :
- یک پروژه api بسازید:
dotnet new webapi -o WebService
بعد از اضافه کردن، باید آن را به solution نیز اضافه کنید:
dotnet sln add WebService/WebService.csproj

سپس class library مدل‌ها یا لایه دیتای خود را به این پروژه با استفاده از دستور زیر رفرنس دهید:
dotnet add Web/Web.csproj reference DataLayer/DataLayer.csproj
حالا به راحتی میتوانید دستور‌های ef را در پروژه webassebly خود اجرا کنید.
چند نکته:
- context خود را بسازید
- استارت‌آپ  و appsetting.json را طبق همین مقاله تنظیم کنید
- اگر در هنگام migration نیاز به نصب بسته ای بود نام بسته مورد نظر را جای دستور زیر قرار بدهید:
برای مثال
cd YourFolder
dotnet add package Microsoft.EntityFrameworkCore.Design
نمونه دستورات ef که در متن اصلی مقاله نیز توضیح داده شده:
dotnet tool update --global dotnet-ef

dotnet ef migrations --startup-project WebService/ add Init -p DataLayer --context SqlServerContext

dotnet ef  --startup-project WebService/ database update Init -p DataLayer --context SqlServerContext

نظرات مطالب
PowerShell 7.x - قسمت دوازدهم - آشنایی با GitHub Actions و بررسی یک مثال
یک نکته‌ی تکمیلی: شبیه سازی mikeal/publish-to-github-action@master با کدنویسی مستقیم
این اکشن که جهت به روز رسانی مخزن کد، با تغییرات جدید است، بر اساس سرورهای لینوکسی کار می‌کند و اگر از سرور ویندوزی استفاده شود، خطا خواهد داد که می‌شود آن‌را با معادل زیر، جایگزین کرد:
      - name: Push changes to repo          
        run: |      
          git config http.sslVerify false
          git config user.name "${{ github.actor }}"
          git config user.email "${{ github.actor }}@users.noreply.github.com"
          git remote add publisher "https://${{ github.actor }}:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}.git"
          git show-ref
          git branch --verbose
          git lfs install
          git checkout main
          git add -A
          git commit -m "Automated publish"
          git pull --rebase publisher main
          git push publisher main
نکته‌ی مهم! اگر این قطعه کد را به انتهای فایل yml اکشن خود اضافه کنید، با خطای «نداشتن دسترسی»، خاتمه پیدا می‌کند. برای رفع این مشکل، مسیر «Settings > Actions > General» را در مخزن کد جاری، طی کرده و در انتهای صفحه، گزینه‌ی «Read and write permissions» را انتخاب کنید. حالت پیش‌فرض آن «Read repository contents permission» است که برای اعمال تغییرات کافی نیست.
مطالب
پیاده سازی پروژه‌های React با TypeScript - قسمت هشتم - تعیین نوع کامپوننت‌های کلاسی
با ارائه‌ی React Hooks، استفاده‌ی از کامپوننت‌های کلاسی، کمتر و کمتر شده‌اند. اما جهت تکمیل و خاتمه‌ی سری جاری، این نوع را نیز در این قسمت بررسی می‌کنیم.


تعیین نوع props و state در کامپوننت‌های کلاسی

برای این منظور ابتدا فایل جدید src\components\BigC.tsx را ایجاد کرده و سپس توسط میانبر rcc، ساختار ابتدایی این کامپوننت را ایجاد می‌کنیم. در ادامه آن‌را در کامپوننت src\App.tsx استفاده خواهیم کرد:


یکی از مزایای دیگر کار با تایپ‌اسکریپت، فعال شدن intellisense مرتبط با افزونه‌هایی مانند typescript hero و auto import است که نمونه‌ای از آن‌را در تصویر فوق مشاهده می‌کنید. فقط کافی است نام المان مرتبط را نوشت و سپس با استفاده از افزونه‌های یاد شده، به صورت خودکار import آن‌را اضافه کرد.

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


در اینجا نیز همانند کامپوننت‌های تابعی، باید ابتدا نوع Props را تعیین کرد و تنها تفاوت آن، روش معرفی این type جدید به کامپوننت است:
import React, { Component } from "react";

type Props = {
  title: string;
};

type State = {
  status: string;
};

export default class BigC extends Component<Props, State> {
  render() {
    return (
      <div>
        <h1>I'm in a class component</h1>
      </div>
    );
  }
}
در کامپوننت‌های کلاسی، نوع State و نوع Props، از طریق آرگومان‌های جنریک Component، به نحوی که مشاهده می‌کنید، مشخص می‌شوند.
مابقی نکات آن مانند props اختیاری و غیره، تفاوت خاصی را با کامپوننت‌های تابعی که پیشتر بررسی کردیم، ندارند و یکی هستند.


مقایسه‌ای بین Types و Interfaces

در این سری بیشتر از types استفاده شد، تا اینترفیس‌های تایپ‌اسکریپت. برای مثال بجای نوع زیر:
type State = {
   rValue: boolean;
};
می‌توان از اینترفیس معادل آن نیز استفاده کرد:
interface State {
  rValue: boolean;
}
اما از کدامیک باید استفاده کرد؟
در تایپ‌اسکریپت، از type بیشتر برای تعریف یک نوع جدید استفاده می‌شود، اما از اینترفیس‌ها برای تعریف موجودیت‌ها و entities. برای مثال از type می‌توان برای تعریف نامی برای نوع‌های primitive, union, intersection نیز استفاده کرد، اما اینترفیس‌ها همواره برای تعیین نوع اشیاء مورد استفاده قرار می‌گیرند. نمی‌توان دو نوع هم‌نام را تعریف کرد، اما اگر دو اینترفیس هم نام را تعریف کنید، با هم یکی می‌شوند. نوع‌ها برخلاف اینترفیس‌ها قابلیت پیاده سازی نداشته و بیشتر جنبه‌ی یک اعلان را دارند و ... در نهایت استفاده‌ی از هر کدام بیشتر یک انتخاب شخصی است.


منابع تکمیلی

یکی از بهترین منابع تکمیلی استفاده‌ی از TypeScript در React، مخزن کد react-typescript-cheatsheet است که به همراه مجموعه‌ای از نکات مرتبط است.
نظرات مطالب
ارتقاء به ASP.NET Core 1.0 - قسمت 13 - معرفی View Components
باسلام و خسته نباشید
من نیاز به این دارم که تعداد زیادی ViewComponent رو تعریف کنم که در کل پروژه استفاده میشه بنابراین میخوام اونها رو بصورت پوشه‌های تودرتو تعریف کنم تا بتونم راحت مدیریتشون کنم. مثل مسیر زیر:
Views/Shared/Components/SubFolder1/SubFolder2/[NameOfComponent]/Default.cshtml 
ولی وقتی این کار رو می‌کنم ویوها رو در هنگام اجرا نمی‌تونه پیدا کنه؟ چکار باید بکنم؟
اشتراک‌ها
مقدمه‌ای بر NET MAUI.

An Introduction to .NET MAUI For Mobile Development

.NET Multi-platform App UI (.NET MAUI) is a cross-platform framework for creating native mobile and desktop apps with C# and XAML.
.NET MAUI is open-source and is the evolution of Xamarin.Forms, extended from mobile to desktop scenarios, with UI controls rebuilt from the ground up for performance and extensibility. If you've previously used Xamarin.Forms to build cross-platform user interfaces, you'll notice many similarities with .NET MAUI. However, there are also some differences. Using .NET MAUI, you can create multi-platform apps using a single project, but you can add platform-specific source code and resources if necessary. One of the key aims of .NET MAUI is to enable you to implement as much of your app logic and UI layout as possible in a single code-base.

0:00 - Setup Visual Studio and MAUI Project
00:16:25 - Create MAUI Pages with C#
00:27:42 - Create MAUI Pages with XAML
00:32:28 - Explore MAUI Layouts
00:39:38 - Static Shared Resources
00:44:36 - Platform Specific Values
00:50:11 - Page Navigation  

مقدمه‌ای بر NET MAUI.
مطالب
امکان یافتن پیش از موعد مشکلات قالب‌های Angular در نگارش 5 آن
مشکلات کامپوننت‌های Angular را چون با زبان TypeScript تهیه می‌شوند، می‌توان بلافاصله در ادیتور مورد استفاده و یا در حین کامپایل برنامه مشاهده کرد؛ اما یک چنین بررسی در مورد قالب‌های HTML ایی آن در زمان کامپایل انجام نمی‌شود و اگر مشکلی وجود داشته باشد، این مشکلات را صرفا در زمان اجرای برنامه در مرورگر می‌توان مشاهده کرد. برای رفع این مشکل و بهبود این وضعیت، در نگارش 5.2.0 فریم ورک Angular (و همچنین Angular CLI 1.7 به بعد)، پرچم جدیدی به تنظیمات کامپایلر آن اضافه شده‌است که با فعالسازی آن، مشکلات binding احتمالی در قالب‌های کامپوننت‌ها را می‌توان یافت. زمانیکه توسط Angular CLI یک برنامه‌ی Angular را در حالت AoT کامپایل می‌کنیم، کامپایلر مراحلی را طی می‌کند که توسط آن کدهای یک قالب کامپوننت، تبدیل به دستور العمل‌هایی قابل اجرای در مرورگر می‌شوند. در طی یکی از این مراحل، کامپایلر قالب‌های Angular، از کامپایلر TypeScript برای اعتبارسنجی عبارت‌های binding استفاده می‌کند. اکنون می‌توان خروجی این مرحله را نیز در حین کار با Angular CLI، مشاهده و مشکلات گزارش شده‌ی توسط آن‌را برطرف کرد.


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

برای فعالسازی بررسی مشکلات قالب‌های کامپوننت‌ها، نیاز است به فایل تنظیمات کامپایلر TypeScript و یا همان tsconfig.json مراجعه کرد و سپس قسمت جدیدی را به آن به نام angularCompilerOptions، افزود:
{
  "compilerOptions": {
    "experimentalDecorators": true,
    ...
   },
   "angularCompilerOptions": {
     "fullTemplateTypeCheck": true,
     "preserveWhiteSpace": false,
     ...
   }
 }
- در اینجا با معرفی خاصیت fullTemplateTypeCheck و تنظیم آن به true، مشکلات موجود در قالب‌ها را در زمان کامپایل برنامه می‌توانید مشاهده کنید.
- البته این خاصیت در حین استفاده‌ی از یکی از دستورات ng serve --aot  و یا  ng build --prod انتخاب می‌شود.
- مقدار این پرچم در نگارش‌های 5x به صورت پیش‌فرض به false تنظیم شده‌است؛ اما در نگارش 6 آن به true تنظیم خواهد شد. بنابراین بهتر است از هم اکنون کار با آن‌را شروع کنید.


یک مثال: بررسی خاصیت fullTemplateTypeCheck

فرض کنید اینترفیس یک مدل را به صورت زیر تعریف کرده‌اید که فقط دارای خاصیت name است:
export interface PonyModel {
   name: string;
}
سپس یک خاصیت عمومی را بر همین مبنا در کامپوننتی، تعریف و مقدار دهی اولیه کرده‌اید:
import { PonyModel } from "./pony";

@Component({
  selector: "app-detect-common-errors-test",
  templateUrl: "./detect-common-errors-test.component.html",
  styleUrls: ["./detect-common-errors-test.component.css"]
})
export class DetectCommonErrorsTestComponent implements OnInit {

  ponyModel: PonyModel = { name: "Pony1" };
اکنون در قالب این کامپوننت، به شکل زیر از این وهله استفاده شده‌است:
 <p>Hello {{ponyModel.age}}

در این حالت اگر fullTemplateTypeCheck فعال شده باشد و دستور ng build --prod را صادر کنیم، به خروجی ذیل خواهیم رسید:
 \detect-common-errors-test.component.html(5,4): : Property 'age' does not exist on type 'PonyModel'.
همانطور که ملاحظه می‌کنید اینبار خطاهای کامپایل فایل html نیز در خروجی کامپایلر ظاهر شده‌است و عنوان می‌کند خاصیت age در اینترفیس PonyModel وجود خارجی ندارد.

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


یک نکته‌ی تکمیلی
افزونه‌ی Angular Language service نیز یک چنین قابلیتی را به همراه دارد (و حتی در نگارش‌های پیش از 5 نیز قابل استفاده است).
مطالب
React 16x - قسمت 20 - کار با فرم‌ها - بخش 3 - بهبود کیفیت کدهای فرم لاگین
تا اینجا اگر به کدهای کامپوننت فرم لاگینی که ایجاد کردیم دقت کنید، تبدیل شده‌است به محلی برای انباشت حجم قابل توجهی از کد. به این ترتیب اگر قرار باشد فرم‌های جدیدی را تعریف کنیم، نیاز خواهد بود قسمت‌های عمده‌ای از این کدها را در هر جایی تکرار کنیم. بنابراین جهت کاهش مسئولیت‌های آن، نیاز است بازسازی کد (refactoring) قابل ملاحظه‌ای بر روی آن صورت گیرد.


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

قصد داریم قسمت‌هایی از کامپوننت لاگین فعلی را استخراج کرده و آن‌ها را درون یک کامپوننت با قابلیت استفاده‌ی مجدد قرار دهیم:
- خاصیت state: می‌خواهیم تمام فرم‌هایی را که تعریف می‌کنیم، دارای خاصیت errors باشند. بنابراین این خاصیت قابلیت استفاده‌ی مجدد را دارد.
- خاصیت schema: قابلیت استفاده‌ی مجدد را ندارد و مختص فرم لاگین تعریف شده‌است. این منطق از هر فرمی با فرم دیگر، متفاوت است.
- متد validate: در این متد، هیچ نوع وابستگی از آن به مفهوم لاگین وجود ندارد و کاملا قابلیت استفاده‌ی مجدد را دارد. تنها this.state.account آن وابسته‌ی به کامپوننت لاگین است و بدیهی است شیء account را در سایر فرم‌ها نخواهیم داشت و ممکن است نام آن movie یا customer باشد. بنابراین قاعده‌ای را در اینجا تعریف می‌کنیم، بر این مبنا که از این پس، تمام فرم‌های ما دارای خاصیتی به نام data خواهند بود که بیانگر اطلاعات آن فرم می‌باشد. با این تغییر، برای مثال در فرم لاگین، data به شیء account تنظیم می‌شود و در فرمی دیگر به شیء customer.
- متد validateProperty: همانند متد validate است و کاملا قابلیت استفاده‌ی مجدد را دارد.
- متد handleSubmit: قسمت ابتدایی این متد که شامل غیرفعال کردن post back به سرور و اعتبارسنجی فرم است، قابلیت استفاده‌ی مجدد را دارد. اما قسمت دوم آن مانند ارسال فرم به سرور و یا هر عملیات دیگری، از یک فرم به فرم دیگر می‌تواند متفاوت باشد.
 - متد handleChange: این متد نیز قابلیت استفاده‌ی مجدد را دارد؛ چون می‌خواهیم در تمام فرم‌ها در حین تایپ اطلاعات، کار اعتبارسنجی ورودی‌ها صورت گیرد. این متد نیز به this.state.account وابسته‌است که قاعده‌ی تعریف خاصیت data در state، می‌تواند این مشکل را حل کند.
- متد رندر: طراحی آن کاملا وابسته‌است به نوع فرمی که مدنظر می‌باشد؛ اما دکمه‌ی submit آن خیر. بجز برچسب دکمه‌ی submit، مابقی قسمت‌های آن مانند کلاس‌های CSS و منطق فعال‌سازی و غیرفعال‌سازی آن، قابلیت استفاده‌ی مجدد را دارند.

بنابراین در ادامه کار، refactoring کامپوننت فرم لاگین را برای استخراج قسمت‌های با قابلیت استفاده‌ی مجدد آن، انجام خواهیم داد.


تبدیل قسمت‌های با قابلیت استفاده‌ی مجدد کامپوننت لاگین، به یک کامپوننت عمومی

ابتدا کامپوننت عمومی Form را که قابلیت استفاده‌ی مجدد دارد، در فایل جدید src\components\common\form.jsx تعریف کرده و سپس کامپوننت فرم لاگین را طوری تغییر می‌دهیم که از آن، بجای کلاس پیش‌فرض Component، ارث بری کند. به این ترتیب تمام متدهای تعریف شده‌ی در این کامپوننت با قابلیت استفاده‌ی مجدد، در کامپوننت‌های مشتق شده‌ی از آن، در دسترس خواهند بود.

1- در ادامه همانطور که عنوان شد، خاصیت state فرم‌ها باید دارای شیء data و شیء errors باشند تا توسط آن‌ها بتوان اطلاعات کل فرم و اطلاعات خطاهای اعتبارسنجی را ذخیره کرد:
import React, { Component } from "react";

class Form extends Component {
    state = {
        data:{},
        errors:{}
     }
با این تغییر، به فرم login بازگشته و خاصیت account موجود در state آن‌را به data تغییر نام می‌دهیم. برای اینکار بهتر است دکمه‌ی F2 را بر روی نام انتخاب شده‌ی account در VSCode فشار دهید تا تکست باکس تغییر نام آن ظاهر شود. مزیت کار با این ابزار refactoring توکار، اصلاح خودکار تمام ارجاعات به account قبلی، با این نام جدید است. همچنین نام تمام خواصی و متغیرهایی را هم که به account تنظیم کرده بودیم، به data تغییر می‌دهیم تا کار به روز رسانی state بر روی data صورت گیرد و نه account قبلی. در این حالت شاید استفاده از امکانات replace کلی ادیتور، بهتر از استفاده از ویژگی F2 باشد.

2- در ادامه، کاری با خاصیت schema تعریف شده‌ی در کامپوننت لاگین نداریم؛ چون کاملا مختص به آن است. اما متدهای validate و validateProperty آن‌را طور کامل cut کرده و به کامپوننت Form، منتقل می‌کنیم. با این انتقال، چون این متدها از کتابخانه‌ی Joi استفاده می‌کنند، باید import آن‌را نیز به ابتدای ماژول جدید فرم، اضافه کرد:
import Joi from "@hapi/joi";

3- سپس متد رندر کامپوننت Form را کاملا حذف می‌کنیم؛ چون این کامپوننت قرار نیست چیزی را رندر کند.

4- در قسمت دوم متد handleSubmit، برای مثال قرار است ارسال داده‌ها به سرور صورت گیرد. به همین جهت آن‌را تبدیل به متدی مانند doSubmit کرده و سپس کل متد handleSubmit را نیز به کامپوننت Form منتقل می‌کنیم.
  doSubmit = () => {
    // call the server
    console.log("Submitted!");
  };

5- متد handleChange را نیز از کامپوننت فرم لاگین cut کرده و به کامپوننت Form منتقل می‌کنیم.

6- پس از این نقل و انتقالات، کار ارث بری از کامپوننت فرم را در کامپوننت فرم لاگین انجام می‌دهیم:
import Form from "./common/form";
// ...

class LoginForm extends Form {

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

کدهای کامل کامپوننت فرم را از پیوست انتهای بحث می‌توانید دریافت کنید؛ البته تمام متدهای آن‌را در قسمت قبل تکمیل کرده بودیم و در اینجا صرفا یکسری cut/paste صورت گرفتند.


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

1- در متد رندر فعلی کامپوننت فرم لاگین، اگر به دکمه‌ی submit آن دقت کنیم، بجز برچسب آن، مابقی قسمت‌های آن در تمام فرم‌های دیگری که تعریف خواهیم کرد، یکسان خواهند بود. به همین جهت این قسمت را می‌توان تبدیل به یک متد کمکی در کلاس Form کرد:
  renderButton(label) {
    return (
      <button disabled={this.validate()} className="btn btn-primary">
        {label}
      </button>
    );
  }
سپس در متد رندر کامپوننت فرم لاگین، تنها کافی است بجای المان button قبلی، از متد فوق استفاده کنیم:
{this.renderButton("Login")}

2- در قسمت‌های قبل، برچسب، فیلدهای ورودی و تگ‌ها و کلاس‌های بوت استرپی را به کامپوننت Input منتقل کردیم، تا به یک فرم ساده‌تر و با قابلیت نگهداری بالاتری برسیم. هرچند این هدف حاصل شده، اما باز هم تعاریف المان‌های Input قرارگرفته‌ی در متد رندر کامپوننت لاگین، دارای الگوی تکراری ذکر یک خاصیت مشخص، تعریف رویدادگردان‌های مشخص و اطلاعات اعتبارسنجی کاملا مشخصی هستند. به همین جهت تعریف المان Input را هم مانند متد renderButton فوق می‌توان به کلاس پایه Form انتقال داد:
  import Input from "./input";
  //...

  renderInput(name, label) {
    const { data, errors } = this.state;
    return (
      <Input
        name={name}
        label={label}
        value={data[name]}
        onChange={this.handleChange}
        error={errors[name]}
      />
    );
همانطور که مشاهده می‌کنید، با استفاده از [] و دسترسی پویای به خواص اشیاء، می‌توان رندر المان Input را تبدیل به متدی با قابلیت نگهداری بهتر کرد و از تکرار ویژگی‌های name ، label ، value ، onChange و error به ازای هر فیلد مورد نیاز، پرهیز کرد. اکنون با این تغییر، متد رندر کامپوننت فرم لاگین به صورت زیر خلاصه می‌شود که بسیار بهتر است از تعریف تعداد قابل ملاحظه‌ای div و کلاس بوت استرپی، تعریف المان‌ها، اتصال تک تک آن‌ها به خواص تعریف شده، اتصال آن‌ها به رویداد گردان‌ها و همچنین به اعتبارسنج‌ها:
  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        {this.renderInput("username", "Username")}
        {this.renderInput("password", "Password")}
        {this.renderButton("Login")}
      </form>
    );
  }

3- تا اینجا فرم لاگین تعریف شده، یک مشکل کوچک را دارد: فیلد پسورد آن، از نوع text تعریف شده و اطلاعات وارد شده را همانند یک textbox معمولی نمایش می‌دهد. برای رفع این مشکل، پارامتر type را با یک مقدار پیش‌فرض پر استفاده، تعریف کرده و به المان Input اعمال می‌کنیم:
  renderInput(name, label, type = "text") {
    const { data, errors } = this.state;
    return (
      <Input
        name={name}
        type={type}
        label={label}
        value={data[name]}
        onChange={this.handleChange}
        error={errors[name]}
      />
    );
  }

سپس این type را در قسمتی که المان مرتبط را رندر می‌کنیم، با password مقدار دهی خواهیم کرد:
  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        {this.renderInput("username", "Username")}
        {this.renderInput("password", "Password", "password")}
        {this.renderButton("Login")}
      </form>
    );
}
نیازی به ذکر type، در اولین renderInput ذکر شده، نیست؛ چون مقدار این پارامتر را ازمقدار پیش‌فرض text، دریافت می‌کند.

البته این تغییرات تا به اینجا کار نخواهند کرد؛ چون هنوز کلاس المان Input را جهت پذیرش ویژگی جدید type، ویرایش نکرده‌ایم. بنابراین به فایل src\components\common\input.jsx مراجعه کرده و type را به آن اعمال می‌کنیم:
import React from "react";

const Input = ({ name, type, label, value, error, onChange }) => {
  return (
    <div className="form-group">
      <label htmlFor={name}>{label}</label>
      <input
        value={value}
        onChange={onChange}
        id={name}
        name={name}
        type={type}
        className="form-control"
      />
      {error && <div className="alert alert-danger">{error}</div>}
    </div>
  );
};

export default Input;
اکنون اگر تغییرات را ذخیره کرده و به مرورگر مراجعه کنیم، فیلد کلمه‌ی عبور، دیگر حروف وارد شده را نمایش نمی‌دهد و بر اساس نوع استاندارد password، عمل می‌کند.

4- مشکل! آیا باید به ازای هر ویژگی جدیدی که قرار است به این input اعمال کنیم، مانند type در اینجا، نیاز است یک پارامتر جدید را تعریف و سپس از آن استفاده کرد؟ در این حالت اینترفیس این کامپوننت از کنترل خارج می‌شود و همچنین هربار باید آن‌را ویرایش کرد و تغییر داد. به علاوه اگر به تعریف این input دقت کنیم، نام 4 ویژگی آن، با مقادیری که دریافت می‌کنند،  هم نام هستند (ویژگی value با مقدار value و ...):
<input
  value={value}
  name={name}
  type={type}
  onChange={onChange}
  id={name}
  className="form-control"
/>
در کامپوننت جاری، منهای پارامترهایی که نام ویژگی‌های تعریف شده، با نام آن پارامترها در تمام قسمت‌های کامپوننت (نه فقط المان input)، یکی نیستند (name، label و error)، مابقی را می‌توان توسط یک «rest operator»، به این متد ارسال کرد:
import React from "react";

const Input = ({ name, label, error, ...rest }) => {
  return (
    <div className="form-group">
      <label htmlFor={name}>{label}</label>
      <input {...rest} name={name} id={name} className="form-control" />
      {error && <div className="alert alert-danger">{error}</div>}
    </div>
  );
};

export default Input;
بنابراین منهای name، label و error که در قسمت‌های دیگر کامپوننت استفاده می‌شوند، مابقی پارامترهای این کامپوننت تابعی را حذف کرده و با یک rest operator، دریافت می‌کنیم. سپس آن‌ها را به کمک یک spread operator، در المان input، گسترده و درج می‌کنیم. شبیه به اینکار را در قسمت 15 و بخش «ارسال props سفارشی در حین مسیریابی به کامپوننت‌ها» آن انجام داده بودیم. با کمک عملگرهای rest و spread، به سادگی می‌توان هرنوع ویژگی جدیدی را که برای کار با المان input نیاز داریم، به کامپوننت جاری ارسال کرد؛ بدون اینکه نیازی باشد هربار تعریف پارامترهای آن را تغییر دهیم. پارامتر rest تعریف شده، یعنی هر خاصیت دیگری را بجز سه خاصیت name، label و error، به صورت خودکار به این کامپوننت تابعی ارسال کن.
با این تغییر در کامپوننت Input، سایر قسمت‌های برنامه نیازی به تغییر ندارند. برای مثال در متد renderInput، سه ویژگی name، label و error تبدیل به سه پارامتر دریافتی از props می‌شوند (ترتیب ذکر آن‌ها اهمیتی ندارد). مابقی ویژگی‌های تعریف شده‌ی در آن، به صورت خودکار در قسمت input {...rest} درج خواهند شد.


کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: sample-20.zip
نظرات مطالب
شروع به کار با EF Core 1.0 - قسمت 11 - بررسی رابطه‌ی Self Referencing
کلاس مدل
    public class Category
    {
        public int Id { get; set; }

        [StringLength(450)]
        public string Name { get; set; }

        public int? ParentId { get; set; }

        public virtual Category Parent { get; set; }

        public virtual ICollection<Category> Children { get; set; }
    }

    public class CategoryConfiguration : IEntityTypeConfiguration<Category>
    {
        public void Configure(EntityTypeBuilder<Category> builder)
        {
            builder.HasIndex(c => c.ParentId);

            builder.HasOne(c => c.Parent)
                   .WithMany(c => c.Children)
                   .HasForeignKey(c => c.ParentId);
        }
    }
ودر نهایت برای افزودن مایگریشن جدید خطای ذیل
Introducing FOREIGN KEY constraint 'FK_Categories_Categories_ParentId' on table 'Categories' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.
Could not create constraint or index. See previous errors.
مسیرراه‌ها
NoSQL
RavenDb
MongoDb در سی شارپ
بخش اول: عملیات درج و بازخوانی
بخش دوم: عملیات به روزرسانی، حذف و ایندکس گذاری
بخش سوم: ذخیره و بازیابی فایل
بخش چهارم :Chunk
بخش پنجم : پشتیبانی گیری و بازگردانی
بخش ششم: Import & Export
بخش هفتم : دیتاهای ایستا و پویا
بخش هشتم : الگوی Repository