مطالب
روشی سریع برای ایجاد RSS و Sitemap در ASP.NET MVC
برای مطالعه روش‌های بدست آوردن خروجی xml مربوط به Rss و Sitemap،  میتوانید از مقالات مشخص شده استفاده کنید .[اینجا] و [اینجا].

در صورتیکه طراحی شما بر اساس MVC صورت گرفته است، در کمتر از چند دقیقه و در سه مرحله میتوانید پرونده Rss و Sitemap را برای همیشه ببندید. 

پیش از تشریح مراحل، به ساختار این دو فایل توجه کنید. 

مراحل کار : 

مرحله 1. ایجاد نوع(Type) مورد نیاز برای ایجاد Xml ‌های فوق

مرحله 2 . ایجاد کنترلر XML

مرحله 3. ایجاد مسیریابی(Routing)


مرحله 1 : ابتدا یک کلاس به منظور شکل دهی به اطلاعات، بر اساس خواسته‌های xml مرتبط با RSS و Sitemap تشکیل دهید:

public class PostToXml
    {
        
        public int PostId { get; set; }

        public string title { get; set; }

        public string link { get; set; }
        
        public string description { get; set; }

        public Nullable<DateTime> pubDate { get; set; }

      
    }

مرحله 2 : یک کنترلر به نام xml ایجاد کنید و اکشن متدهای زیر را درون آن قرار دهید :

       public ContentResult RSS()
        {
            
            var items = GetRssFeed();
            var rss = new XDocument(new XDeclaration("1.0", "utf-8", "yes"),
              new XElement("rss",
                new XAttribute("version", "2.0"),
                  new XElement("channel",
                    new XElement("title", "آخرین مطالب سایت"),
                    new XElement("link", "http://" + Request.Url.Host+"/rss"),
                    new XElement("description", "آخرین مطالب سایت من"),
                    new XElement("copyright","(c)" + DateTime.Now.Year + ", نام سایت من.تمامی حقوق محفوظ است"),
                  from item in items
                  select
                  new XElement("item",
                    new XElement("title", item.title),
                    new XElement("description", item.description),
                    new XElement("link", item.link),
                    new XElement("pubDate", item.pubDate)

                  )
                )
              )
            );
            return Content(rss.ToString(), "text/xml");
        }



        public ContentResult Sitemap()
        {
            XNamespace ns = "http://www.sitemaps.org/schemas/sitemap/0.9";
            var items = GetLinks();
            var sitemap = new XDocument(new XDeclaration("1.0", "utf-8", "yes"),
                new XElement(ns + "urlset",
                    from item in items
                    select
                    new XElement("url",
                      new XElement("loc", item.link),
                      new XElement("changefreq", "monthly"),
                      new XElement("priority", "0.5")
                      )
                    )
                  );
            return Content(sitemap.ToString(), "text/xml");
        }


        public IEnumerable<PostToXml> GetRssFeed()
        {
          // یک کوئری که لیستی از تایپ مشخص شده به ما بدهد
        }

         public IEnumerable<PostToXml> GetLinks()
        {
            // یک کوئری که لیستی از تایپ مشخص شده به ما بدهد
        }

این کنترلر دارای دو اکشن متد Rss و Sitemap است و این اکشن‌ها وظیفه‌ی ایجاد فایل‌های Xml را به عهده دارند. مواد اولیه این xml ‌ها از دو متد GetRssFeed و GetLinks تهیه می‌شوند. ما این مواد را در تمپلیت Rss و Sitemap جایگذاری خواهیم کرد. (به کمک دو اکشن متد Rss و Sitemap )

کافیست لیستی از مواردی را که می‌خواهیم در Rss یا Sitemap ثبت شوند، تهیه کنیم. این لیست بر اساس شکل تنظیم دیتابیس و مسیریابی سایت، می‌تواند پیچیده و یا ساده باشد. (به کمک کوئری گرفتن با linq و یا اضافه کردن مستقیم آدرس‌ها به لیست و یا ترکیبی از هر دو مورد) برای درک بهتر موضوع، لطفا تصویر موجود در ابتدای مقاله را مشاهده نمایید.

مرحله 3 : در مرحله آخر کافیست دو مورد زیر را به فایل RoutConfig.cs بیافزایید: 

routes.MapRoute(
              "Sitemap", "sitemap",
              new { controller = "XML", action = "Sitemap" });
 routes.MapRoute(
              "RSS", "rss",
               new { controller = "XML", action = "RSS" });

به کمک آدرس‌های زیر می‌توانید به آنچه که تهیه کرده‌اید دسترسی داشته باشید :

http://domain.com/rss
http://domain.com/sitemap  

فایل پروژه را دریافت کنید :

MVC_RSS_Sitemap-43ad3c6681734b34b91deaaabcdba871.rar 

مطالب
درج یک باره چندین رکورد بصورت همزمان هنگام استفاده از ORMها
همونطور که میدونیم درج یکباره چندین رکورد هنگام استفاده از Entity Framework فعلا امکان پذیر نیست و باید از یک حلقه استفاده کرد و آنها رو یک به یک وارد کرد که هنگامی تعداد رکوردها زیاد باشن زمان اجرا یکم زیاد میشه. برای رفع این مشکل در EF Code First میتونین خاصیت AutoDetectChangesEnabled رو برای Context غیرفعال کنید که استفاده از این روش قبلا در این مقاله توضیح داده شده است. راه دیگه استفاده از SqlBulkCopy هست که میتوانید هنگام استفاده از ORMها ازش استفاده کنید. اگه قبلا از ADO.NET استفاده کرده باشید و خواسته باشید تعداد زیادی رکورد رو بصورت همزمان وارد دیتابیس کنید حتما با SqlBulkCopy آشنایی دارید.

فرض کنید دارید در پروژه، از Entity Framework استفاده میکنید و یک مدل با نام Person دارید که تعریفش به صورت زیر است 

public class Person
{
     public int PersonId { get; set; }

     public string Name { get; set; }
}

حالا میخوایم تعداد ٥٠٠٠ رکورد از Person رو یکجا وارد دیتابیس کنیم. برای استفاده از SqlBulkCopy، روش به این شکل هست که ابتدا یکDataTable  ایجاد میکنیم. سپس ستونهای متناظر با جدول Person رو با استفاده از DataColumn ایجاد میکنیم و DataColumnهای ایجاد شده رو به DataTable اضافه میکنیم و سپس اطلاعات رو وارد DataTable میکنیم و اون رو با استفاده از SqlBulkCopy وارد دیتابیس میکنیم که این روش یکم وقتگیر و خسته کننده است. 

راه آسانتر استفاده از یک کتابخانه با نام EntityDataReader هست که توسط مایکروسافت نوشته شده که دیگه نیازی به ساختنDataTable  نیست و این کتابخانه کارهای لازم رو خودش انجام میده. در پروژەتون یک کلاس با نامEntityDataReader ایجاد کنید و سورس مربوط این کلاس رو از اینجا copy و در داخل کلاس paste کنید. 

حالا یک لیست از Pesron با نام personList ایجاد مینماییم و با استفاده از یک حلقه تعداد ٥٠٠٠ تا نمونه از Person ایجاد و به لیست اضافه میکنیم.

var personList = new List<Person>();
for (var i = 0; i < 5000; i++)
{
    var person = new Person
        {
            Name = "John Doe",
        };
}

در ادامه برای استفاده از SqlBulkCopy نیاز به ConnectionString و نام جدول متناظر با کلاس Person در دیتابیس داریم.

اگر از پروژ وب استفاده میکنید میتونید با این خط کد ConnectionString رو که در فایل web.config ذخیره شده است بروگردونید که در اینجا DataConnection نام ConnectionString ذخیره شده در web.config هست.

var connectionString = ConfigurationManager.ConnectionStrings["DataConnection"].ConnectionString;

اگر از EF Code First استفاده میکنید و در تنظیمات Context خاصیت PluralizingTableNameConvention رو حذف کردیدەاید نام جدول dbo.Person هست و در غیر اینصورت db.People هست.

و در ادامه داریم:   

var connectionString = ConfigurationManager.ConnectionStrings["DataConnection"].ConnectionString;
var bulkCopy = new SqlBulkCopy(connectionString) { DestinationTableName = "dbo.Person" };
bulkCopy.WriteToServer(personList.AsDataReader()  );

سرعت این روش بسیار بالاست و برای درجهای با تعداد بالا بهینه است.

برای ویرایش و حذف چندین رکورد بصورت همزمان متیونید از کتابخانه Entity Framework Extended Library استفاده کنید که امکانات دیگری هم داره و از طریق nuget هم قابل نصب است.

اشتراک‌ها
و حالا AngularJS برای برنامه های هایبرید!
برای کسانی که برنامه‌های هایبرید(کد html  و بعد کامپایل برای موبایل) می‌نویسن فکر می‌کنم باید این افزونه مفید باشه:
چه می‌کنه این AngularJS!  ;)
و حالا AngularJS برای برنامه های هایبرید!
اشتراک‌ها
تهیه بکاپ روزانه از پایگاه داده توسط ابزار SQL Server Management Studio Job

اگر مالک وب سایت شخصی هستید که در VPS و یا سرور اختصاصی آن را هاست نموده اید، حتما به بکاپ روزانه و حتی ساعتی از پایگاه داده نیاز خواهید داشت. ابزار SQL Server Job، این کار را برای شما به سادگی و ظرف چند دقیقه انجام خواهد داد.

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

تهیه بکاپ روزانه از پایگاه داده توسط ابزار SQL Server Management Studio Job
مطالب
لینک‌های هفته دوم دی

وبلاگ‌ها ، سایت‌ها و مقالات ایرانی (داخل و خارج از ایران)


ASP. Net


طراحی و توسعه وب


PHP


اس‌کیوال سرور


سی شارپ


SharePoint

عمومی دات نت


ویندوز


مسایل اجتماعی و انسانی برنامه نویسی


متفرقه


مطالب
React 16x - قسمت 12 - طراحی یک گرید - بخش 2 - فیلتر کردن اطلاعات
تا اینجا کامپوننت صفحه بندی را به همراه اعمال آن به لیست نمایش داده شده، پیاده سازی کردیم. در ادامه می‌خواهیم لیست ژانرهای سینمایی را که در فایل fakeGenreService.js تعریف شده‌اند:
export const genres = [
  { _id: "5b21ca3eeb7f6fbccd471818", name: "Action" },
  { _id: "5b21ca3eeb7f6fbccd471814", name: "Comedy" },
  { _id: "5b21ca3eeb7f6fbccd471820", name: "Thriller" }
];

export function getGenres() {
  return genres.filter(g => g);
}
توسط list-group‌های بوت استرپی، در کنار صفحه نمایش داده و سپس به ازای هر گروه انتخابی توسط کاربر، فیلم‌های مرتبط با آن گروه را فیلتر کرده و نمایش دهیم.


بررسی ساختار کامپوننت ListGroup

شبیه به کامپوننت صفحه بندی که در قسمت قبل ایجاد کردیم، می‌خواهیم کامپوننت ListGroup نیز به طور کامل از اشیاء movie مستقل باشد؛ تا در آینده بتوان از آن در جاهای دیگری نیز استفاده کرد. به همین جهت فایل جدید src\components\common\listGroup.jsx را ایجاد کرده و سپس با استفاده از میانبرهای imrc و cc در VSCode، ساختار ابتدایی این کامپوننت را ایجاد می‌کنیم. هرچند می‌توان این کامپوننت را به صورت «Stateless Functional Component» نیز طراحی کرد؛ چون state و متد دیگری بجز render نخواهد داشت و تمام اطلاعات خودش را از والد خود دریافت می‌کند.
سپس به کامپوننت movies مراجعه کرده و این کامپوننت خالی را import می‌کنیم:
import ListGroup from "./common/listGroup";
پس از آن به متد رندر کامپوننت movies مراجعه کرده و با اضافه کردن یک row بوت استرپی دو ستونی، قصد داریم کامپوننت لیست فیلم‌ها را در ستون اول این ردیف نمایش دهیم. به همین جهت المان آن‌را در این محل قرار می‌دهیم تا بتوانیم اینترفیس ابتدایی آن‌را پیش از پیاده سازی آن، طراحی کنیم.
برای این منظور ابتدا React.Fragment موجود را با یک div با "className="row جایگزین می‌کنیم. سپس داخل این row، دو ستون را تعریف خواهیم کرد که در اولی، المان جدید ListGroup قرار می‌گیرد و در دومی، مابقی عناصری که تاکنون اضافه کرده‌ایم؛ مانند جدول، صفحه بندی و نمایش تعداد آیتم‌ها:
    return (
      <div className="row">
        <div className="col-2">
          <ListGroup />
        </div>
        <div className="col">
          ...
        </div>
      </div>
    );
این listGroup، حداقل نیاز به لیست آیتم‌هایی را دارد که باید نمایش دهد. این لیست نیز از fakeGenreService و متد getGenres آن تامین می‌شود که به صورت یک خاصیت جدید در state به نحو زیر درج خواهد شد:
import { getGenres } from "../services/fakeGenreService";
// ...

class Movies extends Component {
  state = {
    // ...
    genres: getGenres()
  };
همانطور که در قسمت 9 این سری نیز بررسی کردیم، اگر getGenres قرار است از سمت سرور و توسط یک درخواست Ajax ای تامین شود، محل صحیح قرارگیری آن در متد lifecycle hook ویژه‌ای به نام componentDidMount است. اما در اینجا چون genres یک لیست درون حافظه‌ای است، مقدار دهی فوق، مشکلی را ایجاد نمی‌کند. هرچند می‌توان هم اکنون نیز تعریف فوق را کمی اصولی‌تر نوشت. برای اینکار متد componentDidMount را اضافه کرده و به نحو زیر تنظیم می‌کنیم:
class Movies extends Component {
  state = {
    movies: [],
    pageSize: 4,
    currentPage: 1,
    genres: []
  };

  componentDidMount() {
    this.setState({ movies: getMovies(), genres: getGenres() });
  }
ابتدا آرایه‌های مورد نیاز movies و genres را در state تعریف کرده و آن‌ها را با یک آرایه‌ی خالی، مقدار دهی اولیه می‌کنیم. از این جهت که تا رسیدن به مرحله‌ی componentDidMount که اندکی طول می‌کشد، خطاهای زمان اجرای عدم دسترسی به این آرایه‌ها در برنامه رخ ندهد. سپس زمانیکه وهله‌ای از این کامپوننت در DOM رندر شد، متد componentDidMount فراخوانی شده و دو خاصیت state را با مقادیر دریافتی، به روز رسانی می‌کند.

پس از آن می‌توان ویژگی جدید items این کامپوننت را به آرایه‌ی genres دریافتی از state، تنظیم کرد:
<ListGroup items={this.state.genres} />
در این مرحله، ورودی دیگری به نظر نمی‌رسد که مورد نیاز باشد. اکنون این سؤال مطرح می‌شود که چه رخ‌دادهایی را قرار است از این کامپوننت دریافت کنیم یا به عبارتی خروجی آن چیست؟
بهتر است هر زمانیکه کاربر، آیتمی را از این لیست انتخاب کرد، توسط بروز رخدادی مانند onItemSelect از وقوع آن مطلع شد و سپس نسبت به آن توسط متد handleGenreSelect، واکنش نشان داد؛ مانند فیلتر کردن لیست فیلم‌ها بر اساس آیتم انتخابی و نمایش آن. به همین جهت ویژگی onItemSelect را به تعریف المان ListGroup اضافه می‌کنیم:
<ListGroup
  items={this.state.genres}
  onItemSelect={this.handleGenreSelect}
/>
و سپس متد handleGenreSelect متصل به آن‌‌را به نحو زیر تعریف خواهیم کرد:
  handleGenreSelect = genre => {
    console.log("handleGenreSelect", genre);
  };
تا اینجا اینترفیس کامپوننت ListGroup را پیش از پیاده سازی آن تعریف کردیم (تعیین ورودی و خروجی آن). در مرحله‌ی بعد، این کامپوننت را تکمیل می‌کنیم.


پیاده سازی نمایش آیتم‌ها در کامپوننت ListGroup

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

class ListGroup extends Component {
  render() {
    return (
      <ul className="list-group">
        {this.props.items.map(item => (
          <li key={item._id} className="list-group-item">
            {item.name}
          </li>
        ))}
      </ul>
    );
  }
}

export default ListGroup;
کار با درج یک ul که با کلاس list-group مزین شده‌است، شروع می‌شود. سپس باید liهای آن‌را که نمایانگر آیتم‌های این لیست است، به صورت پویا با کلاس‌های list-group-item رندر کرد. برای اینکار از آرایه‌ی دریافتی this.props.items و فراخوانی متد map بر روی آن کمک می‌گیریم. در اینجا key هر ردیف با استفاده از خاصیت id هر آیتم و برچسب هر کدام از طریق خاصیت name هر شیء دریافتی، تامین می‌شود.

تا اینجا اگر برنامه را ذخیره کرده و در مرورگر نمایش دهیم، به خروجی زیر می‌رسیم:


البته به نظر عرض ستون آن نامناسب است. به همین جهت به کامپوننت movies مراجعه کرده و col-2 ستون آن‌را به col-3 تبدیل می‌کنیم.


پویا سازی انتخاب نام خواص شیء دریافتی، در کامپوننت ListGroup

در حال حاضر پیاده سازی کامپوننت ListGroup، به شیءای دقیقا با خواص id_ و name وابسته‌است و اگر شیء دیگری را که دارای خواصی معادل این نام‌ها نیست، به آن ارسال کنیم، دیگر کار نخواهد کرد. به همین جهت در محل تعریف المان این کامپوننت در کامپوننت movies، دو ویژگی دیگر نام خواص شیء مدنظر را تنظیم می‌کنیم تا بتوانیم با هر نوع شیءای در اینجا کار کنیم:
<ListGroup
  items={this.state.genres}
  textProperty="name"
  valueProperty="_id"
  onItemSelect={this.handleGenreSelect}
/>
پس از این تغییر و افزودن textProperty و valueProperty، برای پویا سازی نام‌های خواص دریافتی در کامپوننت ListGroup، از روش کار با []، جهت دسترسی پویای به خواص یک شیء، استفاده می‌کنیم تا دیگر این کامپوننت به شیء خاص genre، وابستگی نداشته باشد و قابلیت استفاده‌ی مجدد از آن افزایش یابد:
import React, { Component } from "react";

class ListGroup extends Component {
  render() {
    return (
      <ul className="list-group">
        {this.props.items.map(item => (
          <li key={item[this.props.valueProperty]} className="list-group-item">
            {item[this.props.textProperty]}
          </li>
        ))}
      </ul>
    );
  }
}

export default ListGroup;


تعیین مقادیر پیش‌فرضی برای خواص props

با زیاد شدن تعداد خواص props، اینترفیس کامپوننت‌ها پیچیده‌تر می‌شوند. در یک چنین حالتی می‌توان در کامپوننت‌ها defaultProps را تعریف کرد و توسط آن مقادیر پیش‌فرضی را برای خواص props درنظر گرفت. به این صورت در حین تعریف المان این کامپوننت، اگر مقادیر مدنظر با مقادیر پیش‌فرض تعیین شده یکی باشند، دیگر نیازی به ذکر این پارامترها نخواهد بود. برای مثال در انتهای کامپوننت ListGroup، خاصیت جدید defaultProps را تعریف می‌کنیم (املای آن باید دقیقا به همین شکل باشد؛ و گرنه شناخته نخواهد شد). سپس در اینجا خواصی را که می‌خواهیم مقادیر پیش‌فرضی را برای آن‌ها تعیین کنیم، ذکر خواهیم کرد:
ListGroup.defaultProps = {
  textProperty: "name",
  valueProperty: "_id"
};

export default ListGroup;
برای نمونه در اینجا دو خاصیت جدید textProperty و valueProperty را به همان مقادیر name و id_ مورد استفاده‌ی در این مثال تنظیم کرده‌ایم. پس از این تعریف، می‌توان به کامپوننت movies که از این ویژگی‌ها استفاده می‌کند مراجعه کرده و آن‌هایی را که با defaultProps تطابق دارند، از لیست ویژگی‌های ذکر شده حذف کرد؛ یعنی تعریف المان ListGroup به صورت زیر ساده می‌شود:
<ListGroup
  items={this.state.genres}
  onItemSelect={this.handleGenreSelect}
/>
بدیهی است اگر در آینده با اشیاء دیگری سر و کار داشتیم، می‌توان مجددا این خواص پیش‌فرض را بر اساس ساختار این اشیاء، مقدار دهی و تعیین کرد.


مدیریت انتخاب گروه‌های فیلم‌ها

در ادامه می‌خواهیم رخ‌داد onClick بر روی هر li این لیست را مدیریت کنیم و سبب بروز رخ‌دادی به نام onItemSelect شویم که در ابتدای بحث، آن‌را به عنوان خروجی این کامپوننت تعریف کردیم. این رخداد نیز در کامپوننت movies به متد handleGenreSelect متصل است. به همین جهت تعریف ویژگی onClick را که سبب انتقال شیء جاری رندر شده، توسط رویداد onItemSelect به خارج از آن می‌شود، به المان li کامپوننت ListGroup اضافه می‌کنیم:
<li
  key={item[this.props.valueProperty]}
  className="list-group-item"
  onClick={() => this.props.onItemSelect(item)}
  style={{ cursor: "pointer" }}
>
  {item[this.props.textProperty]}
</li>
پس از این تغییرات و ذخیره‌ی برنامه، اگر به خروجی برنامه در مرورگر مراجعه کرده و بر روی هر کدام از آیتم‌های لیست گروه‌های فیلم‌ها کلیک کنیم، شیء مرتبط با آن آیتم در کنسول توسعه دهنده‌های مرورگر، لاگ می‌شود که نشان از برقراری صحیح ارتباطات این قسمت را دارد.

پس از فعالسازی امکان کلیک بر روی هر آیتم لیست رندر شده، اکنون می‌خواهیم با انتخاب هر گروه، این گروه در این لیست، به صورت انتخاب شده، همانند شماره صفحه‌ی انتخاب شده‌ی در کامپوننت صفحه بندی، تغییر رنگ دهد و متمایز نمایش داده شود تا مشخص باشد که هم اکنون با کدام آیتم در حال کار هستیم. برای اینکار تنها کافی است کلاس active را به صورت پویا به className هر li، اضافه یا کم کنیم. البته برای این منظور این کامپوننت باید از آیتم انتخاب شده مطلع باشد؛ به همین جهت selectedItem را در لیست ویژگی‌های اینترفیس تعریف این المان اضافه می‌کنیم. برای اینکار ابتدا selectedGenre را با هربار فراخوانی handleGenreSelect که به onItemSelect کامپوننت متصل است، با فراخوانی متد setState به روز رسانی می‌کنیم:
  handleGenreSelect = genre => {
    console.log("handleGenreSelect", genre);
    this.setState({selectedGenre: genre});
  };
در یک چنین حالتی الزامی به تعریف selectedGenre در خاصیت state ابتدای کامپوننت نیست. چون با فراخوانی متد setState اگر یکی از خواص منتسب به شیء state به روز شده باشد، آن خاصیت نیز به روز می‌شود و یا اگر این خاصیت جدید باشد، با state موجود یکی خواهد شد؛ هرچند آن‌را به صورت زیر نیز می‌توان تعریف کرد که با یک شیء خالی مقدار دهی شده‌است:
class Movies extends Component {
  state = {
    // ...
    selectedGenre: {}
  };
سپس ویژگی selectedItem کامپوننت را به این مقدار تغییر یافته‌ی this.state.selectedGenre تنظیم می‌کنیم تا با هر بار فراخوانی setState که سبب رندر مجدد کامپوننت Movies در DOM مجازی React می‌شود، کامپوننت از selectedItem تغییر یافته مطلع شده و با افزودن کلاس active به آن آیتم، واکنش نشان دهد:
<ListGroup
  items={this.state.genres}
  onItemSelect={this.handleGenreSelect}
  selectedItem={this.state.selectedGenre}
/>
اکنون به کامپوننت ListGroup مراجعه کرده و بر اساس ویژگی جدید selectedItem، تغییرات زیر را به className اعمال می‌کنیم:
<li
  key={item[this.props.valueProperty]}
  className={
    item === this.props.selectedItem
      ? "list-group-item active"
      : "list-group-item"
  }
  style={{ cursor: "pointer" }}
  onClick={() => this.props.onItemSelect(item)}
>
  {item[this.props.textProperty]}
</li>
در اینجا اگر item در حال رندر با this.props.selectedItem دریافتی یکی باشد، کلاس active به کلاس list-group-item اضافه می‌شود و برعکس.



مدیریت فیلتر کردن اطلاعات گروه فیلم انتخابی

در قسمت قبل، در ابتدای متد رندر کامپوننت movies، از متد paginate برای صفحه بندی اطلاعات استفاده کردیم. فیلتر گروه جاری انتخاب شده را باید پیش از این متد قرار دارد؛ چون تعداد صفحات و اطلاعات نمایش داده شده‌ی در هر کدام باید بر اساس لیست فیلم‌های فیلتر شده باشد.
برای انجام اینکار تغییرات زیر را اعمال خواهیم کرد:
الف) بجای متد paginate، از متد getPagedData زیر استفاده می‌کنیم:
  getPagedData() {
    const {
      pageSize,
      currentPage,
      selectedGenre,
      movies: allMovies
    } = this.state;

    const filteredMovies =
      selectedGenre && selectedGenre._id
        ? allMovies.filter(m => m.genre._id === selectedGenre._id)
        : allMovies;

    const first = (currentPage - 1) * pageSize;
    const last = first + pageSize;
    const pagedMovies = filteredMovies.slice(first, last);

    return { totalCount: filteredMovies.length, data: pagedMovies };
  }
- در اینجا بجای اینکه مدام this.stat‌ها را جهت دریافت خواص آن تکرار کنیم، با استفاده از ویژگی Object Destructuring، خواصی را که نیاز داریم یکبار انتخاب کرده و سپس به دفعات از آن‌ها استفاده می‌کنیم. به همین جهت در این قطعه کد، فقط یکبار this.state را مشاهده می‌کنید که بسیار تمیزتر است و همچنین کارآیی آن نیز به علت عدم انتخاب مداوم مقدار خاصیتی از یک شیء، بالاتر از حالت قبل است.
- در حین Object Destructuring، نام خاصیت movies را نیز به allMovies تغییر داده‌ایم تا واضح‌تر باشد.
- در ادامه با استفاده از متد filter جاوااسکریپت، بر اساس id هر گروه انتخاب شده، اشیاء مرتبط با آن، از allMovies جدا شده و بازگشت داده می‌شود. البته اگر id هم انتخاب نشده باشد (اولین بار نمایش صفحه)، تمام رکوردها یعنی allMovies، مورد استفاده قرار می‌گیرد.
- پس از آن، همان کدهای صفحه بندی اطلاعات را که در قسمت قبل بررسی کردیم، مشاهده می‌کنید که اینبار بجای allMovies قسمت قبل، بر روی filteredMovies اعمال شده‌است.
- در آخر، این متد، یک شیء را با دو خاصیت که بیانگر تعداد کل رکوردهای انتخاب شده و داده‌های فیلتر شده‌ی صفحه بندی شده‌است، بازگشت می‌دهد.

ب) تغییرات متد رندر کامپوننت movies به صورت زیر است:
- ابتدا متد getPagedData فوق، فراخوانی شده و شیء دریافتی از آن با استفاده از ویژگی Object Destructuring، به دو خاصیت totalCount و movies انتساب داده می‌شود:
  render() {
    const { length: count } = this.state.movies;

    if (count === 0) return <p>There are no movies in the database.</p>;

    const { totalCount, data: movies } = this.getPagedData();
- از آرایه‌ی movies، در قسمت قبل برای رندر لیست فیلم‌ها استفاده شد. به همین جهت در اینجا تغییر نام data به movies را مشاهده می‌کنید.
- همچنین کامپوننت صفحه بندی، اینبار باید totalCount آیتم‌های فیلتر شده را نمایش دهد و نه totalCount تمام فیلم‌های موجود را:
<Pagination
    itemsCount={totalCount}
در اینجا برچسب نمایش تعداد آیتم‌های موجود نیز باید تغییر کند:
<p>Showing {totalCount} movies in the database.</p>
ج) ممکن است در اولین بار مشاهده‌ی صفحه، کاربر صفحه‌ی شماره‌ی 3 را انتخاب کند که سبب تغییر currentPage موجود در state، به عدد 3 می‌شود. اکنون اگر کاربر نمایش فیلتر شده‌ی فیلم‌های یک گروه خاص را انتخاب کند، باید این شماره، به عدد 1 مجددا تنظیم شود:
  handleGenreSelect = genre => {
    console.log("handleGenreSelect", genre);
    this.setState({ selectedGenre: genre, currentPage: 1 });
  };



افزودن گزینه‌ی نمایش تمام اطلاعات به لیست گروه‌های فیلم‌ها

در ادامه قصد داریم به بالای لیست گروه‌های موجود، گزینه‌ی All Genres را نیز اضافه کنیم تا با کلیک بر روی آن، مجددا بتوان لیست تمام فیلم‌های موجود را مشاهده کرد.


برای این منظور در جائیکه لیست getGenres را دریافت و نمایش می‌دهیم، یعنی متد componentDidMount، اندکی تغییر ایجاد کرده و یک آرایه‌ی جدید را ایجاد می‌کنیم؛ بطوریکه اولین عنصر آن، گزینه‌ی جدید All Genres باشد و سپس توسط spread operator، مابقی عناصر آرایه‌ی گروه‌ها را به این آرایه‌ی جدید اضافه می‌کنیم:
  componentDidMount() {
    const genres = [{ _id: "", name: "All Genres" }, ...getGenres()];
    this.setState({ movies: getMovies(), genres });
  }
همین اندازه تغییر برای فعالسازی این گزینه کفایت می‌کند؛ از این جهت که در متد getPagedData، ابتدا بررسی می‌شود که اگر آیتمی انتخاب شده بود و همچنین دارای id نیز بود، آنگاه کار فیلتر کردن صورت گیرد، درغیراینصورت، تمام رکوردها را بازگشت دهد:
const filteredMovies =
      selectedGenre && selectedGenre._id
        ? allMovies.filter(m => m.genre._id === selectedGenre._id)
        : allMovies;

کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید:  sample-12.zip
نظرات مطالب
نحوه ایجاد یک تصویر امنیتی (Captcha) با حروف فارسی در ASP.Net MVC
سلام، از نظر شما متشکرم

همون طور که آقای محسن خان گفت، احتمالا هاست شما medium trust هست. اما رو کامپیوتر خودتون full trust برنامه نویسی می‌کنید.

چون هویت کاربر هنوز مشخص نشده پیغامی مبنی بر این لاگ میشه که اسمبلی mscorlib وجود نداره. در واقع وجود داره ولی نه برای کاربر anonymous ! همچنین این به دلیل medium trust بودن هم میتونه باشه. برای حل این مشکل کارهای زیر رو انجام بدین:

1) به فایل web.config برین و کد زیر رو اضافه کنید:
<trust level="Full" originUrl=".*" />

2) باید تغییری رو در متدهای الحاقی Encrypt و Decrypt بدین، که از این متدها برای رمزنگاری و رمزگشایی محتوای کوکی تصویر امنیتی استفاده میشه. قبل از هر کدوم از این متدها flag زیر رو اضافه کنید:

[System.Security.Permissions.PermissionSet(System.Security.Permissions.SecurityAction.Assert, Unrestricted = true)]

همچنین، یک خط داخل بدنه هر کدوم از متدهای الحاقی Encrypt و Decrypt هست، منظورم این خط کد هست:
var cspp = new CspParameters { KeyContainerName = key };
که باید به خط کد زیر تبدیل بشه:
var cspp = new CspParameters { KeyContainerName = key, Flags = CspProviderFlags.UseMachineKeyStore };

مطالب
React 16x - قسمت 18 - کار با فرم‌ها - بخش 1 - دریافت ورودی‌ها از کاربر
تقریبا تمام برنامه‌ها نیاز دارند فرم‌های مخصوصی را داشته باشند. به همین جهت در این قسمت، برنامه‌ی نمایش لیست فیلم‌ها را که تا این مرحله تکمیل کردیم، با افزودن تعدادی فرم بهبود می‌بخشیم؛ مانند فرم لاگین، فرم ثبت نام، فرمی برای ثبت و ویرایش فیلم‌ها و یک فرم جستجوی سریع در لیست فیلم‌های موجود.


ایجاد فرم لاگین

فرم لاگینی را که به برنامه‌ی نمایش لیست فیلم‌های تکمیل شده‌ی تا قسمت 17، اضافه خواهیم کرد، یک فرم بوت استرپی است و می‌توانید جزئیات بیشتر مزین سازی المان‌های این نوع فرم‌ها را با کلاس‌های بوت استرپ، در مطلب «کار با شیوه‌نامه‌های فرم‌ها در بوت استرپ 4» مطالعه کنید.
در ابتدا فایل جدید src\components\loginForm.jsx را ایجاد کرده و سپس توسط میان‌برهای imrc و cc در VSCode، ساختار ابتدایی کامپوننت جدید LoginForm را ایجاد می‌کنیم:
import React, { Component } from "react";


class LoginForm extends Component {
  render() {
    return <h1>Login</h1>;
  }
}

export default LoginForm;
در ادامه یک Route جدید را در فایل app.js برای این فرم، با مسیر login/ و کامپوننت LoginForm، در ابتدای Switch موجود، تعریف می‌کنیم:
import LoginForm from "./components/loginForm";
//...

function App() {
  return (
    <React.Fragment>
      <NavBar />
      <main className="container">
        <Switch>
          <Route path="/login" component={LoginForm} />
          <Route path="/movies/:id" component={MovieForm} />
          // ...
        </Switch>
      </main>
    </React.Fragment>
  );
}
پس از تعریف این مسیریابی، نیاز است لینک آن‌را نیز به منوی راهبری سایت اضافه کنیم. به همین جهت در فایل navBar.jsx که آن‌را در قسمت قبل تکمیل کردیم، در انتهای لیست موجود و پس از Rentals، لینک لاگین را نیز قرار می‌دهیم:
<NavLink className="nav-item nav-link" to="/login">
   Login
</NavLink>
که در نهایت حاصل این تغییرات، به صورت زیر در مرورگر ظاهر می‌شود:


اکنون نوبت به افزودن فرم بوت استرپی لاگین به فایل loginForm.jsx رسیده‌است:
import React, { Component } from "react";


class LoginForm extends Component {
  render() {
    return (
      <form>
        <div className="form-group">
          <label htmlFor="username">Username</label>
          <input id="username" type="text" className="form-control" />
        </div>
        <div className="form-group">
          <label htmlFor="password">Password</label>
          <input id="password" type="password" className="form-control" />
        </div>
        <button className="btn btn-primary">Login</button>
      </form>
    );
  }
}

export default LoginForm;
توضیحات:
- ابتدا المان form به صفحه اضافه می‌شود.
- سپس هر ورودی، داخل یک div با کلاس form-group، محصور می‌شود. کار آن تبدیل یک برچسب و فیلد ورودی، به یک گروه از ورودی‌های بوت استرپ است.
- در اینجا هر برچسب دارای یک ویژگی for است. اما چون قرار است عبارات jsx، به معادل‌های جاوا اسکریپتی ترجمه شوند، نمی‌توان از واژه‌ی کلیدی for در اینجا استفاده کرد. به همین جهت از معادل react ای آن که htmlFor است، در کدهای فوق استفاده کرده‌ایم؛ شبیه به نکته‌ای که در مورد تبدیل ویژگی class به className وجود دارد. مقدار هر ویژگی htmlFor نیز به id فیلد ورودی متناظر با آن تنظیم می‌شود. به این ترتیب اگر کاربر بر روی این برچسب کلیک کرده و آن‌را انتخاب کند، فیلد متناظر با آن، دارای focus می‌شود.
- فیلدهای ورودی نیز دارای کلاس form-control هستند.

با این خروجی نهایی در مرورگر:



مدیریت ارسال فرم‌ها

به صورت پیش فرض و استاندارد، دکمه‌ی افزوده شده‌ی به المان form، سبب ارسال اطلاعات آن به سرور و سپس بارگذاری کامل صفحه می‌شود. این رفتاری نیست که در یک برنامه‌ی SPA مدنظر باشد. برای مدیریت این حالت، می‌توان از رخ‌داد onSubmit هر المان فرم، استفاده کرد:
class LoginForm extends Component {
  handleSubmit = e => {
    console.log("handleSubmit", e);
    e.preventDefault();

    // call the server
  };

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
      //...
در اینجا یک متد رویدادگردان را برای رخ‌داد onSubmit تعریف کرده‌ایم که توسط آن رخ‌داد جاری، دریافت و متد preventDefault آن فراخوانی می‌شود تا دیگر پس از کلیک بر روی دکمه‌ی submit، حالت پیش‌فرض و استاندارد full page reload و post back به سمت سرور، رخ ندهد.


دسترسی مستقیم به المان‌های فرم‌ها

پس از فراخوانی متد preventDefault، کار مدیریت ارسال فرم به سرور را باید خودمان مدیریت کنیم و دیگر رخ‌داد full post back استاندارد به سمت سرور را نخواهیم داشت. در جاوا اسکریپت خالص برای دریافت مقادیر وارد شده‌ی توسط کاربر می‌توان نوشت:
const username = document.getElementById("username").value;
اما در React و کدهای یک کامپوننت، نباید ارجاع مستقیمی را به شیء document و DOM اصلی مرورگر داشته باشیم. در برنامه‌های React هیچگاه نباید با شیء document کار کرد؛ چون کل فلسفه‌ی آن ایجاد یک abstraction بر فراز DOM اصلی مرورگر است که به آن DOM مجازی گفته می‌شود. به این ترتیب مدیریت برنامه و همچنین آزمون نویسی برای آن نیز ساده‌تر می‌شود. اما اگر واقعا نیاز به دسترسی به یک المان DOM در React وجود داشت، چه باید کرد؟
برای دسترسی به یک المان DOM در React، باید یک reference را به آن نسبت داد. برای این منظور یک خاصیت جدید را در سطح کلاس کامپوننت، ایجاد کرده و آن‌را با React.RefObject، مقدار دهی اولیه می‌کنیم:
class LoginForm extends Component {
  username = React.createRef();
سپس ویژگی ref المان مدنظر را به این RefObject تنظیم می‌کنیم:
<input
  ref={this.username}
  id="username"
  type="text"
  className="form-control"
/>
اکنون زمان submit فرم، اگر نیاز به مقدار username وجود داشت، می‌توان توسط خاصیت ارجاعی username تعریف شده، به خاصیت current آن که DOM element مدنظر را بازگشت می‌دهد، دسترسی یافت و مانند مثال زیر، مقدار آن‌را مورد استفاده قرار داد:
  handleSubmit = e => {
    e.preventDefault();

    // call the server
    const username = this.username.current.value;
    console.log("handleSubmit", username);
  };

البته در حالت کلی باید استفاده‌ی از RefObjectها را به حداقل رساند (راه حل بهتری برای دریافت ورودی‌ها وجود دارد) و جاهائی از آن‌ها استفاده کرد که واقعا راه حل دیگری وجود ندارد؛ مانند تنظیم focus بر روی یک المان DOM. در این حالت حتما باید ارجاعی را از آن المان DOM در دسترس داشت و یا برای پویانمایی (animation) نیز مجبور به استفاده‌ی از RefObjectها هستیم.
برای نمونه روش تنظیم focus بر روی یک فیلد ورودی توسط RefObjectها به صورت زیر است:
class LoginForm extends Component {
  username = React.createRef();

  componentDidMount = () => {
    this.username.current.focus();
  };
در life-cycle hook ای به نام componentDidMount که پس از رندر کامپوننت در DOM فراخوانی می‌شود، می‌‌توان توسط RefObject تعریف شده، به شیء current که معادل DOM Element متناظر است، دسترسی یافت و سپس متد focus آن‌را فراخوانی کرد. در این حالت در اولین بار نمایش فرم، یک چنین تصویری حاصل می‌شود:


البته روش بهتری نیز برای انجام اینکار وجود دارد. المان‌های JSX دارای ویژگی autoFocus نیز هستند که دقیقا همین کار را انجام می‌دهد:
<input
  autoFocus
  ref={this.username}
  id="username"
  type="text"
  className="form-control"
/>
برای آزمایش آن، قطعه کد componentDidMount را کامنت کرده و برنامه را اجرا کنید.


تبدیل المان‌های فرم‌ها به Controlled elements

در بسیاری از اوقات، فرم‌های ما state خود را از سرور دریافت می‌کنند. فرض کنید که در حال ایجاد یک فرم ثبت اطلاعات فیلم‌ها هستیم. در این حالت باید بر اساس id فیلم، اطلاعات آن را از سرور دریافت و در state ذخیره کرد؛ سپس فیلدهای فرم را بر اساس آن مقدار دهی اولیه کرد. برای نمونه در فرم لاگین می‌توان state را با شیء account، به صورت زیر مقدار دهی اولیه کرد:
class LoginForm extends Component {
  state = {
    account: { username: "", password: "" }
  };
تا اینجا فیلدهای فرم لاگین، از این state مطلع نبوده و تغییرات داده‌های ورودی در آن‌ها، به شیء account منعکس نمی‌شوند. علت اصلی هم اینجا است که هر کدام از فیلدهای ورودی در React، دارای state خاص خود بوده و مستقل از state کامپوننت جاری هستند. برای رفع این مشکل باید آن‌ها را تبدیل به controlled element هایی کرد که دارای state خاص خود نبوده، تمام اطلاعات مورد نیاز خود را از طریق props دریافت می‌کنند و تغییرات در داده‌های خود را از طریق صدور رخ‌دادهایی اطلاع رسانی می‌کنند. برای اینکار باید مراحل زیر طی شوند:
ابتدا ویژگی value فیلد برای مثال username را به خاصیت username شیء account موجود در state متصل می‌کنیم:
<input 
  value={this.state.account.username}
به این ترتیب دیگر این المان، state خاص خود را نداشته و از طریق props، مقادیر خود را دریافت می‌کند. تا اینجا username، به رشته‌ی خالی دریافتی از شیء state و خاصیت account آن، به صورت یک طرفه متصل شده‌است. یعنی زمانیکه فرم نمایش داده می‌شود، دارای یک مقدار خالی است. برای اینکه تغییرات رخ‌داده‌ی در این المان را به state منعکس کرد، باید رخ‌داد change آن‌را مدیریت نمود. به این ترتیب زمانیکه کاربری اطلاعاتی را در اینجا وارد می‌کند، رخ‌داد change صادر شده و پس از آن می‌توان اطلاعات وارد شده را دریافت و state را به روز رسانی کرد. به روز رسانی state نیز سبب رندر مجدد فرم می‌شود. بنابراین فیلدهای ورودی، با اطلاعات state جدید، به روز رسانی و رندر می‌شوند. به همین جهت ابتدا رویداد onChange را به فیلد username اضافه کرده:
<input 
  value={this.state.account.username}
  onChange={this.handleChange}
و متد مدیریت کننده‌ی آن‌را به صورت زیر تعریف می‌کنیم:
  handleChange = e => {
    const account = { ...this.state.account }; //cloning an object
    account.username = e.currentTarget.value;
    this.setState({ account });
  };
در اینجا، هدف به روز رسانی this.state.account، بر اساس رخ‌داد رسیده (پارامتر e) است و چون نمی‌توان state را مستقیما به روز رسانی کرد، ابتدا یک clone از آن را تهیه می‌کنیم. سپس توسط e.currentTarget به المان در حال به روز رسانی دسترسی یافته و مقدار آن‌را به مقدار خاصیت username انتساب می‌دهیم. در آخر state را بر اساس این تغییرات، به روز رسانی می‌کنیم. این انعکاس در state را توسط افزونه‌ی react developer tools هم می‌توان مشاهده کرد:



مدیریت دریافت اطلاعات چندین فیلد ورودی

تا اینجا موفق شدیم اطلاعات state را به تغییرات فیلد username در فرم لاگین متصل کنیم؛ اما فیلد password را چگونه باید مدیریت کرد؟ برای اینکه تمام این مراحل را مجددا تکرار نکنیم، می‌توان از مقدار دهی پویای خواص در جاوا اسکریپت که توسط [] انجام می‌شود استفاده کرد:
  handleChange = e => {
    const account = { ...this.state.account }; //cloning an object
    account[e.currentTarget.name] = e.currentTarget.value;
    this.setState({ account });
  };
البته برای اینکه این قطعه کد کار کند، نیاز است ویژگی name فیلدهای ورودی را نیز تنظیم کرد تا e.currentTarget.name، به نام یکی از خواص شیء account تعریف شده‌ی در state اشاره کند. برای نمونه فیلد کلمه‌ی عبور، ابتدا دارای ویژگی value متصل به خاصیت password شیء account موجود در state می‌شود. سپس تغییرات آن توسط رویداد onChange، به متد handleChange منتقل شده و خاصیت name آن نیز مقدار دهی شده‌است تا مقدار دهی پویای خواص، در این متد میسر شود:
<input
  id="password"
  name="password"
  value={this.state.account.password}
  onChange={this.handleChange}
  type="password"
  className="form-control"
/>
که در نهایت سبب مقدار دهی صحیح state، با هر دو فیلد تغییر یافته می‌شود:


یک نکته: می‌توان توسط Object Destructuring، تکرار e.currentTarget را حذف کرد:
  handleChange = ({ currentTarget: input }) => {
    const account = { ...this.state.account }; //cloning an object
    account[input.name] = input.value;
    this.setState({ account });
  };
ما از شیء e دریافتی، تنها به خاصیت currentTarget آن نیاز داریم. بنابراین آن‌را از طریق Object Destructuring در همان پارامتر ورودی متد جاری دریافت کرده و سپس آن‌را به نام input، تغییر نام می‌دهیم.


آشنایی با خطاهای متداول دریافتی در حین کار با فرم‌ها

فرض کنید خاصیت username را از شیء account موجود در state حذف کرده‌ایم. در زمان نمایش ابتدایی فرم، خطایی را دریافت نخواهیم کرد، اما اگر اطلاعاتی را در آن وارد کنیم، بلافاصله در کنسول توسعه دهندگان مرورگر چنین اخطاری ظاهر می‌شود:
Warning: A component is changing an uncontrolled input of type text to be controlled.
Input elements should not switch from uncontrolled to controlled (or vice versa).
Decide between using a controlled or uncontrolled input element for the lifetime of the component.
More info: https://fb.me/react-controlled-components
چون خاصیت username را حذف کرده‌ایم، اینبار که در textbox مقداری را وارد می‌کنیم، سبب انتساب undefined و یا null به مقدار المان خواهد شد. در این حالت React چنین المانی را به صورت controlled element درنظر نمی‌گیرد و دارای state خاص خودش خواهد بود. به همین جهت عنوان می‌کند که بین یک المان کنترل شده و نشده، یکی را انتخاب کنید.
دقیقا چنین اخطاری را با ورود null/undefined بجای "" در حین مقدار دهی اولیه‌ی username در شیء account نیز دریافت خواهیم کرد:
Warning: `value` prop on `input` should not be null.
Consider using an empty string to clear the component or `undefined` for uncontrolled components.
بنابراین به عنوان یک قاعده در فرم‌های React، المان‌های یک فرم را باید توسط یک "" مقدار دهی اولیه کرد و یا با مقداری که از سمت سرور دریافت می‌شود.


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

هر چند در پیاده سازی فعلی سعی کردیم با بکارگیری مقداردهی پویای خواص اشیاء، تکرار کدها را کاهش دهیم، اما باز هم به ازای هر فیلد ورودی باید این مسایل تکرار شوند:
- ایجاد یک div با کلاس‌های بوت استرپی.
- ایجاد label و همچنین فیلد ورودی.
- در اینجا مقدار htmlFor باید با مقدار id فیلد ورودی یکی باشد.
- مقدار دهی ویژگی‌های value و onChange نیز باید تکرار شوند.

بنابراین بهتر است این تعاریف را استخراج و به یک کامپوننت با قابلیت استفاده‌ی مجدد منتقل کرد. به همین جهت فایل جدید src\components\common\input.jsx را در پوشه‌ی common ایجاد کرده و سپس توسط میانبرهای imrc و sfc، این کامپوننت تابعی بدون حالت را تکمیل می‌کنیم:
import React from "react";

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

export default Input;
در اینجا کل تگ div مرتبط با username را از کامپوننت فرم لاگین cut کرده و در اینجا در قسمت return، قرار داده‌ایم. سپس شروع به تبدیل مقادیر قبلی به مقادیری که قرار است از props تامین شوند، کرده‌ایم. یا می‌توان props را به عنوان آرگومان این متد تعریف کرد و یا می‌توان توسط Object Destructuring، خواصی را که از props نیاز داریم، در پارامتر متد Input ذکر کنیم که این روش چون به نوعی اینترفیس کامپوننت را نیز مشخص می‌کند و همچنین کدهای تکراری دسترسی به props را به حداقل می‌رساند، تمیزتر و با قابلیت نگهداری بالاتری است. برای مثال هر جائیکه نام username استفاده شده بود، با خاصیت name جایگزین شده و بجای برچسب از label، بجای مقدار username از متغیر value و بجای رخ‌داد تعریف شده نیز onChange قرار گرفته‌است.

سپس به کامپوننت فرم لاگین بازگشته و ابتدا آن‌را import می‌کنیم:
import Input from "./common/input";
اکنون متد رندر ماژول src\components\loginForm.jsx، به صورت زیر با درج دو Input، خلاصه می‌شود که دیگر در آن خبری از تگ‌ها و کدهای تکراری نیست:
  render() {
    const { account } = this.state;
    return (
      <form onSubmit={this.handleSubmit}>
        <Input
          name="username"
          label="Username"
          value={account.username}
          onChange={this.handleChange}
        />
        <Input
          name="password"
          label="Password"
          value={account.password}
          onChange={this.handleChange}
        />
        <button className="btn btn-primary">Login</button>
      </form>
    );


کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید:  sample-18.zip
مطالب
توسعه برنامه‌های Cross Platform با Xamarin Forms & Bit Framework - قسمت هجدهم
در این قسمت می‌خواهیم با Rest Api ارتباط برقرار کنیم. به جای نوشتن سمت سرور، از یک سرور آماده استفاده می‌کنیم که مثال اول آن، LIST USERS است و لیست کاربران را نمایش می‌دهد. توضیحات این قسمت به فراخوانی سرویس‌های Rest ارتباط دارد، با پروتکل HTTP و دیتای JSON. البته فراخوانی سرویس‌های SOAP نیز ساده است که در این آموزش به آنها نمی‌پردازیم.
برای این کار از HttpClient استفاده می‌کنیم. استفاده کردن از WebClient و WebRequest اشتباه محض هست و این دو را کلا فراموش کنید. مطمئن باشید هر کدی که با آن دو در اینترنت پیدا می‌کنید، با HttpClient هم قابلیت پیاده سازی را دارند و مطمئن باشید که اگر از آن دو کلاس استفاده کنید، حتما به دردسر بدی میافتید. در زمان استفاده از HttpClient هم در نظر بگیرید که نباید مدام HttpClient را new و dispose کنید. این کار اشتباه است و یک HTTP client برای شما کافی است. ساختن HTTP client نکات  بسیاری دارد که در همین سایت به آنها پرداخته شده‌است. در Xamarin دغدغه‌های استفاده از Network Stack هر سیستم عامل نیز به لیست مواردی که باید به آنها دقت کنید اضافه می‌شوند. می‌توانید درگیر تمامی این موارد شوید، یا برای سادگی بیشتر، ضمن نصب پکیج Bit.CSharpClient.Rest که کدهای آن نیز در GitHub قرار داده شده‌اند، صرفا HTTP Client را بگیرید و به هر جایی که دوست دارید Request بزنید. لزومی به اینکه در سمت سرور از Bit استفاده کرده باشید تا بتوانید از Bit.CSharpClient.Rest استفاده کنید نیست.

خب، پس Package مربوطه را نصب و در App.xaml.cs کدهای زیر را استفاده کنید:
//قرار دهید containerBuilder.RegisterRequiredServices(); این دو را بعد از
containerBuilder.RegisterHttpClient();
containerBuilder.RegisterIdentityClient();
در View Model ای که قصد استفاده از Http Client را دارید، یک Property از جنس Http Client تعریف کنید و برای خواندن اطلاعات مثال، کد زیر را بزنید:
توضیحات این کد در ادامه آمده است.
public virtual HttpClient HttpClient { get; set; } 

async Task CallUsersListApiUsingHttpClient()
{
    HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, "https://reqres.in/api/users");
    // Use request.Headers to set jwt token, ...
    // Use request.Content to send body. You can use StringContent, StreamContent, etc.
    HttpResponseMessage response = await HttpClient.SendAsync(request);
    response.EnsureSuccessStatusCode();
    using (StreamReader streamReader = new StreamReader(await response.Content.ReadAsStreamAsync()))
    using (JsonReader jsonReader = new JsonTextReader(streamReader))
    {
        List<UserDto> users = (await JToken.LoadAsync(jsonReader))["data"].ToObject<List<UserDto>>();
    }
}
برای درک بهتر این کد، بعد از Clone/Pull کردن آخرین وضعیت پروژه XamApp به سراغ کلاس RestSamplesViewModel بروید. فراخوانی https://reqres.in/api/users چنین JSON ای را بر می‌گرداند: 
{
    "page": 2,
    "per_page": 3,
    "total": 12,
    "total_pages": 4,
    "data": [
        {
            "id": 4,
            "first_name": "Eve",
            "last_name": "Holt",
            "avatar": "https://s3.amazonaws.com/uifaces/faces/twitter/marcoramires/128.jpg"
        },
        {
            "id": 5,
            "first_name": "Charles",
            "last_name": "Morris",
            "avatar": "https://s3.amazonaws.com/uifaces/faces/twitter/stephenmoon/128.jpg"
        }
    ]
}

قسمت‌های مختلف این JSON برای ما اهمیتی ندارند و تنها قسمت data آن که اطلاعات user‌ها را شامل می‌شود، برای ما اهمیت دارند. صد البته که هر سروری، دیتای JSON را با ساختاری که دوست داشته باشد بر می‌گرداند. در کدی که نوشته‌ایم، ابتدا یک HttpRequestMessage را ساخته‌ایم. این HttpRequestMessage از نوع Get و به آدرس https://reqres.in/api/users است. می‌توان روی HttpRequestMessage هم هدرهای مختلفی را تنظیم نمود و هم می‌توان به آن Content داد.
سپس آن را با HttpClient.SendAsync ارسال می‌کنیم و با فراخوانی EnsureSuccessStatusCode مطمئن می‌شویم که خطا نداده‌است. برای خواندن Response با بالاترین Performance ممکن، ابتدا از StreamReader برای خواندن Stream دریافتی استفاده می‌کنیم. با توجه به JSON بودن Response دریافتی، از JsonTextReader و JToken استفاده می‌کنیم (این مورد هیچ ربطی به JWT یا Json Web Token ندارد!). بعد از Load کردن آن، قسمت ["data"] را به لیستی از کلاس UserDto تبدیل می‌کنیم. Dto مخفف Data Transfer Object است و دیتایی است که ما یا ارسال می‌کنیم یا در همین سناریو مثال، از سرور دریافت می‌کنیم. کد کلاس UserDto:
public class UserDto
{
    [JsonProperty("id")]
    public int Id { get; set; }
    [JsonProperty("first_name")]
    public string FirstName { get; set; }
    [JsonProperty("last_name")]
    public string LastName { get; set; }
    [JsonProperty("avatar")]
    public string Avatar { get; set; }
}
البته Http Client فقط برای ارسال یا دریافت JSON نیست. می‌توان با آن فایل و Xml و ... نیز ارسال و دریافت نمود. در این قسمت مهم نبود که سرور شما با چه تکنولوژی ای توسعه داده شده‌است. صرف بودن سرور روی پروتکل Http کافی است. واضح است که شما دارید از HttpClient استفاده می‌کنید. در صورتیکه سرور TCP باشد، شما در CSharp می‌توانید از TcpClient و Socket استفاده کنید. اگر سمت سرور شما Wcf یا OData یا Graphql باشد نیز کلاینت‌های خودشان را در CSharp دارید و می‌توانید در پروژه‌تان از تمامی آنها استفاده کنید که آموزش همه این موارد از حوصله این قسمت خارج است؛ اما در صورتیکه سمت سرور شما نیز با Bit توسعه داده شده باشد، می‌توانید با روش‌های خیلی بهتری به سرور خود وصل شوید که این موضوع قسمت‌های بعدی آموزش است.