مطالب
دسترسی به Collectionها در یک ترد دیگر در WPF
اگر در WPF سعی کنیم آیتمی را به مجموعه اعضای یک Collection مانند یک List یا ObservableCollection از طریق تردی دیگر اضافه کنیم، با خطای ذیل متوقف خواهیم شد:
 This type of CollectionView does not support changes to its SourceCollection
from a thread different from the Dispatcher thread
راه حلی که برای آن تا دات نت 4 در اکثر سایت‌ها توصیه می‌شد به نحو ذیل است:
Adding to an ObservableCollection from a background thread


مشکل!
اگر همین برنامه را که برای دات نت 4 کامپایل شده‌است، بر روی سیستمی که دات نت 4.5 بر روی آن نصب است اجرا کنیم، برنامه با خطای ذیل متوقف می‌شود:
 System.InvalidOperationException: This exception was thrown because the generator for control
'System.Windows.Controls.ListView Items.Count:62' with name '(unnamed)' has received sequence of
CollectionChanged events that do not agree with the current state of the Items collection.
The following differences were detected:  Accumulated count 61 is different from actual count 62.


مشکل از کجاست؟
در دات نت 4 و نیم، دیگر نیازی به استفاده از کلاس MTObservableCollection یاد شده نیست و به صورت توکار امکان کار با Collectionها از طریق تردی دیگر میسر است. فقط برای فعال سازی آن باید نوشت:
 private static object _lock = new object();
//...
BindingOperations.EnableCollectionSynchronization(persons, _lock);
پس از اینکه برای نمونه، مجموعه‌ی فرضی persons وهله سازی شد، تنها کافی است متد جدید EnableCollectionSynchronization بر روی آن فراخوانی شود.


برای برنامه‌ی دات نت 4 ایی که قرار است در سیستم‌های مختلف اجرا شود چطور؟

در اینجا باید از Reflection کمک گرفت. اگر متد EnableCollectionSynchronization بر روی کلاس BindingOperations یافت شد، یعنی برنامه‌ی دات نت 4، در محیط جدید در حال اجرا است:
public static void EnableCollectionSynchronization(IEnumerable collection, object lockObject)
{
      MethodInfo method = typeof(BindingOperations).GetMethod("EnableCollectionSynchronization",
             new Type[] { typeof(IEnumerable), typeof(object) });
      if (method != null)
      {
         method.Invoke(null, new object[] { collection, lockObject });
      }
}
در این حالت فقط کافی است این متد جدید یافت شده را بر روی Collection مدنظر فراخوانی کنیم.
همچنین اگر بخواهیم کلاس MTObservableCollection معرفی شده را جهت سازگاری با دات نت 4 و نیم به روز کنیم، به کلاس ذیل خواهیم رسید. این کلاس با دات نت 4 و 4.5 سازگار است و جهت کار با ObservableCollectionها از طریق تردهای مختلف تهیه شده‌است:
using System;
using System.Collections;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Linq;
using System.Windows.Data;
using System.Windows.Threading;

namespace WpfAsyncCollection
{
    public class AsyncObservableCollection<T> : ObservableCollection<T>
    {
        public override event NotifyCollectionChangedEventHandler CollectionChanged;
        private static object _syncLock = new object();

        public AsyncObservableCollection()
        {
            enableCollectionSynchronization(this, _syncLock);
        }

        protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
        {
            using (BlockReentrancy())
            {
                var eh = CollectionChanged;
                if (eh == null) return;

                var dispatcher = (from NotifyCollectionChangedEventHandler nh in eh.GetInvocationList()
                                  let dpo = nh.Target as DispatcherObject
                                  where dpo != null
                                  select dpo.Dispatcher).FirstOrDefault();

                if (dispatcher != null && dispatcher.CheckAccess() == false)
                {
                    dispatcher.Invoke(DispatcherPriority.DataBind, (Action)(() => OnCollectionChanged(e)));
                }
                else
                {
                    foreach (NotifyCollectionChangedEventHandler nh in eh.GetInvocationList())
                        nh.Invoke(this, e);
                }
            }
        }

        private static void enableCollectionSynchronization(IEnumerable collection, object lockObject)
        {
            var method = typeof(BindingOperations).GetMethod("EnableCollectionSynchronization", 
                                    new Type[] { typeof(IEnumerable), typeof(object) });
            if (method != null)
            {
                // It's .NET 4.5
                method.Invoke(null, new object[] { collection, lockObject });
            }
        }
    }
}
در این کلاس، در سازنده‌ی آن متد عمومی enableCollectionSynchronization فراخوانی می‌شود. اگر برنامه در محیط دات نت 4 فراخوانی شود، تاثیری نخواهد داشت چون method در حال بررسی نال است. در غیراینصورت، برنامه در حالت سازگار با دات نت 4.5 اجرا خواهد شد.
مطالب
کار با modal dialogs مجموعه Bootstrap در برنامه‌های Angular
در مطلب «Angular CLI - قسمت ششم - استفاده از کتابخانه‌های ثالث» با نحوه‌ی دریافت، نصب و راه اندازی کتابخانه‌ی ngx-bootstrap آشنا شدیم. در اینجا می‌خواهیم نحوه‌ی کار با کامپوننت Modal آن را بررسی کنیم.


سازماندهی بهتر کامپوننت‌های ngx-bootstrap

پس از نصب بسته‌ی npm کتابخانه‌ی ngx-bootstrap و تنظیم فایل angular-cli.json. که در مطلب «Angular CLI - قسمت ششم - استفاده از کتابخانه‌های ثالث» بررسی شدند، برای کار با کامپوننت‌های این کتابخانه باید متدهای BsDropdownModule.forRoot، TooltipModule.forRoot، ModalModule.forRoot و ... را به قسمت imports فایل app.module.ts اضافه کرد. با انجام این‌کار پس از مدتی به یک فایل بسیار شلوغ app.module.ts خواهیم رسید. برای مدیریت بهتر آن می‌توان شبیه به مطلب «سازماندهی برنامه‌های Angular توسط ماژول‌ها» در پوشه‌ی Shared برنامه، ماژول ذیل را تدارک دید. برای اینکار ابتدا فایل جدید src\app\shared\shared.bootstrap.module.ts را ایجاد نمائید. سپس کامپوننت‌های این کتابخانه را به صورت ذیل در این تک ماژول اختصاصی قرار دهید:
import { NgModule } from "@angular/core";
import { CommonModule } from "@angular/common";
import { BsDropdownModule } from "ngx-bootstrap/dropdown";
import { TooltipModule } from "ngx-bootstrap/tooltip";
import { ModalModule } from "ngx-bootstrap/modal";

@NgModule({
  imports: [
    CommonModule,
    BsDropdownModule.forRoot(),
    TooltipModule.forRoot(),
    ModalModule.forRoot()
  ],
  exports: [
    BsDropdownModule,
    TooltipModule,
    ModalModule
  ]
})
export class SharedBootstrapModule { }
متدهای forRoot در قسمت imports قرار می‌گیرند (فلسفه‌ی وجودی این متد و الگوی ویژه را در مطلب «سازماندهی برنامه‌های Angular توسط ماژول‌ها» پیشتر بررسی کرده‌ایم). سپس برای اینکه این کامپوننت‌ها در سایر ماژول‌های برنامه قابل استفاده باشند، باید نام ماژول مرتبط با هر کدام را در قسمت exports نیز ذکر کرد.
اکنون برای استفاده‌ی از SharedBootstrapModule اختصاصی فوق، می‌توان دو روش را بکار برد:
الف) import مستقیم آن در فایل app.module.ts
import { SharedBootstrapModule } from './shared/shared.bootstrap.module';
@NgModule({
  imports: [BrowserModule, SharedBootstrapModule],
  // ...
})
export class AppModule {}
ب) import آن در SharedModule
و یا اگر فایل src\app\shared\shared.module.ts را مطابق مطلب «سازماندهی برنامه‌های Angular توسط ماژول‌ها» ایجاد کرده‌اید، این ماژول به صورت ذیل، در دو قسمت imports و exports آن اضافه خواهد شد:
import { SharedBootstrapModule } from "./shared.bootstrap.module";

@NgModule({
  imports: [
    CommonModule,
    SharedBootstrapModule
  ],
  exports: [
    CommonModule,
    SharedBootstrapModule
  ]
})


نمایش یک modal dialog توسط کامپوننت Modal

پس از تعریف ModalModule.forRoot، اکنون می‌توان به کامپوننت Modal این ماژول دسترسی یافت. برای این منظور کامپوننتی که قرار است یک Modal را نمایش دهد، چنین ساختاری را پیدا می‌کند:
import { Component, OnInit, TemplateRef } from "@angular/core";
import { BsModalRef, BsModalService } from "ngx-bootstrap";

@Component({
  selector: "app-modal-dialog-test",
  templateUrl: "./modal-dialog-test.component.html",
  styleUrls: ["./modal-dialog-test.component.css"]
})
export class ModalDialogTestComponent implements OnInit {

  modalRef: BsModalRef;

  constructor(private modalService: BsModalService) { }

  openModal(template: TemplateRef<any>) {
    this.modalRef = this.modalService.show(template,
      { animated: true, keyboard: true, backdrop: true, ignoreBackdropClick: false });
  }

  closeModal() {
    this.modalRef.hide();
  }
}
توسط سرویس BsModalService که به سازنده‌ی کلاس کامپوننت تزریق شده‌است، می‌توان به متد show آن دسترسی یافت. این متد یک ng-template را قبول می‌کند. بنابراین در قالب این کامپوننت باید قسمتی را که قرار است به صورت modal نمایش داده شود، توسط یک ng-template تعریف کرد.
سپس با فراخوانی متد this.modalService.show می‌توان این قالب را نمایش داد. خروجی این متد ارجاعی را به این modal بازگشت می‌دهد. از این ارجاع می‌توان در جهت بستن آن استفاده کرد (مانند متد closeModal).

بنابراین در ادامه، قالب کامپوننت مثال این قسمت، یک چنین شکلی را پیدا می‌کند:
<h1>Displaying modal bootstrap dialogs</h1>

<button type="button" class="btn btn-info" (click)="openModal(template1)">Create template modal</button>

<ng-template #template1>
  <div class="modal-header">
    <h4 class="modal-title pull-left">Modal</h4>
    <button type="button" class="close pull-right" aria-label="Close" (click)="closeModal()">
      <span aria-hidden="true">&times;</span>
    </button>
  </div>
  <div class="modal-body">
    This is a modal.
  </div>
</ng-template>
در اینجا محتوای modal داخل یک ng-template قرار گرفته‌است و این قالب توسط یک template reference variable به نام template1 مشخص شده‌است. این نام را در متد openModal(template1) استفاده خواهیم کرد تا به متد show سرویس نمایش modal منتقل شود.



طراحی یک کامپوننت عمومی مودال جهت دریافت تائید انجام عملیات

در ادامه می‌خواهیم توسط یک modal dialog، کار دریافت تائید و یا لغو انجام یک عملیات را انجام دهیم. چون این کامپوننت عمومی قرار است در بیش از یک ماژول استفاده شود، بنابراین نیاز است آن‌را در Shared Module ثبت کرد. به همین جهت این کامپوننت را به نحو ذیل در پوشه‌ی Shared ایجاد می‌کنیم:
 ng g c Shared/ConfirmModal --skip-import
پرچم skip-import نیز ذکر شده‌است، چون قصد نداریم به صورت مستقیم از طریق درج selector آن در صفحه، با آن کار کنیم. سرویس سفارشی مودالی که برای این منظور تدارک خواهیم دید، کار نمایش آن‌را انجام می‌دهد.
import { ConfirmModalComponent } from "./confirm-modal/confirm-modal.component";

@NgModule({
  imports: [
  ],
  entryComponents: [
    ConfirmModalComponent
  ],
  declarations: [
    ConfirmModalComponent
  ]
})
export class SharedModule {}
نحوه‌ی درج و تعریف این کامپوننت اندکی متفاوت است. چون این کامپوننت قرار است «به صورت پویا» توسط متد show سرویس BsModalService نمایش داده شود (پارامتر اول آن می‌تواند یک قالب و یا یک کامپوننت کامل باشد)، باید در قسمت entryComponents و declarations مربوط به SharedModule درج شود. آن‌را در قسمت exports ذکر نمی‌کنیم، چون قرار نیست با درج مستقیم selector آن در صفحه، آن‌را نمایش دهیم.

این کامپوننت دریافت تائید کاربر به صورت ذیل تعریف می‌شود:
import { Component } from "@angular/core";

@Component({
  selector: "app-confirm-modal",
  templateUrl: "./confirm-modal.component.html",
  styleUrls: ["./confirm-modal.component.css"]
})
export class ConfirmModalComponent {

  args: {
    title: string;
    message: string;
  };

  close: (val?: any) => void;

}
در اینجا args آن توسط سرویسی که در ادامه طراحی می‌کنیم، مقدار دهی خواهد شد (طراحی args در اینجا کاملا دلخواه است و در کامپوننت‌های مشابه دیگر می‌تواند متفاوت باشد). متد close آن نیز کار گزارش دهی به فراخوان را انجام می‌دهد.
قالب این کامپوننت نیز بدون استفاده از ng-template تعریف می‌شود:
<div class="modal-header">
  <h4 class="modal-title pull-left">{{ args?.title }}</h4>
  <button type="button" class="close pull-right" aria-label="Close" (click)="close()">
    <span aria-hidden="true">&times;</span>
  </button>
</div>
<div class="modal-body">
  <p>{{ args?.message }}</p>
</div>
<div class="modal-footer">
  <button class="btn btn-danger" (click)="close(true)">Yes</button>
  <button class="btn btn-primary" (click)="close()">Cancel</button>
</div>
چون این کامپوننت قرار است به صورت پویا توسط متد show بارگذاری شود، نیازی نیست محتوای قالب آن‌را توسط ng-template مخفی کرد و سپس نمایش داد. زمانیکه این کامپوننت بارگذاری شد، یعنی قصد داریم یک modal کامل را نمایش دهیم.

تا اینجا یک کامپوننت نمایش دریافت تائید انجام عملیات را تهیه کردیم. در ادامه نیاز است یک سرویس را جهت بارگذاری پویای اینگونه کامپوننت‌های مودال طراحی کنیم. این سرویس عمومی در پوشه‌ی Core و CoreModule ثبت خواهد شد:
 >ng g s Core/Modal
با این محتوا
import { Injectable } from "@angular/core";
import { BsModalService } from "ngx-bootstrap";

@Injectable()
export class ModalService {

  constructor(private bsModalService: BsModalService) { }

  show(component: any, args?: any, options?: any): Promise<any> {
    return new Promise(resolve => {
      options = options || {};
      const modal = this.bsModalService.show(component, options);
      let result: any;
      const sub = this.bsModalService.onHidden.subscribe(() => {
        sub.unsubscribe();
        resolve(result);
      });
      modal.content.args = args;
      modal.content.close = (val?: any) => {
        result = val;
        modal.hide();
      };
    });
  }
}
کار این سرویس، نمایش یک کامپوننت مودال مانند ConfirmModalComponent به صورت پویا است؛ از این جهت که متد this.bsModalService.show هم امکان نمایش یک ng-template را دارد و هم یک کامپوننت کامل را به صورت پویا.

یک مودال در سه حالت ممکن است بسته شود:
الف) کلیک بر روی دکمه‌ی close و یا cancel
ب) کلیک بر روی علامت ضربدر درج شده‌ی در یک سمت عنوان آن
ج) کلیک بر روی قسمتی از صفحه، خارج از مودال
در حالات ب و ج، رخ‌داد this.bsModalService.onHidden فراخوانی می‌شود. در حالت الف، همان متد close درج شده‌ی در کامپوننت فراخوانی می‌شود.
برای اینکه بتوان نتیجه‌ی عملیات را از طرف یک سرویس به کامپوننت فراخوان آن گزارش دهیم، یکی از روش‌ها، استفاده از Promiseها است که مشاهده می‌کنید. با فراخوانی resolve(result)، کار ارسال نتیجه‌ی فراخوانی متدهای close(true) و ()close صورت می‌گیرد (یا true و یا undefined).

خاصیت modal.content امکان دسترسی به خواص عمومی کامپوننت در حال استفاده را میسر می‌کند (content به کامپوننت بارگذاری شده اشاره می‌کند). اینجا است که می‌توان برای مثال به خاصیت args یک کامپوننت، مقادیری را نسبت داد و یا به متد close آن دسترسی یافت.

پس از افزودن این سرویس، محل تعریف آن در قسمت providers مربوط به CoreModule است تا در تمام برنامه قابل دسترسی شود:
import { ModalService } from "./modal.service";

@NgModule({
  providers: [
    ModalService
  ]
})
export class CoreModule {}
در پایان برای آزمایش این سرویس جدید، یک دکمه و یک برچسب را به قالب کامپوننت ModalDialogTestComponent ابتدای بحث اضافه می‌کنیم:
<button type="button" class="btn btn-danger" (click)="deleteRecord()">Delete record</button>
<div *ngIf="confirmResult" class="alert alert-info">{{confirmResult}}</div>
با این کدها:
import { ModalService } from "./../../core/modal.service";
import { ConfirmModalComponent } from "./../../shared/confirm-modal/confirm-modal.component";

export class ModalDialogTestComponent implements OnInit {

  confirmResult: string;

  constructor(private modalService: ModalService) { }

  deleteRecord() {
    this.confirmResult = "";
    this.modalService.show(
      ConfirmModalComponent,
      {
        title: "Confirm", message: "Do you want to delete this record?"
      },
      {
        animated: true, keyboard: true, backdrop: true, ignoreBackdropClick: false
      }).then(confirmed => {
        if (confirmed) {
          this.confirmResult = "Deleted!";
        } else {
          this.confirmResult = "Canceled!";
        }
      });
  }
}
در اینجا نحوه‌ی استفاده‌ی از این ModalService سفارشی را ملاحظه می‌کنید. ابتدا به سازنده‌ی کلاس تزریق شده‌است و سپس در متد deleteRecord توسط متد show آن، کامپوننت ConfirmModalComponent به صورت پویا بارگذاری شده‌است. همچنین خاصیت args آن نیز با خواص title و message سفارشی، مقدار دهی شده‌است. چون این متد یک Promise را باز می‌گرداند، می‌توان مشترک آن شد و نتیجه‌ی نهایی را از آن دریافت کرد و بر اساس آن تصمیم گرفت که آیا باید عملیاتی رخ‌دهد، یا خیر.


توسط this.modalService.show می‌توان انواع و اقسام کامپوننت‌های مودال را به صورت پویا بارگذاری کرد و نمایش داد.


کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید.
نظرات مطالب
اجرای وظایف زمان بندی شده با Quartz.NET - قسمت اول
سلام.
من یه وظیفه نوشتم که می‌یاد هر 1 دقیقه 1 بار یه متن رو در Table درج می‌کنه.
این کد کلاس
using Quartz;
using System.Data;
using System.Data.OleDb;
using System.Configuration;


namespace WebApplication1
{
    
    public class TestQuartzClass:IJob
    {
        
       
        public void Execute(IJobExecutionContext context)
        {
            //Page myPage = (Page)HttpContext.Current.Handler;
            //TextBox MyTextBox=(TextBox)myPage.FindControl("txt");
            
            string sql = "Insert into tbl_Test (Content) values (@Content)";
            ExecuteNoneQuery(System.Data.CommandType.Text, sql, new OleDbParameter[]{
                //new OleDbParameter("@Content", MyTextBox.Text)
                new OleDbParameter("@Content","Hello world!")
            });

        }

        public int ExecuteNoneQuery(CommandType commandType, string commandText, params OleDbParameter[] commandParameters)
        {
            using (OleDbConnection con = new OleDbConnection(ConfigurationManager.ConnectionStrings["ConStr"].ConnectionString))
            {
                OleDbCommand cmd = new OleDbCommand();
                cmd.Connection = con;
                cmd.CommandType = commandType;
                cmd.CommandText = commandText;
                cmd.Parameters.AddRange(commandParameters);
                con.Open();
                int retVal = cmd.ExecuteNonQuery();
                con.Close();
                return retVal;
            }
        }
    }


}



و این هم کد صفحه ای که با کلیک دکمه وظیفه شروع به کار می‌کنه

using System;
using Quartz;
using Quartz.Impl;

namespace WebApplication1
{
    public partial class WebForm1 : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {

        }

        public static void ConfigureQuartzJobs()
        {
            // construct a scheduler factory
            ISchedulerFactory schedFact = new StdSchedulerFactory();

            // get a scheduler
            IScheduler sched = schedFact.GetScheduler();
            sched.Start();
            IJobDetail job = JobBuilder.Create<TestQuartzClass>()
                .WithIdentity("SendJob")
                .Build();
            var trigger = TriggerBuilder.Create()
                .WithIdentity("SendTrigger")
                .WithSimpleSchedule(x => x.WithIntervalInMinutes(1).RepeatForever())
                //.StartAt(startTime)
                .StartNow()
                .Build();

            sched.ScheduleJob(job, trigger);
        }

        protected void btn_Click(object sender, EventArgs e)
        {
            ConfigureQuartzJobs();
        }
    }
}

همونطور که می‌بینید متن رو به شکل زیر پاس دادم.
new OleDbParameter("@Content","Hello world!")
ولی من می‌خوام این متن رو از TextBox بگیرم.
و برای اینکه در کلاس بتونم به کنترلهای صفحه دسترسی داشته باشم کدهای زیر رو به متد Exceute اضافه کردم
Page myPage = (Page)HttpContext.Current.Handler;
            TextBox MyTextBox=(TextBox)myPage.FindControl("txt");

ولی به محض اینکه این کدها رو اضافه می‌کنم دیگه برنامه کار نمی‌کنه. 

متن داخل تکست باکس رو هم قصد داشتم به شکل زیر پاس بدم.
new OleDbParameter("@Content", MyTextBox.Text)

لطفاً راهنمایی کنید.
من یک نمونه هم به منظور تست آماده کردم که از لینک زیر می‌تونید دانلود کنید:
http://www.4shared.com/rar/1Fu_jpOOba/WebApplication1.html 
نظرات مطالب
روشی برای محاسبه‌ی تعداد کاربران آنلاین در ASP.NET Core
متد invoke به صورت زیر اصلاح شد:
public async Task Invoke(HttpContext context)
        {
            if (!_memoryCache.TryGetValue("OnlineUsers", out Dictionary<string,DateTime> onlineUsers))
            {
                onlineUsers = new Dictionary<string, DateTime>();
                _memoryCache.Set("OnlineUsers", onlineUsers , new MemoryCacheEntryOptions() { AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(10) });
            }
            if (context.User.Identity.IsAuthenticated)
            {
                var name = context.User.Identity.Name;
                if (name != null)
                {
                    if (onlineUsers.ContainsKey(name))
                        onlineUsers[name] = DateTime.Now;
                    else
                        onlineUsers.Add(name, DateTime.Now);
                }

            }


            await _next(context);
        }
در مورد بحث thread-safe  باید MemoryCashe را بازنویسی کرد و متد GetOrAdd را فراخوانی کرد 
مطالب
آشنایی با CLR: قسمت هفتم
کدهای IL و تایید آن ها

ساختار استکی
IL از ساختار استک استفاده می‌کند. به این معنی که تمامی دستور العمل‌ها داخل آن push شده و نتیجه‌ی اجرای آن‌ها pop می‌شوند. از آنجا که IL به طور مستقیم ارتباطی با ثبات‌ها ندارد، ایجاد زبانهای برنامه نویسی جدید بر اساس CLR بسیار راحت‌تر هست و عمل کامپایل، تبدیل کردن به کدهای IL می‌باشد.

بدون نوع بودن(Typeless)
از دیگر مزیت‌های آن این است که کدهای IL بدون نوع هستند. به این معنی که موقع افزودن دستورالعملی به داخل استک، دو عملگر وارد می‌شوند و هیچ جداسازی در رابطه با سیستم‌های 32 یا 64 بیت صورت نمی‌گیرد و موقع اجرای برنامه است که تصمیم می‌گیرد از چه عملگرهایی باید استفاده شود.

Virtual Address Space
بزرگترین مزیت این سیستم‌ها امنیت و مقاومت آن هاست. موقعی که تبدیل کد IL به سمت کد بومی صورت می‌گیرد، CLR فرآیندی را با نام verification یا تاییدیه، اجرا می‌کند. این فرآیند تمامی کدهای IL را بررسی می‌کند تا از امنیت کدها اطمینان کسب کند. برای مثال بررسی می‌کند که هر متدی صدا زده می‌شود با تعدادی پارامترهای صحیح صدا زده شود و به هر پارامتر آن نوع صحیحش پاس شود و مقدار بازگشتی هر متد به درستی استفاده شود. متادیتا شامل اطلاعات تمامی پیاده سازی‌ها و متدها و نوع هاست که در انجام تاییدیه مورد استفاده قرار می‌گیرد.
در ویندوز هر پروسه، یک آدرس مجازی در حافظه دارد و این جدا سازی حافظه و ایجاد یک حافظه مجازی کاری لازم اجراست. شما نمی‌توانید به کد یک برنامه اعتماد داشته باشید که از حد خود تخطی نخواهد کرد و فرآیند برنامه‌ی دیگر را مختل نخواهد کرد. با خواندن و نوشتن در یک آدرس نامعتبر حافظه، ما این اطمینان را کسب می‌کنیم که هیچ گاه تخطی در حافظه صورت نمی‌گیرد.
قبلا به طور مفصل در این مورد ذخیره سازی در حافظه صحبت کرده ایم.

Hosting
از آنجا که پروسه‌های ویندوزی به مقدار زیادی از منابع سیستم عامل نیاز دارند که باعث کاهش منابع و محدودیت در آن می‌شوند و نهایت کارآیی سیستم را پایین می‌آورد، ولی با کاهش تعدادی برنامه‌های در حال اجرا به یک پروسه‌ی واحد می‌توان کارآیی سیستم را بهبود بخشید و منابع کمتری مورد استفاده قرار می‌گیرند که این یکی دیگر از مزایای کدهای managed نسبت به unmanaged است. CLR در حقیقت این قابلیت را به شما می‌دهد تا چند برنامه‌ی مدیریت شده را در قالب یک پروسه به اجرا درآورید. هر برنامه‌ی مدیریت شده به طور پیش فرض بر روی یک appDomain اجرا می‌گردد و هر فایل EXE روی حافظه‌ی مجازی مختص خودش اجرا می‌شود. هر چند پروسه‌هایی از قبیل IIS و SQL Server که پروسه‌های CLR را پشتیبانی یا هاست می‌کنند می‌توانند تصمیم بگیرند که آیا appDomain‌ها را در یک پروسه‌ی واحد اجرا کنند یا خیر که در مقاله‌های آتی آن را بررسی می‌کنیم.

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

موقعیکه جیت تلاش دارد تا یک کد ناامن را کامپایل کند، اسمبلی را بررسی می‌کند که آیا این متد اجازه و تاییدیه آن را دارد یا خیر. آیا  System.Security.Permissions.SecurityPermission با فلگ SkipVerification مقدار دهی شده است یا خیر. اگر پاسخ مثبت بود JIT آن‌ها را کامپایل کرده و اجازه‌ی اجرای آن‌ها را می‌دهد. CLR به این کد اعتماد می‌کند و امیدوار است که آدرس دهی مستقیم و دستکاری بایت‌های حافظه موجب آسیبی نگردد. ولی اگر پاسخ منفی بود، یک استثناء از نوع System.InvalidProgramException یا System.Security.VerificationException را ایجاد می‌کند تا از اجرای این متد جلوگیری به عمل آید. در واقع کل برنامه خاتمه میابد ولی آسیبی به حافظه نمی‌زند.

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

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

IL و حقوق حق تالیف آن
بسیاری از توسعه دهندگان از اینکه IL هیچ شرایطی برای حفظ حق تالیف آن‌ها ایجاد نکرده است، ناراحت هستند. چرا که ابزارهای زیادی هستند که با انجام عملیات مهندسی معکوس می‌توانند به الگوریتم آنان دست پیدا کنند و میدانید که IL خیلی سطح پایین نیست و برگرداندن آن به شکل یک کد، کار راحت‌تری هست و بعضی ابزارها کدهای خوبی هم ارائه می‌کنند. از دست این ابزارها می‌توان به ILDisassembler و  JustDecompile اشاره کرد.
اگر علاقمند هستید این عیب را برطرف کنید، می‌توانید از ابزارهای ثالث که به ابزارهای obfuscator (یک نمونه سورس باز ) معروف هستند استفاده کنید تا با کمی پیچیدگی در متادیتاها، این مشکل را تا حدی برطرف کنند. ولی این ابزارها خیلی کامل نیستند، چرا که نباید به کامپایل کردن کار لطمه بزنند. پس اگر باز خیلی نگران این مورد هستید می‌توانید الگوریتم‌های حساس و اساسی خود را در قالب unmanaged code ارائه کنید که در بالا اشاراتی به آن کرده‌ایم.
برنامه‌های تحت وب به دلیل عدم دسترسی دیگران از امنیت کاملتری برخوردار هستند.
مطالب
نحوه ایجاد یک اسلایدشو به صورت داینامیک

در این آموزش قصد دارم نحوه ایجاد یک Slideshow به صورت داینامیک را با ASP.NET طراحی کنم(منظور از ایجاد Slideshow به صورت داینامیک این است که عکس‌ها را به صورت داینامیک از DB بخواند).

اولین گام این است که Plugin مورد نظر را دریافت کنید که من از پلاگین Orbit استفاده کرده ام

ابتدا یک DataBase با نام DynamicSlideShow ایجاد و یک جدول با ساختار زیر با نام Picturesدرون آن ایجاد می‌کنیم

گام بعدی ایجاد یک پروژه Asp.Net با زبان C# و اضافه کردن فایلهای پلاگین به پروژه و ایجاد یک Handler برای بازیابی داده‌ها از DB است. ساختار   Solutionما باید به صورت زیر باشد

برای اینکه بتوانیم با DB ارتباط برقرار کنیم از EF استفاده می‌کنیم به همین منظور ابتدا یک Model  به نام DynamicSlideShowModel ایجاد می‌کنیم

در گام بعد بر روی GenerateFromDatabase  انتخاب کرده و بر روی دکمه Next کلیک می‌کنیم و در مراحل بعد ابتدا DB مربوط به مثال (DynamicSlideShow) و جدول آن را انتخاب می‌کنیم . حال یک Model به درون پوشه App_Code  اضافه شده است  

در ادامه برای واکشی رکوردهای موجود در جدول  Pictures  کدهای زیر را درون Handler می‌نویسیم

  var ctx = new DynamicSlideShowEntities();
  var list = ctx.Pictures.ToList();
  string str = JsonConvert.SerializeObject(list);
  context.Response.Write(str);

  

در این کدها تنها نکته‌ای که احتیاج به توضیح دارد این است که ابتدا یک لیست از رکوردها را از جدول Pictures واکشی می‌کنیم و برای پاس دادن به کلاینت ما آن‌ها را به فرمت Json تبدیل کرده ایم که برای تبدبل از کنابخانه آماده Newtonsoft.Json.dll استفاده کرده ایم 

حال باید با استفاده از jQuery کدهای درون Handler را اجرا کنیم؛ برای همین منظور یک صفحه با نام default.aspx ایجاد کرده و کدهای زیر را درون آن می‌نویسیم

<head runat="server">
    <title>Dynamic SlideShow</title>
    <link href="CSS/orbit-1.2.3.css" rel="stylesheet" type="text/css" />
    <script src="Script/jquery-1.7.1.min.js" type="text/javascript"></script>
    <script src="Script/jquery.orbit-1.2.3.min.js" type="text/javascript"></script>
    <script type="text/javascript">
        $(function () {
            $.ajax({
                url: "Handler.ashx",
                contentType: "application/json; charset=utf-8",
                success: function (data) {
                    $.each(data, function (i, b) {
                        var str = '<img src="' + b.PicturePath + '" alt="' + b.PictureText + '"/>';
                        $("#featured").append(str);
                    });
                    $('#featured').orbit();
                },
                dataType: "json"

            });
            });
    </script>
</head>
<body>
    <form id="form1" runat="server">
    <div id="featured">
    </div>
    </form>
</body>

در اینجا ابتدا با استفاده از متد ajax کتابخانه jQuery کدهای درون Handler را اجرا کرده و به ازای هر المان موجود در جدول یک تگ img به صفحه اضافه می‌کنیم.

بازخوردهای دوره
تزریق وابستگی‌های AutoMapper در لایه سرویس برنامه
ممنون از شما،
یک سوال: بنده کلاس ObjectFactory را همانطور که فرمودید به  این  صورت تغییر دادم. در لایه سرویس نیز این متد را تهیه کرده‌ام:
public IList<AdvertismentViewModel> GetAdvertisementsByMe(int userId)
{
            var adsList = _advertisements.Where(x => x.UserId == userId).ToList();
            var adsViewModel = new List<AdvertismentViewModel>();
            _mappingEngine.Map(source: adsList, destination: adsViewModel);
            return adsViewModel;
}
در متد فوق کلاس Advertisment به کلاس زیر نگاشت داده شده است:
public class AdvertismentViewModel
    {
        public string Image { get; set; }
        public string Title { get; set; }
        public string ExpireDate { get; set; }
    }
اما با فراخوانی متد GetAdvertisementsByMe استثناء AutoMapperMappingException صادر می‌شود:
Missing type map configuration or unsupported mapping.

Mapping types:
Advertisement -> AdvertismentViewModel
Project.DomainClasses.Advertisement -> Project.Models.AdvertismentViewModel

Destination path:
List`1[0]

Source value:
System.Data.Entity.DynamicProxies.Advertisement_E82DFF273E08C95AA785F8F7A0D2B5ABC8E54C4566DFE1C8A92D8D3C447608AE

مطالب
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
مطالب
توسعه برنامه های Cross Platform با Xamarin Forms & Bit Framework - قسمت ششم
در پنج قسمت گذشته به نحوه نصب و راه اندازی Xamarin برای توسعه هر سه پلتفرم Android / iOS / Windows پرداختیم. حال با داشتن محیطی آماده برای کد زدن و تست، می‌توانیم به آموزش کد نویسی بپردازیم.
ابتدا بد نیست نگاهی به ساختار XamApp بیاندازیم. این پروژه‌ی مثال، خود از چهار قسمت تشکیل شده‌است:
XamApp | XamApp.Android | XamApp.UWP | XamApp.iOS
در خود XamApp درصد زیادی (گاهی تا 95%) از کد پروژه پیاده سازی می‌شود که بین سه پلتفرم مشترک است و شامل ظاهر فرم‌ها و منطق برنامه می‌شود. در پاره‌ای از مواقع، لازم می‌شود که از امکانات هر پلتفرم استفاده کنیم و در این صورت به سراغ پروژه‌های Android - iOS - Windows می‌رویم. نه تنها امکانات هر پلتفرم را در همان زبان CSharp و Visual Studio می‌توانید استفاده کنید، بلکه Documentation مربوطه به زبان CSharp برای تک تک امکانات سیستم عامل‌ها وجود دارد و اصلا لازم نیست برای فراگیری نحوه کار AR Kit در iOS به سراغ مستندات Apple بروید و یا برای کار با Intent در Android سری به سایت گوگل بزنید!
برای پیاده سازی UI از XAML استفاده می‌کنیم. XAML خود XML است، به همراه کلی امکانات هیجان انگیز که سعی می‌کنیم بجای ارائه یک توضیح کلی، به مرور و با معرفی امکاناتش، شما را با ماهیت خود XAML آشنا کنیم.
در XAML، همه چیز Tag هست (<>). برای مثال برای ایجاد یک دکمه داریم:
<Button Text="This is a test button" />
در XAML هر تگ، معادل یک کلاس CSharp ای است. در نتیجه ما کلاس Button هم داریم. همچنین Text در مثال بالا به این معنا است که کلاس Button دارای یک Property با نام Text است.

عناصر UI خود به سه دسته تقسیم می‌شوند. Page - Layout - Control
Page‌ها که شامل Content Page - MasterDetail Page - Tabbed Page و ... می‌شوند، صفحات برنامه را تشکیل می‌دهند. Content Page، ساده‌ترین نوع صفحه‌است که کل صفحه نمایش را پر می‌کند و همان طور که از اسمش بر می‌آید، Content ای برای نمایش دارد.

Tabbed Page نیز چندین Tab را نمایش می‌دهد که هر ‌Tab خود یک Content Page است. Carousel Page نیز همانند Tabbed Page است، ولی با Swipe کردن به چپ و راست می‌شود بین صفحات چرخید. هر دوی اینها Multi Page محسوب می‌شوند. MasterDetail نیز این امکان را می‌دهد که از بغل منویی برای Swipe کردن وجود داشته باشد. در نهایت Navigation Page محتوای یک Content Page را نمایش می‌دهد، ولی در بالای آن Navigation Bar دارد؛ شامل دکمه بازگشت به صفحه قبل و Title صفحه جاری و ...


علاوه بر Page ها، Layout‌ها نیز وجود دارند. برای مثال، Stack Layout برای چینش خطی (افقی یا عمودی) استفاده می‌شود. Grid برای ساختار شبکه‌ای استفاده می‌شود و Flex Layout عملکردی مشابه با Flex در وب دارد.

برای مثال، در صورتی که بخواهید چهار دکمه را هم اندازه با هم نمایش دهید، دارید:

    <Grid>
        <Button Text="1" Grid.Row="0" Grid.Column="0" />
        <Button Text="2" Grid.Row="0" Grid.Column="1" />
        <Button Text="3" Grid.Row="1" Grid.Column="0" />
        <Button Text="4" Grid.Row="1" Grid.Column="1" />
    </Grid>


در نهایت کنترل‌ها را داریم. برای مثال Label، Button و ... هر کدام از اینها نقشی را ایفا می‌کنند و امکاناتی دارند.

پس Page داریم، داخل Page از Layout استفاده می‌کنیم برای چینش کلی صفحه و در نهایت از کنترل‌های Image، ListView، Button و ... استفاده می‌کنیم تا ظاهر فرم تکمیل شود.


هر Page علاوه بر ظاهر خود، دارای یک منطق نیز هست. منطق، کاری است که آن فرم انجام می‌دهد. برای مثال فرم لاگین می‌تواند یک Stack Layout عمودی باشد، شامل یک Entry برای گرفتن نام کاربری، یک Entry برای گرفتن رمز عبور، که IsPassword آن True است و در نهایت یک دکمه که برای انجام عمل لاگین است.

در قسمت منطق که با CSharp نوشته می‌شود، ما یک Property از جنس string برای نگه داشتن نام کاربری داریم. یک Property از جنس string برای نگه داشتن رمز عبور و یک Command که عمل لاگین را انجام دهد. Property اول با نام UserName به Text آن Entry اول وصل می‌شود (به اصطلاح Bind می‌شود) و همین طور Property دوم با نام Password نیز به Text آن Entry دوم که IsPassword اش True بود وصل می‌شود و در نهایت Command لاگین به دکمه لاگین وصل می‌شود.


برای زدن ظاهر فرم لاگین، در پروژه XamApp روی فولدر Views راست کلیک نموده و از منوی Add به New Item رفته و Content Page را می‌زنیم. نام آن را LoginView.xaml می‌گذاریم که داخل تگ Content Page خواهیم داشت:

    <StackLayout Orientation="Vertical">
        <Entry Placeholder="User name" Text="{Binding UserName}" />
        <Entry IsPassword="True" Placeholder="Password" Text="{Binding Password}" />
        <Button Command="{Binding LoginCommand}" Text="Login" />
    </StackLayout>

برای زدن منطق، در پروژه XamApp روی فولدر ViewModels راست کلیک نموده و از منوی Add گزینه Class را انتخاب کرده و نام آن را LoginViewModel.cs می‌گذاریم که در داخل آن خواهیم داشت:

public class LoginViewModel : BitViewModelBase
{
        public string UserName { get; set; }

        public string Password { get; set; }

        public BitDelegateCommand LoginCommand { get; set; }

        public LoginViewModel()
        {
            LoginCommand = new BitDelegateCommand(Login);
        }

        public async Task Login()
        {
            // Login implementation ...
        }
}

BitDelegateCommand در این مثال، وظیفه اجرای متد Login را به عهده دارد و آن را اجرا می‌کند؛ زمانیکه کاربر روی دکمه لاگین Click یا Tap کند.


برای این که Content Page جدید، یعنی LoginView به همراه منطق آن، یعنی LoginViewModel در برنامه نشان داده شوند، لازم است با Navigation به آن صفحه برویم. برای این کار، ابتدا باید این زوج را رجیستر کنیم. برای این کار به متد RegisterTypes در کلاس App رفته (زیر فایل App.xaml یک فایل App.xaml.cs است) و خط زیر را به آن اضافه می‌کنیم:

containerRegistry.RegisterForNav<LoginView, LoginViewModel>("Login");

حال در متد OnInitializedAsync در چند خط بالاتر داریم:

await NavigationService.NavigateAsync("/Login", animated: false);

این سطر باعث می‌شود که Navigation Service که همان طور که از اسمش بر می‌آید، کارش Navigation بین صفحات است، صفحه لاگین را باز کند.

هم اکنون پروژه XamApp بروز شده و دارای این مثال است. در صورتی که آن را الآن Clone کنید و یا در صورتی که از قبل گرفته بودید، دستور git pull را برای گرفتن آخرین تغییرات بزنید، می‌توانید این کدها رو داخل پروژه داشته باشید.

برنامه را اجرا کنید و در متد Login، یک Break point بگذارید. سپس برنامه را اجرا کنید. User Name و Password را پر کنید و بر روی دکمه لاگین بزنید. خواهید دید که متد لاگین اجرا می‌شود و User Name و Password با مقادیری که نوشته بودید، پر شده‌اند.

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