انگیولار گزینه مناسبی میباشد به دلیل کارایی در مقیاس بالا برای پروژه و طراحی ماژولار همچنین برنامه نویس مربوطه به نسبت بقیه فریمورکها سریعتر پیدا میشود .
هر چند تجربه کار با این کتابخانهها بیشتر برای من در حد مشاهده بوده ولی به نظر من به چند دلیل انگیولار برتری داره
اول اینکه مستندات و مثالها و کدهای زیادی ازش در اینترنت یافت میشه
دوم اینکه برنامه نویسهای بیشتری در این کتابخانه در ایران هستند که در صورت پروژه ای که قبلا با انگیولار نوشته شده باشه و نیاز به توسعه در آینده داشته باشه و اگه تیم سابق نباشه میشه افرادی جدیدتری رو سریع و راحتتر پیدا کرد.
سوم اینکه انگیولار توسط گوگل منتشر میشه که نسبت به بقیه معتبرتره و هم اینکه چون بازنویسی کردن احتمال مشکل در آینده براش به شدت پایین میاد.
فکر میکنم اون هشت گزینه ای که شما مطح کردید رو انگیولار به خوبی داره.
اول اینکه مستندات و مثالها و کدهای زیادی ازش در اینترنت یافت میشه
دوم اینکه برنامه نویسهای بیشتری در این کتابخانه در ایران هستند که در صورت پروژه ای که قبلا با انگیولار نوشته شده باشه و نیاز به توسعه در آینده داشته باشه و اگه تیم سابق نباشه میشه افرادی جدیدتری رو سریع و راحتتر پیدا کرد.
سوم اینکه انگیولار توسط گوگل منتشر میشه که نسبت به بقیه معتبرتره و هم اینکه چون بازنویسی کردن احتمال مشکل در آینده براش به شدت پایین میاد.
فکر میکنم اون هشت گزینه ای که شما مطح کردید رو انگیولار به خوبی داره.
سلام توی پروژه من سطح دسترسیها زیاده وقتی میخوام یه توکن تولید کنم حجم توکن خیلی بالا میره چطور این مشکل را رفع کنم
اینم کد تولید توکنه
public async Task<string> GenerateTokenAsync(ApplicationUser User) { var secretKey = Encoding.UTF8.GetBytes(_siteSettings.JwtSettings.SecretKey); var signingCredentials = new SigningCredentials(new SymmetricSecurityKey(secretKey), SecurityAlgorithms.HmacSha256Signature); var encrytionKey = Encoding.UTF8.GetBytes(_siteSettings.JwtSettings.EncrypKey); var encryptingCredentials = new EncryptingCredentials(new SymmetricSecurityKey(encrytionKey), SecurityAlgorithms.Aes128KW, SecurityAlgorithms.Aes128CbcHmacSha256); var tokenDescriptor = new SecurityTokenDescriptor() { Issuer = _siteSettings.JwtSettings.Issuer, Audience = _siteSettings.JwtSettings.Audience, IssuedAt = DateTime.Now, NotBefore = DateTime.Now.AddMinutes(_siteSettings.JwtSettings.NotBeforeMinutes), Expires = DateTime.Now.AddMinutes(_siteSettings.JwtSettings.ExpirationMinutes), SigningCredentials = signingCredentials, Subject = new ClaimsIdentity(await GetClaimsAsync(User)), EncryptingCredentials = encryptingCredentials, }; var tokenHandler = new JwtSecurityTokenHandler(); var securityToken = tokenHandler.CreateToken(tokenDescriptor); return tokenHandler.WriteToken(securityToken); } public async Task<IEnumerable<Claim>> GetClaimsAsync(ApplicationUser User) { var Claims = new List<Claim>() { new Claim(ClaimTypes.Name,User.UserName), new Claim(ClaimTypes.NameIdentifier,User.Id), new Claim(ClaimTypes.MobilePhone,User.PhoneNumber), new Claim(new ClaimsIdentityOptions().SecurityStampClaimType,User.SecurityStamp), new Claim(JwtRegisteredClaimNames.Jti,Guid.NewGuid().ToString()) }; var Roles = _roleManager.Roles.ToList(); foreach (var item in Roles) { var RoleClaims = await _roleManager.FindClaimsInRole(item.Id); foreach (var claim in RoleClaims.Claims) { Claims.Add(new Claim(ConstantPolicies.DynamicPermissionClaimType, claim.ClaimValue)); } } foreach (var item in Roles) Claims.Add(new Claim(ClaimTypes.Role, item.Name)); return Claims; }
توی پروژه ما backendبا .netoreو frontهم با react هست
همه ما میدانیم برای اینکه محتوای ما به وسیله Google و سایر موتورهای جستجو index شود باید این محتوا در سمت سرور ایجاد و به کلاینت ارسال شود. مدتی بود با مقالاتی مواجه میشدم که نیازی به این کار نیست و گوگل این قابلیت را دارد تا اطلاعاتی را که سمت کلاینت پردازش و Render میشوند نیز index کند. تا این که خودم تصمیم گرفتم این مورد را تست کنم.
چند روز پیش شروع به بررسی SEO در AngularJS 1.x کردم. صورت مسئلهی من به این صورت بود که نام تعدادی شهر را با AngularJS در صفحه Render کنم، طوریکه در DOM اولیه که از سرور هدایت میشود، نام شهرها موجود نباشند. کد زیر را مشاهده کنید.<html dir="rtl"> <head> <title>وب سایت</title> <script src="angular.min.js"> </script> </head> <body ng-app="app"> <ul ng-controller="ctrl"> <li ng-repeat="item in list">{{item}}</li> </ul> </body> <script> var app=angular.module('app',[]); app.controller('ctrl',function($scope,$timeout){ $scope.list=[ 'اردبیل', 'تهران', 'شیراز', 'قزوین', ] }); </script> </html>
سپس در وب مستر گوگل، مسیر را تعریف کردم و به crawl گوگل اعلام کردم که این مسیر را index کند. بعد از مدتی متوجه شدم این صفحه با تمام نامهای شهرها index شدهاند!
مسئله را سختتر کردم و این بار به صورت مسئله اولیه این مورد را هم اضافه کردم که بعد از اینکه صفحه بارگذاری شد، بعد از 5 ثانیه، نام شهر مشهد هم به لیست DOMها اضافه شود و به کد بالا این کد را هم اضافه کردم (این کار را برای شبیه سازی درخواست AJAX انجام دادم):
$timeout(function(){ $scope.list.push('مشهد') },5000);
البته نیاز به بررسی دقیقتر این مسئله هست و باید در پروژههای واقعی این مورد را بررسی کرد تا safe بودن این قابلیت گوگل مورد تایید قرار بگیرد. در حال حاضر برای SEO در ReactJS و AngualrJS و VueJS از Render سمت Server استفاده میکنم. اگر این قابلیت به طور 100% جوابگوی SEO باشد، دیگر نیازی نیست Developerها سمت سرور و کلاینت، کارهای تکراری برای SEO انجام دهند.
در این قسمت میخواهیم دانستههای 5 قسمت قبل را در طی یک تمرین کنار هم قرار داده و مرور کنیم.
برپایی ساختار ابتدایی پروژهی تمرین
ابتدا یک پروژهی جدید React را ایجاد میکنیم:
سپس بستههای بوت استرپ و font-awesome را نیز در آن نصب میکنیم:
در ادامه نیاز است فایلهای CSS این کتابخانهها و قلمهای وب را import کنیم. به همین جهت ابتدای فایل index.js را به نحو زیر ویرایش خواهیم کرد:
در نهایت کار مدیریت این فایلها و قرار دادن آنها در بستهی نهایی برنامه، توسط webpack به صورت خودکار انجام میشود.
همچنین به فایل index.css هم مراجعه کرده و یک padding را به بالای صفحه اضافه میکنیم؛ تا اطلاعات نمایش داده شده، با کمی فاصله از لبهی مرورگر رندر شوند:
پس از نصب و import این کتابخانههای ثالث، به فایل App.js مراجعه کرده و کلاس container اصلی بوت استرپ را در آن تعریف میکنیم تا در برگیرندهی محتوای برنامه شود:
همانطور که در قسمت چهارم نیز بحث شد، برای ذکر classهای عناصر در React، از خاصیت className استفاده میشود.
معرفی سرویسهای دادهی برنامه
کدهای نهایی این قسمت را از فایل پیوست شدهی در انتهای مطلب، میتوانید دریافت کنید. در اینجا یک پوشهی src\services تعریف شدهاست که داخل آن دو فایل fakeGenreService.js و fakeMovieService.js قرار دارند. این فایلها، منبع دادهی درون حافظهای مثال تمرین ما هستند.
سرویس fakeGenre چنین ساختاری را دارد و ژانرهای سینمایی، مانند اکشن، کمدی و غیره در آن لیست شدهاند:
این سرویس دارای متد ()getGenres، برای بازگشت لیست کامل genres است. علت ذکر خاصیت id با یک _، روش نامگذاری خاصیت id در mongo-db است.
و سرویس fakeMovie که دارای ساختار کلی زیر است، لیست 9 فیلم سینمایی را به همراه دارد:
به علاوه این سرویس دارای متدهای ()getMovies برای دریافت لیست فیلمها، getMovie(id) برای بازگشت یک فیلم خاص، saveMovie(movie) برای افزودن یک فیلم جدید به لیست و deleteMovie(id) برای حذف یک فیلم از لیست درون حافظهای سرویس جاری است.
ایجاد کامپوننت Movies برای نمایش لیست فیلمها در برنامه
اکنون میخواهیم یک کامپوننت جدید را به نام Movies در فایل جدید src\components\movies.jsx ایجاد کنیم، تا لیست فیلمهای سرویس fakeMovieService را نمایش دهد. برای اینکار مراحل زیر را طی خواهیم کرد:
- نمایش سادهی لیست فیلمها توسط یک جدول. برای دریافت لیست اشیاء موجود در fakeMovieService، از متد ()getMovies آن میتوان استفاده کرد.
- اضافه کردن یک دکمهی حذف، به هر ردیف، به نحوی که با کلیک بر روی آن، آن ردیف حذف شود.
- نمایش یک پیام بالای جدول که تعداد فیلمهای موجود در سرویس درون حافظهای را نمایش میدهد. همچنین پس از حذف تمام ردیفها، باید پیام «فیلمی موجود نیست» را نمایش دهد.
پس از ایجاد فایل خالی جدید movies.jsx در پوشهی جدید components، با استفاده از «simple react snippets» نصب شدهی در VSCode، یکبار imrc را تایپ کرده (مخفف import react component است) و سپس دکمهی tab را فشار میدهیم، در آخر اینکار را برای cc نیز تکرار میکنیم (مخفف create class است) تا importها و سپس ساختار ابتدایی کامپوننت React ما تشکیل شوند. نام این کامپوننت را هم Movies که با حرف بزرگ شروع میشود، وارد میکنیم.
اکنون مجددا به App.js مراجعه میکنیم و بجای Hello world ای که نمایش دادیم، کامپوننت Movies را اضافه میکنیم. برای این منظور ابتدا import آنرا به ابتدای فایل اضافه میکنیم:
سپس متد return آنرا جهت درج المان کامپوننت Movies اصلاح خواهیم کرد:
دریافت لیست اشیاء فیلمها از سرویس fakeMovieService
برای دریافت لیست اشیاء فیلمها، ابتدا تعریف سرویس آنرا به ابتدای کامپوننت Movies اضافه میکنیم:
در اینجا از {} استفاده شده، چون یک named export را import کردهایم.
سپس خاصیت state را جهت تعریف خاصیت movies که با متد ()getMovies سرویس fakeMovieService مقدار دهی میشود، به نحو زیر تکمیل میکنیم:
البته این روش مقدار دهی اولیهی خاصیت state، برای دریافت اطلاعات سرویسها، هرچند در اینجا بدون مشکل کار میکند، اما بهتر است توسط component life cycle hooks مدیریت شود که در قسمتهای بعدی بیشتر به جزئیات آنها خواهیم پرداخت.
نمایش لیست فیلمها، به همراه مدیریت حذف هر ردیف
در ادامه، کدهای کامل و تکمیل شدهی این کامپوننت را ملاحظه میکنید:
توضیحات:
همانطور که در ابتدای بحث نیز ذکر شد، هدف از این تمرین، مرور قسمتهای قبل است و تمام نکات زیر را در قسمتهای پیشین، با جزئیات بیشتری بررسی کردهایم:
- ابتدا خاصیت state و سپس خاصیت movies شیء منتسب به آن، با لیست فیلمهای موجود در سرویس مرتبط، مقدار دهی شدهاند.
- سپس در ابتدای متد render، کار رندر شرطی انجام شدهاست. اگر تعداد فیلمهای دریافتی صفر بود، پیام «فیلمی در بانک اطلاعاتی موجود نیست» نمایش داده میشود و در غیراینصورت، جدول اصلی بوت استرپی برنامه رندر خواهد شد.
در اینجا چون از خاصیت طول آرایهی فیلمها در چندین قسمت قرار است استفاده شود، آنرا توسط Object Destructuring به یک متغیر نسبت دادهایم. همچنین توسط یک نام مستعار هم خاصیت length را با نام جدید count استفاده میکنیم.
- در ادامه بازگشت React.Fragment را مشاهده میکنید. علت اینجا است که نمیخواهیم div اضافهتری را در UI رندر کنیم. React.Fragment سبب میشود تا بتوانیم چندین فرزند را به المان جاری تبدیل شدهی به کدهای جاوا اسکریپتی اضافه کنیم، بدون اینکه خودش به المانی ترجمه شود.
- پس از return، یک () قابل مشاهدهاست. چون خروجی return ما چند سطری است، اگر در سطری که return قرار میگیرد، اطلاعاتی درج نشود، موتور جاوا اسکریپت آنرا با یک سمیکالن خاتمه خواهد داد! و دیگر سطرهای بعدی دیده نمیشوند و پردازش نخواهند شد. به همین جهت از روش ذکر یک () پس از return در فایلهای jsx زیاد استفاده میشود.
- در ابتدای return، همان خاصیت count را نمایش میدهیم.
- سپس کار رندر جدول اصلی برنامه که با کلاسهای جداول بوت استرپ نیز مزین شده، انجام شدهاست. در React برای عدم تداخل ویژگی class با نام از پیش رزرو شدهی class، از خاصیت className برای ذکر کلاسهای CSSای استفاده میشود.
- قسمت thead این جدول مشخص است و سرستونهای جدول را مشخص میکند.
- پس از آن نیاز است ردیفهای جدول را رندر کنیم. اینکار را توسط متد Array.map، با نگاشت هر آیتم آرایهی this.state.movies، به یک tr جدول انجام دادهایم.
- React برای اینکه بتواند DOM مجازی خودش را کنترل کند، نیاز دارد عناصر موجود در آنرا به صورت منحصربفردی تشخص دهد. به همین جهت در اینجا ذکر key را بر روی المان tr که با movie._id مقدار دهی شدهاست، مشاهده میکنید.
- رندر مقادیر سلولهای ردیفها توسط درج {} و سپس ذکر مقداری از شیء movie دریافتی توسط متد Array.map انجام میشود.
- در اینجا ستون رندر دکمهی Delete را نیز مشاهده میکنید. برای مدیریت this در آن و دسترسی به شیء movie جاری (ارسال پارامتر به رویداد گردان آن) و همچنین دسترسی به شیء this کلاس جاری برای کار با آرایهی this.state.movies، از روش arrow functions برای تعریف رویدادگردان onClick استفاده کردهایم.
- در متد handleDelete، یک آرایهی جدید را که id ردیفهای آن با id شیء ردیف انتخابی یکی نیست، بازگشت میدهیم. انتساب این آرایهی جدید به آرایهی this.state.movies، تغییری را در برنامههای React ایجاد نمیکند. در اینجا باید توسط متد this.setState که از کلاس پایهی extends Component دریافت میشود، خاصیت movies را بازنویسی کرد تا React از تغییرات مطلع شده و DOM مجازی جدیدی را با مقایسهی با نمونه جدید، محاسبه کرده و به DOM اصلی، جهت به روز رسانی UI اعمال کند.
- البته در اینجا this.setState({ movies }) را بجای this.setState({ movies: movies }) مشاهده میکنید. علت اینجا است که اگر عبارات key و value یکی باشند، میتوان تنها همان عبارت key را جهت حذف تکرار واژهها، ذکر کرد.
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: sample-05.zip
برپایی ساختار ابتدایی پروژهی تمرین
ابتدا یک پروژهی جدید React را ایجاد میکنیم:
> create-react-app sample-05 > cd sample-05 > npm start
> npm install --save bootstrap > npm install --save font-awesome
در ادامه نیاز است فایلهای CSS این کتابخانهها و قلمهای وب را import کنیم. به همین جهت ابتدای فایل index.js را به نحو زیر ویرایش خواهیم کرد:
import "bootstrap/dist/css/bootstrap.css"; import "font-awesome/css/font-awesome.css";
همچنین به فایل index.css هم مراجعه کرده و یک padding را به بالای صفحه اضافه میکنیم؛ تا اطلاعات نمایش داده شده، با کمی فاصله از لبهی مرورگر رندر شوند:
body { margin: 0; padding: 20px 0 0 0; font-family: sans-serif; }
پس از نصب و import این کتابخانههای ثالث، به فایل App.js مراجعه کرده و کلاس container اصلی بوت استرپ را در آن تعریف میکنیم تا در برگیرندهی محتوای برنامه شود:
return ( <main className="container"> <h1>Hello world!</h1> </main> );
معرفی سرویسهای دادهی برنامه
کدهای نهایی این قسمت را از فایل پیوست شدهی در انتهای مطلب، میتوانید دریافت کنید. در اینجا یک پوشهی src\services تعریف شدهاست که داخل آن دو فایل fakeGenreService.js و fakeMovieService.js قرار دارند. این فایلها، منبع دادهی درون حافظهای مثال تمرین ما هستند.
سرویس fakeGenre چنین ساختاری را دارد و ژانرهای سینمایی، مانند اکشن، کمدی و غیره در آن لیست شدهاند:
export const genres = [ { _id: "5b21ca3eeb7f6fbccd471818", name: "Action" }, // ... ];
و سرویس fakeMovie که دارای ساختار کلی زیر است، لیست 9 فیلم سینمایی را به همراه دارد:
const movies = [ { _id: "5b21ca3eeb7f6fbccd471815", title: "Terminator", genre: { _id: "5b21ca3eeb7f6fbccd471818", name: "Action" }, numberInStock: 6, dailyRentalRate: 2.5, publishDate: "2018-01-03T19:04:28.809Z" }, //... ];
ایجاد کامپوننت Movies برای نمایش لیست فیلمها در برنامه
اکنون میخواهیم یک کامپوننت جدید را به نام Movies در فایل جدید src\components\movies.jsx ایجاد کنیم، تا لیست فیلمهای سرویس fakeMovieService را نمایش دهد. برای اینکار مراحل زیر را طی خواهیم کرد:
- نمایش سادهی لیست فیلمها توسط یک جدول. برای دریافت لیست اشیاء موجود در fakeMovieService، از متد ()getMovies آن میتوان استفاده کرد.
- اضافه کردن یک دکمهی حذف، به هر ردیف، به نحوی که با کلیک بر روی آن، آن ردیف حذف شود.
- نمایش یک پیام بالای جدول که تعداد فیلمهای موجود در سرویس درون حافظهای را نمایش میدهد. همچنین پس از حذف تمام ردیفها، باید پیام «فیلمی موجود نیست» را نمایش دهد.
خروجی نهایی مثال ما به صورت زیر است:
و اگر تمام آیتمهای آنرا حذف کنیم، چنین پیامی نمایش داده میشود:
پس از ایجاد فایل خالی جدید movies.jsx در پوشهی جدید components، با استفاده از «simple react snippets» نصب شدهی در VSCode، یکبار imrc را تایپ کرده (مخفف import react component است) و سپس دکمهی tab را فشار میدهیم، در آخر اینکار را برای cc نیز تکرار میکنیم (مخفف create class است) تا importها و سپس ساختار ابتدایی کامپوننت React ما تشکیل شوند. نام این کامپوننت را هم Movies که با حرف بزرگ شروع میشود، وارد میکنیم.
اکنون مجددا به App.js مراجعه میکنیم و بجای Hello world ای که نمایش دادیم، کامپوننت Movies را اضافه میکنیم. برای این منظور ابتدا import آنرا به ابتدای فایل اضافه میکنیم:
import Movies from "./components/movies";
return ( <main className="container"> <Movies /> </main> );
دریافت لیست اشیاء فیلمها از سرویس fakeMovieService
برای دریافت لیست اشیاء فیلمها، ابتدا تعریف سرویس آنرا به ابتدای کامپوننت Movies اضافه میکنیم:
import { getMovies } from "../services/fakeMovieService";
سپس خاصیت state را جهت تعریف خاصیت movies که با متد ()getMovies سرویس fakeMovieService مقدار دهی میشود، به نحو زیر تکمیل میکنیم:
state = { movies: getMovies() };
نمایش لیست فیلمها، به همراه مدیریت حذف هر ردیف
در ادامه، کدهای کامل و تکمیل شدهی این کامپوننت را ملاحظه میکنید:
import React, { Component } from "react"; import { getMovies } from "../services/fakeMovieService"; class Movies extends Component { state = { movies: getMovies() }; handleDelete = movie => { const movies = this.state.movies.filter(m => m._id !== movie._id); this.setState({ movies }); }; render() { const { length: count } = this.state.movies; if (count === 0) return <p>There are no movies in the database.</p>; return ( <React.Fragment> <p>Showing {count} movies in the database.</p> <table className="table"> <thead> <tr> <th>Title</th> <th>Genre</th> <th>Stock</th> <th>Rate</th> <th /> </tr> </thead> <tbody> {this.state.movies.map(movie => ( <tr key={movie._id}> <td>{movie.title}</td> <td>{movie.genre.name}</td> <td>{movie.numberInStock}</td> <td>{movie.dailyRentalRate}</td> <td> <button onClick={() => this.handleDelete(movie)} className="btn btn-danger btn-sm" > Delete </button> </td> </tr> ))} </tbody> </table> </React.Fragment> ); } } export default Movies;
همانطور که در ابتدای بحث نیز ذکر شد، هدف از این تمرین، مرور قسمتهای قبل است و تمام نکات زیر را در قسمتهای پیشین، با جزئیات بیشتری بررسی کردهایم:
- ابتدا خاصیت state و سپس خاصیت movies شیء منتسب به آن، با لیست فیلمهای موجود در سرویس مرتبط، مقدار دهی شدهاند.
- سپس در ابتدای متد render، کار رندر شرطی انجام شدهاست. اگر تعداد فیلمهای دریافتی صفر بود، پیام «فیلمی در بانک اطلاعاتی موجود نیست» نمایش داده میشود و در غیراینصورت، جدول اصلی بوت استرپی برنامه رندر خواهد شد.
در اینجا چون از خاصیت طول آرایهی فیلمها در چندین قسمت قرار است استفاده شود، آنرا توسط Object Destructuring به یک متغیر نسبت دادهایم. همچنین توسط یک نام مستعار هم خاصیت length را با نام جدید count استفاده میکنیم.
- در ادامه بازگشت React.Fragment را مشاهده میکنید. علت اینجا است که نمیخواهیم div اضافهتری را در UI رندر کنیم. React.Fragment سبب میشود تا بتوانیم چندین فرزند را به المان جاری تبدیل شدهی به کدهای جاوا اسکریپتی اضافه کنیم، بدون اینکه خودش به المانی ترجمه شود.
- پس از return، یک () قابل مشاهدهاست. چون خروجی return ما چند سطری است، اگر در سطری که return قرار میگیرد، اطلاعاتی درج نشود، موتور جاوا اسکریپت آنرا با یک سمیکالن خاتمه خواهد داد! و دیگر سطرهای بعدی دیده نمیشوند و پردازش نخواهند شد. به همین جهت از روش ذکر یک () پس از return در فایلهای jsx زیاد استفاده میشود.
- در ابتدای return، همان خاصیت count را نمایش میدهیم.
- سپس کار رندر جدول اصلی برنامه که با کلاسهای جداول بوت استرپ نیز مزین شده، انجام شدهاست. در React برای عدم تداخل ویژگی class با نام از پیش رزرو شدهی class، از خاصیت className برای ذکر کلاسهای CSSای استفاده میشود.
- قسمت thead این جدول مشخص است و سرستونهای جدول را مشخص میکند.
- پس از آن نیاز است ردیفهای جدول را رندر کنیم. اینکار را توسط متد Array.map، با نگاشت هر آیتم آرایهی this.state.movies، به یک tr جدول انجام دادهایم.
- React برای اینکه بتواند DOM مجازی خودش را کنترل کند، نیاز دارد عناصر موجود در آنرا به صورت منحصربفردی تشخص دهد. به همین جهت در اینجا ذکر key را بر روی المان tr که با movie._id مقدار دهی شدهاست، مشاهده میکنید.
- رندر مقادیر سلولهای ردیفها توسط درج {} و سپس ذکر مقداری از شیء movie دریافتی توسط متد Array.map انجام میشود.
- در اینجا ستون رندر دکمهی Delete را نیز مشاهده میکنید. برای مدیریت this در آن و دسترسی به شیء movie جاری (ارسال پارامتر به رویداد گردان آن) و همچنین دسترسی به شیء this کلاس جاری برای کار با آرایهی this.state.movies، از روش arrow functions برای تعریف رویدادگردان onClick استفاده کردهایم.
- در متد handleDelete، یک آرایهی جدید را که id ردیفهای آن با id شیء ردیف انتخابی یکی نیست، بازگشت میدهیم. انتساب این آرایهی جدید به آرایهی this.state.movies، تغییری را در برنامههای React ایجاد نمیکند. در اینجا باید توسط متد this.setState که از کلاس پایهی extends Component دریافت میشود، خاصیت movies را بازنویسی کرد تا React از تغییرات مطلع شده و DOM مجازی جدیدی را با مقایسهی با نمونه جدید، محاسبه کرده و به DOM اصلی، جهت به روز رسانی UI اعمال کند.
- البته در اینجا this.setState({ movies }) را بجای this.setState({ movies: movies }) مشاهده میکنید. علت اینجا است که اگر عبارات key و value یکی باشند، میتوان تنها همان عبارت key را جهت حذف تکرار واژهها، ذکر کرد.
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: sample-05.zip
در این پست قصد دارم روش استفاه از ServiceLoctor رو به وسیله یک مثال ساده بهتون نمایش بدم. Microsoft Unity روش توصیه شده Microsoft برای پیاده سازی Dependecy Injecttion و ServiceLocator Pattern است. یک ServiceLocator در واقع وظیفه تهیه Instanceهای مختلف از کلاسها رو برای پیاده سازی Dependency Injection بر عهده داره.
برای شروع یک پروژه از نوع Console Application ایجاد کنید و یک ارجاع به Assemblyهای زیر رو در برنامه قرار بدید.
حالا باید Repository مربوطه رو تهیه کنید. ایتدا یک Interface به صورت زیر ایجاد کنید.
سپس کلاسی ایجاد کنید که این Interface رو پیاده سازی کنه.
کلاس BookRepository یک لیست از Book رو ایجاد میکنه و اونو برگشت میده.
در مرحله بعد باید Service مربوطه برای استفاده از این Repository ایجاد کنید. ولی باید Repository رو به Constructor این کلاس Service پاس بدید. اما برای انجام این کار باید از ServiceLocator استفاده کنیم.
همان طور که میبینید این کلاس دو تا Constructor داره که در حالت اول باید یک IBookRepository رو به کلاس پاس داد و در حالت دوم ServiceLocator این کلاس رو برای استفاده دز اختیار سرویس قرار میده.
متد Print هم تمام کتابهای مربوطه رو برامون چاپ میکنه.
در مرحله آخر باید ServiceLocator رو تنظیم کنید. برای این کار کدهای زیر رو در کلاس Program قرار بدید.
در این کلاس ابتدا یک UnityContainer ایجاد کردم و اینترفیس IBookRepository رو به کلاس BookRepository؛ Register کردم تا هر جا که به IBookRepository نیاز داشتم یک Instance از کلاس BookRepository ایجاد بشه. در خط بعدی ServiceLocator برنامه رو ست کردم و برای این کار از کلاس UnityServiceLocator استفاده کردم .
بعد از اجرای برنامه خروجی زیر قابل مشاهده است.
برای شروع یک پروژه از نوع Console Application ایجاد کنید و یک ارجاع به Assemblyهای زیر رو در برنامه قرار بدید.
- Microsoft.Practices.ServiceLocation
- Microsoft.Practices.Unity
- Microsoft.Practices.EnterpriseLibrary.Common
اگر Assemblyهای بالا رو در اختیار ندارید میتونید اونها رو از اینجا دانلود کنید. Microsoft Enterprise Library یک کتابخانه تهیه شده توسط شرکت Microsoft است که شامل موارد زیر است و بعد از نصب میتونید در قسمتهای مختلف برنامه از اونها استفاده کنید.
- Enterprise Library Caching Application Block : یک CacheManager قدرتمند در اختیار ما قرار میده که میتونید از اون برای کش کردن دادهها استفاده کنید.
Enterprise Library Exception Handling Application Block : یک کتابخانه مناسب و راحت برای پیاده سازی یک Exception Handler در برنامهها است.
Enterprise Library Loggin Application Block : برای تهیه یک Log Manager در برنامه استفاده میشود.
Enterprise Library Validation Application Block : برای اجرای Validation برای Entityها با استفاده از Attribute میتونید از این قسمت استفاده کنید.
Enterprise Library DataAccess Application Block : یک کتابخانه قدرتمند برای ایجاد یک DataAccess Layer است با Performance بسیار بالا.- Enterprise Library Shared Library: برای استفاده از تمام موارد بالا در پروژه باید این Dll رو هم به پروژه Reference بدید. چون برای همشون مشترک است.
برای اجرای مثال ابتدا کلاس زیر رو به عنوان مدل وارد کنید.
public class Book { public string Title { get; set; } public string ISBN { get; set; } }
حالا باید Repository مربوطه رو تهیه کنید. ایتدا یک Interface به صورت زیر ایجاد کنید.
public interface IBookRepository { List<Book> GetBooks(); }
public class BookRepository : IBookRepository { public List<Book> GetBooks() { List<Book> listOfBooks = new List<Book>(); listOfBooks.AddRange( new Book[] { new Book(){Title="Book1" , ISBN="123"}, new Book(){Title="Book2" , ISBN="456"}, new Book(){Title="Book3" , ISBN="789"}, new Book(){Title="Book4" , ISBN="321"}, new Book(){Title="Book5" , ISBN="654"}, } ); return listOfBooks; } }
در مرحله بعد باید Service مربوطه برای استفاده از این Repository ایجاد کنید. ولی باید Repository رو به Constructor این کلاس Service پاس بدید. اما برای انجام این کار باید از ServiceLocator استفاده کنیم.
public class BookService { public BookService() : this( ServiceLocator.Current.GetInstance<IBookRepository>() ) { } public BookService( IBookRepository bookRepository ) { this.BookRepository = bookRepository; } public IBookRepository BookRepository { get; private set; } public void PrintAllBooks() { Console.WriteLine( "List Of All Books" ); BookRepository.GetBooks().ForEach( ( Book item ) => { Console.WriteLine( item.Title ); } ); } }
متد Print هم تمام کتابهای مربوطه رو برامون چاپ میکنه.
در مرحله آخر باید ServiceLocator رو تنظیم کنید. برای این کار کدهای زیر رو در کلاس Program قرار بدید.
class Program { static void Main( string[] args ) { IUnityContainer unityContainer = new UnityContainer(); unityContainer.RegisterType<IBookRepository, BookRepository>(); ServiceLocator.SetLocatorProvider( () => new UnityServiceLocator( unityContainer ) ); BookService service = new BookService(); service.PrintAllBooks(); Console.ReadLine(); } }
بعد از اجرای برنامه خروجی زیر قابل مشاهده است.
در قسمت قبل، نوع توابع ارسالی از طریق props را تعیین کردیم. فرض کنید در همان مثال میخواهیم بجای ارسال یک رشته به فراخوان کامپوننت تعریف شده، اصل رخداد واقع شده را ارسال کنیم. به همین جهت onClick دریافتی را مستقیما به رخداد onClick، نسبت میدهیم:
در این حالت بلافاصله با خطای زیر مواجه خواهیم شد که عنوان میکند امضای تابع onClick، با امضای رویداد مدنظر یکی نیست:
برای رفع این مشکل، میتوان رخداد کلیک ماوس بر روی یک دکمه را از نوع بسیار عمومی React.MouseEvent تعریف کرد:
محدود کردن دامنهی رویدادها
زمانیکه نوع یک رویداد به صورت کلی e: React.MouseEvent تعریف میشود، اگر به تصویر فوق دقت کنیم، تایپاسکریپت آنرا قابل اعمال به هر نوع Element ای میداند. اما اگر بخواهیم دامنهی دید آنرا صرفا به المان استاندارد button تعریف شده محدود کنیم، اشارهگر ماوس را در فایل src\components\Button.tsx بر روی رویداد onClick المان button تعریف شده قرار میدهیم:
همانطور که مشخص است، نوع این المان را به صورت HTMLButtonElement ذکر کردهاست. بنابراین میتوان تعریف کلی قبلی را به صورت زیر محدود کرد:
در اینجا توسط generics، نوع React.MouseEvent به HTMLButtonElement محدود شدهاست و دیگر اینبار بازهی دید آن یک Element کلی نیست. این تعریف عنوان میکند که رخداد واقع شده، فقط از یک button صادر شدهاست و نه از المان نوع دیگری. به این ترتیب در حین استفادهی از رخداد رسیده در کامپوننت App در برگیرندهی آن، به متدها و رخدادهای ویژهتری نیز دسترسی خواهیم یافت.
تعیین نوع رخداد onChange
در ادامه فرض کنید میخواهیم اطلاعات رویداد onChange را نیز صادر کنیم. روش عمومی تشخیص نوع آن، قرار دادن اشارهگر ماوس بر روی رویداد مدنظر و سپس استفاده از همان نوعی است که نمایش میدهد؛ مانند تصویر زیر:
در اینجا یا میتوان از رویداد اختصاصی React.ChangeEvent از نوع HTMLInputElement، استفاده کرد و یا از حالت عمومیتر React.FormEvent.
سؤال: اطلاعات نمایش داده شدهی نوعهای متناظر با رویدادهای مختلف در تصاویر فوق، از کجا تامین میشوند؟
اگر به فایل package.json پروژهی تایپاسکریپتی ایجاد شده مراجعه کنید، میتوانید حداقل دو مدخل جدید زیر را نیز در آن مشاهده کنید:
این بستههای جدید، حاوی تعاریف تمام نوعهای تایپاسکریپتی مرتبط با اشیاء یک پروژهی React هستند. برای مثال در فایل src\components\Button.tsx، اشارهگر ماوس را بر روی MouseEvent تعریف شده قرار دهید و سپس دکمهی ctrl را نگه دارید. مشاهده خواهید کرد که تعریف این نوع، تبدیل به یک لینک قابل کلیک خواهد شد. اگر بر روی آن کلیک کنید، به فایل node_modules\@types\react\index.d.ts پروژهی خود هدایت میشوید که دقیقا محل تعریف این رویداد و موارد مشابه است.
سؤال: آیا بستههای @types دار موجود در فایل package.json، حجم فایلهای نهایی برنامه را افزایش میدهند؟ آیا برنامههای React مبتنی بر TypeScript، حجم بیشتری را نسبت به نمونههای ES6 آن دارند؟
اکثر این تعاریف @types به صورت اینترفیسهای تایپاسکریپتی تعریف شدهاند که صرفا در زمان کامپایل برنامه، کمک حال کامپایلر آن خواهند بود و پس از کامپایل، تبخیر شده و در کدهای نهایی تولیدی، حضور نخواهند داشت. به عبارتی استفادهی از تایپاسکریپت، حجم نهایی پروژهها را افزایش نمیدهد؛ چون اینترفیسهای تایپاسکریپت، معادلی را در جاوااسکریپت استاندارد ندارند و فقط به عنوان راهنمای کامپایلر تایپاسکریپت عمل میکنند.
یک نکته: تعریف رویدادهای مدنظر را میتوان در importها نیز قرار داد:
که در این حالت دیگر نیازی به ذکر کامل React.FormEvent نیست. اما در کل، روش تعریف React.xyz، مرسومتر است.
تعریف نوع Children Props
زمانیکه داخل تعریف المان یک کامپوننت، کامپوننت دیگری ذکر میشود، به عنوان فرزند او در React درنظر گرفته میشود. برای نمونه تعریف کامپوننت Button را در فایل src\App.tsx به صورت زیر تغییر میدهیم تا فرزندی را به صورت «Hello world» بپذیرد:
اکنون سؤال اینجا است که چگونه میتوان نوع آنرا توسط تایپاسکریپت مشخص کرد؟
با توجه به اینکه این فرزند، از نوع رشتهای است، فقط کافی است خاصیت children را با همین نوع، به Props اضافه کرده و از آن استفاده کنیم:
در ادامه، مثال فوق را اندکی مشکلتر میکنیم. فرض کنید اینبار بجای یک رشته، یک المان image، به عنوان فرزند کامپوننت دکمه معرفی شود:
که سبب بروز خطای زیر میشود:
عنوان میکند که با این تغییر، نوع children به Element تغییر یافتهاست؛ اما در تعریف Props آن، به صورت رشتهای معرفی شدهاست. در این حالت اگر به تعریف Props مراجعه کنیم و نوع string را به Element تغییر دهیم، باز هم این خطا برطرف نمیشود. حتی اگر children: HTMLImageElement را نیز اضافه کنیم، باز هم این خطا تغییری نمیکند.
روش صحیح حل این مشکل را در ادامه مشاهده میکنید:
- ابتدا خاصیت children را از تعریف نوع Props حذف میکنیم.
- سپس ذکر نوع Props را هم از Object Destructuring صورت گرفته، حذف میکنیم.
- در آخر، نوع کامپوننت جاری را به صورت <React.FC<Props معرفی میکنیم. در اینجا FC یعنی Functional Component و با تعریف آن، میتوان نوع props را به صورت آرگومان جنریک آن مشخص کرد. پس از آن دیگر نیازی به ذکر خاصیت children، در Props نیست؛ چون این خاصیت جزئی از React.FC میباشد و به صورت خودکار شناسایی میشود:
بنابراین در حالت کلی اگر از خاصیت children استفاده نمیکنید، از همان syntax قبلی که به صورت export const Button = ({ onClick, onChange }: Props) است، استفاده کنید؛ در غیراینصورت استفادهی از نوع <React.FC<T، ضروری خواهد بود.
export const Button = ({ onClick }: Props) => { return <button onClick={onClick}>Click Me</button>; };
برای رفع این مشکل، میتوان رخداد کلیک ماوس بر روی یک دکمه را از نوع بسیار عمومی React.MouseEvent تعریف کرد:
import React from "react"; type Props = { onClick: (e: React.MouseEvent) => void; }; export const Button = ({ onClick }: Props) => { return <button onClick={onClick}>Click Me</button>; };
پس از آن میتوان تعریف المان کامپوننت Button را در فایل src\App.tsx به صورت زیر تغییر داد؛ چون از دیدگاه تایپاسکریپت، اکنون نوع e به صورت <e: React.MouseEvent<Element, MouseEvent تعریف شدهاست و به تمام خواص و متدهای MouseEvent، با کمک intellisense کامل مرتبط با آنها، دسترسی وجود دارد:
<Button onClick={(e) => { e.preventDefault(); console.log(e); }} />
زمانیکه نوع یک رویداد به صورت کلی e: React.MouseEvent تعریف میشود، اگر به تصویر فوق دقت کنیم، تایپاسکریپت آنرا قابل اعمال به هر نوع Element ای میداند. اما اگر بخواهیم دامنهی دید آنرا صرفا به المان استاندارد button تعریف شده محدود کنیم، اشارهگر ماوس را در فایل src\components\Button.tsx بر روی رویداد onClick المان button تعریف شده قرار میدهیم:
همانطور که مشخص است، نوع این المان را به صورت HTMLButtonElement ذکر کردهاست. بنابراین میتوان تعریف کلی قبلی را به صورت زیر محدود کرد:
import React from "react"; type Props = { onClick: (e: React.MouseEvent<HTMLButtonElement>) => void; }; export const Button = ({ onClick }: Props) => { return <button onClick={onClick}>Click Me</button>; };
تعیین نوع رخداد onChange
در ادامه فرض کنید میخواهیم اطلاعات رویداد onChange را نیز صادر کنیم. روش عمومی تشخیص نوع آن، قرار دادن اشارهگر ماوس بر روی رویداد مدنظر و سپس استفاده از همان نوعی است که نمایش میدهد؛ مانند تصویر زیر:
import React from "react"; type Props = { onClick: (e: React.MouseEvent<HTMLButtonElement>) => void; onChange?: (e: React.FormEvent<HTMLInputElement>) => void; }; export const Button = ({ onClick, onChange }: Props) => { return ( <> <input onChange={onChange} /> <button onClick={onClick}>Click Me</button> </> ); };
سؤال: اطلاعات نمایش داده شدهی نوعهای متناظر با رویدادهای مختلف در تصاویر فوق، از کجا تامین میشوند؟
اگر به فایل package.json پروژهی تایپاسکریپتی ایجاد شده مراجعه کنید، میتوانید حداقل دو مدخل جدید زیر را نیز در آن مشاهده کنید:
{ "dependencies": { "@types/react": "^16.9.35", "@types/react-dom": "^16.9.8", },
سؤال: آیا بستههای @types دار موجود در فایل package.json، حجم فایلهای نهایی برنامه را افزایش میدهند؟ آیا برنامههای React مبتنی بر TypeScript، حجم بیشتری را نسبت به نمونههای ES6 آن دارند؟
اکثر این تعاریف @types به صورت اینترفیسهای تایپاسکریپتی تعریف شدهاند که صرفا در زمان کامپایل برنامه، کمک حال کامپایلر آن خواهند بود و پس از کامپایل، تبخیر شده و در کدهای نهایی تولیدی، حضور نخواهند داشت. به عبارتی استفادهی از تایپاسکریپت، حجم نهایی پروژهها را افزایش نمیدهد؛ چون اینترفیسهای تایپاسکریپت، معادلی را در جاوااسکریپت استاندارد ندارند و فقط به عنوان راهنمای کامپایلر تایپاسکریپت عمل میکنند.
یک نکته: تعریف رویدادهای مدنظر را میتوان در importها نیز قرار داد:
import React, { FormEvent } from "react"; type Props = { onChange?: (e: FormEvent<HTMLInputElement>) => void; };
تعریف نوع Children Props
زمانیکه داخل تعریف المان یک کامپوننت، کامپوننت دیگری ذکر میشود، به عنوان فرزند او در React درنظر گرفته میشود. برای نمونه تعریف کامپوننت Button را در فایل src\App.tsx به صورت زیر تغییر میدهیم تا فرزندی را به صورت «Hello world» بپذیرد:
<Button onClick={(e) => { e.preventDefault(); console.log(e); }} > Hello world! </Button>
با توجه به اینکه این فرزند، از نوع رشتهای است، فقط کافی است خاصیت children را با همین نوع، به Props اضافه کرده و از آن استفاده کنیم:
import React, { FormEvent } from "react"; type Props = { onClick: (e: React.MouseEvent<HTMLButtonElement>) => void; onChange?: (e: FormEvent<HTMLInputElement>) => void; children: string; }; export const Button = ({ onClick, onChange, children }: Props) => { return ( <> <input onChange={onChange} /> <button onClick={onClick}>{children}</button> </> ); };
<Button onClick={(e) => { e.preventDefault(); console.log(e); }} > <img src={logo} className="App-logo" alt="logo" /> </Button>
عنوان میکند که با این تغییر، نوع children به Element تغییر یافتهاست؛ اما در تعریف Props آن، به صورت رشتهای معرفی شدهاست. در این حالت اگر به تعریف Props مراجعه کنیم و نوع string را به Element تغییر دهیم، باز هم این خطا برطرف نمیشود. حتی اگر children: HTMLImageElement را نیز اضافه کنیم، باز هم این خطا تغییری نمیکند.
روش صحیح حل این مشکل را در ادامه مشاهده میکنید:
import React, { FormEvent } from "react"; type Props = { onClick: (e: React.MouseEvent<HTMLButtonElement>) => void; onChange?: (e: FormEvent<HTMLInputElement>) => void; // children: HTMLImageElement; }; export const Button: React.FC<Props> = ({ onClick, onChange, children }) => { return ( <> <input onChange={onChange} /> <button onClick={onClick}>{children}</button> </> ); };
- سپس ذکر نوع Props را هم از Object Destructuring صورت گرفته، حذف میکنیم.
- در آخر، نوع کامپوننت جاری را به صورت <React.FC<Props معرفی میکنیم. در اینجا FC یعنی Functional Component و با تعریف آن، میتوان نوع props را به صورت آرگومان جنریک آن مشخص کرد. پس از آن دیگر نیازی به ذکر خاصیت children، در Props نیست؛ چون این خاصیت جزئی از React.FC میباشد و به صورت خودکار شناسایی میشود:
بنابراین در حالت کلی اگر از خاصیت children استفاده نمیکنید، از همان syntax قبلی که به صورت export const Button = ({ onClick, onChange }: Props) است، استفاده کنید؛ در غیراینصورت استفادهی از نوع <React.FC<T، ضروری خواهد بود.
فرض کنید Stored Procedure ی با چند مقدار برگشتی را میخواهیم در EF CodeFirst مورد استفاده قرار دهیم. برای مثال Stored Procedure زیر را در نظر بگیرید:
CREATE PROCEDURE [dbo].[GetAllBlogsAndPosts] AS SELECT * FROM dbo.Blogs SELECT * FROM dbo.Posts
Stord Procedure ی که توسط این دستور ساخته میشود تمام رکوردهای جدول Blogs و تمامی رکوردهای جدول Posts را واکشی کرده و به عنوان خروجی برمیگرداند (دو خروجی متفاوت). روش فراخوانی و استفاده از دادههای این StoredProcedure در EF CodeFirst به صورت زیر است :
تعریف دو کلاس مدل Blog و Post به ترتیب برای نگهداری اطلاعات وبلاگها و پستها در زیر آمده است. در ادامه نیز تعریف کلاس BloggingContext را مشاهده میکنید.
public class Blog { public int BlogId { get; set; } public string Name { get; set; } public virtual List<Post> Posts { get; set; } } public class Post { public int PostId { get; set; } public string Title { get; set; } public string Content { get; set; } public int BlogId { get; set; } public virtual Blog Blog { get; set; } } public class BloggingContext : DbContext { public DbSet<Blog> Blogs { get; set; } public DbSet<Post> Posts { get; set; } } using System; using System.Collections.Generic; using System.Data.Entity; using System.Data.Entity.Infrastructure; using System.Data.Objects; namespace Sproc.Demo { class Program { static void Main(string[] args) { using (var db = new BloggingContext()) { db.Database.Initialize(force: false); var cmd = db.Database.Connection.CreateCommand(); cmd.CommandText = "[dbo].[GetAllBlogsAndPosts]"; try { // اجرای پروسیجر db.Database.Connection.Open(); var reader = cmd.ExecuteReader(); // خواند رکوردهای blogs var blogs = ((IObjectContextAdapter)db) .ObjectContext .Translate<Blog>(reader, "Blogs", MergeOption.AppendOnly); foreach (var item in blogs) { Console.WriteLine(item.Name); } // پرش به نتایج بعدی (همان Posts) reader.NextResult(); var posts = ((IObjectContextAdapter)db) .ObjectContext .Translate<Post>(reader, "Posts", MergeOption.AppendOnly); foreach (var item in posts) { Console.WriteLine(item.Title); } } finally { db.Database.Connection.Close(); } } } }
در کدهای بالا ابتدا یک Connection به بانک اطلاعاتی باز میشود:
db.Database.Connection.Open();
و پس از آن نوبت به اجرای Stored Procedure میرسد:
var reader = cmd.ExecuteReader();
در کد بالا پس از اجرای Stored Procudure نتایج بدست آمده در یک reader ذخیره میشود. شئ reader از نوع DBDataReader میباشد. پس از اجرای Stored Procedure و دریافت نتایج و ذخیره سازی در شئی reader ، نوبت به جداسازی رکوردها میرسد. همانطور که در تعریف Stored procedure مشخص است این Stored Procedure دارای دو نوع خروجی از نوعهای Blog و Post میباشد و این دو نوع باید از هم جدا شوند.برای انجام این کار از متد Translate شئی Context استفاده میشود. این متد قابلیت کپی کردن نتایج موجود از یک شئی DBDataReader به یک شئی از نوع مدل را دارد. برای مثال :
var blogs = ((IObjectContextAdapter)db) .ObjectContext .Translate<Blog>(reader, "Blogs", MergeOption.AppendOnly);
در کدهای بالا تمامی رکوردهایی از نوع Blog از شئی reader خوانده شده و پس از تبدیل به نوع Blog درون شئی Blogs ذخیره میشود.
پس از آن توسط حلقه foreach محتویات Blogs پیمایش شده و مقدار موجود در فیلد Name نمایش داده میشود.
foreach (var item in blogs) { Console.WriteLine(item.Name); }
با توجه به اینکه حاصل اجرای این Stored Procedure دو خروجی متفاوت بوده است ، پس از پیمایش رکوردهای Blogs باید به سراغ نتایج بعدی که همان رکوردهای Post میباشد برویم. برای اینکار از متد NextResult شئی reader استفاده میشود:
reader.NextResult();
در ادامه برای خواندن رکوردهایی از نوع Post نیز به همان روشی که برای Blog انجام شد عمل میشود.