مطالب
React 16x - قسمت 6 - کامپوننت‌ها - بخش 3 - یک تمرین
در این قسمت می‌خواهیم دانسته‌های 5 قسمت قبل را در طی یک تمرین کنار هم قرار داده و مرور کنیم.


برپایی ساختار ابتدایی پروژه‌ی تمرین

ابتدا یک پروژه‌ی جدید React را ایجاد می‌کنیم:
> create-react-app sample-05
> cd sample-05
> npm start
سپس بسته‌های بوت استرپ و font-awesome را نیز در آن نصب می‌کنیم:
> npm install --save bootstrap
> npm install --save font-awesome

در ادامه نیاز است فایل‌های CSS این کتابخانه‌ها و قلم‌های وب را import کنیم. به همین جهت ابتدای فایل index.js را به نحو زیر ویرایش خواهیم کرد:
import "bootstrap/dist/css/bootstrap.css";
import "font-awesome/css/font-awesome.css";
در نهایت کار مدیریت این فایل‌ها و قرار دادن آن‌ها در بسته‌ی نهایی برنامه، توسط webpack به صورت خودکار انجام می‌شود.

همچنین به فایل index.css هم مراجعه کرده و یک padding را به بالای صفحه اضافه می‌کنیم؛ تا اطلاعات نمایش داده شده، با کمی فاصله از لبه‌ی مرورگر رندر شوند:
body {
  margin: 0;
  padding: 20px 0 0 0;
  font-family: sans-serif;
}

پس از نصب و import این کتابخانه‌های ثالث، به فایل App.js مراجعه کرده و کلاس container اصلی بوت استرپ را در آن تعریف می‌کنیم تا در برگیرنده‌ی محتوای برنامه شود:
  return (
    <main className="container">
      <h1>Hello world!</h1>
    </main>
  );
همانطور که در قسمت چهارم نیز بحث شد، برای ذکر classهای عناصر در React، از خاصیت className استفاده می‌شود.


معرفی سرویس‌های داده‌ی برنامه

کدهای نهایی این قسمت را از فایل پیوست شده‌ی در انتهای مطلب، می‌توانید دریافت کنید. در اینجا یک پوشه‌ی src\services تعریف شده‌است که داخل آن دو فایل fakeGenreService.js و fakeMovieService.js قرار دارند. این فایل‌ها، منبع داده‌ی درون حافظه‌ای مثال تمرین ما هستند.
سرویس fakeGenre چنین ساختاری را دارد و ژانرهای سینمایی، مانند اکشن، کمدی و غیره در آن لیست شده‌اند:
export const genres = [
  { _id: "5b21ca3eeb7f6fbccd471818", name: "Action" },
  // ...
];
این سرویس دارای متد ()getGenres، برای بازگشت لیست کامل genres است. علت ذکر خاصیت id با یک _، روش نامگذاری خاصیت id در mongo-db است.

و سرویس fakeMovie که دارای ساختار کلی زیر است، لیست 9 فیلم سینمایی را به همراه دارد:
const movies = [
  {
    _id: "5b21ca3eeb7f6fbccd471815",
    title: "Terminator",
    genre: { _id: "5b21ca3eeb7f6fbccd471818", name: "Action" },
    numberInStock: 6,
    dailyRentalRate: 2.5,
    publishDate: "2018-01-03T19:04:28.809Z"
  },

  //...
];
به علاوه این سرویس دارای متدهای ()getMovies برای دریافت لیست فیلم‌ها، getMovie(id) برای بازگشت یک فیلم خاص، saveMovie(movie) برای افزودن یک فیلم جدید به لیست و deleteMovie(id) برای حذف یک فیلم از لیست درون حافظه‌ای سرویس جاری است.


ایجاد کامپوننت Movies برای نمایش لیست فیلم‌ها در برنامه

اکنون می‌خواهیم یک کامپوننت جدید را به نام Movies در فایل جدید src\components\movies.jsx ایجاد کنیم، تا لیست فیلم‌های سرویس fakeMovieService را نمایش دهد. برای اینکار مراحل زیر را طی خواهیم کرد:
- نمایش ساده‌ی لیست فیلم‌ها توسط یک جدول. برای دریافت لیست اشیاء موجود در fakeMovieService، از متد ()getMovies آن می‌توان استفاده کرد.
- اضافه کردن یک دکمه‌ی حذف، به هر ردیف، به نحوی که با کلیک بر روی آن، آن ردیف حذف شود.
- نمایش یک پیام بالای جدول که تعداد فیلم‌های موجود در سرویس درون حافظه‌ای را نمایش می‌دهد. همچنین پس از حذف تمام ردیف‌ها، باید پیام «فیلمی موجود نیست» را نمایش دهد.

خروجی نهایی مثال ما به صورت زیر است:


و اگر تمام آیتم‌های آن‌را حذف کنیم، چنین پیامی نمایش داده می‌شود:


پس از ایجاد فایل خالی جدید movies.jsx در پوشه‌ی جدید components، با استفاده از «simple react snippets» نصب شده‌ی در VSCode، یکبار imrc را تایپ کرده (مخفف import react component است) و سپس دکمه‌ی tab را فشار می‌دهیم، در آخر اینکار را برای cc نیز تکرار می‌کنیم (مخفف create class است) تا importها و سپس ساختار ابتدایی کامپوننت React ما تشکیل شوند. نام این کامپوننت را هم Movies که با حرف بزرگ شروع می‌شود، وارد می‌کنیم.

اکنون مجددا به App.js مراجعه می‌کنیم و بجای Hello world ای که نمایش دادیم، کامپوننت Movies را اضافه می‌کنیم. برای این منظور ابتدا import آن‌را به ابتدای فایل اضافه می‌کنیم:
import Movies from "./components/movies";
سپس متد return آن‌را جهت درج المان کامپوننت Movies اصلاح خواهیم کرد:
return (
    <main className="container">
      <Movies />
    </main>
  );


دریافت لیست اشیاء فیلم‌ها از سرویس fakeMovieService

برای دریافت لیست اشیاء فیلم‌ها، ابتدا تعریف سرویس آن‌را به ابتدای کامپوننت Movies اضافه می‌کنیم:
 import { getMovies } from "../services/fakeMovieService";
در اینجا از {} استفاده شده، چون یک named export را import کرده‌ایم.

سپس خاصیت state را جهت تعریف خاصیت movies که با متد ()getMovies سرویس fakeMovieService مقدار دهی می‌شود، به نحو زیر تکمیل می‌کنیم:
 state = {
    movies: getMovies()
  };
البته این روش مقدار دهی اولیه‌ی خاصیت state، برای دریافت اطلاعات سرویس‌ها، هرچند در اینجا بدون مشکل کار می‌کند، اما بهتر است توسط component life cycle hooks مدیریت شود که در قسمت‌های بعدی بیشتر به جزئیات آن‌ها خواهیم پرداخت.


نمایش لیست فیلم‌ها، به همراه مدیریت حذف هر ردیف

در ادامه، کدهای کامل و تکمیل شده‌ی این کامپوننت را ملاحظه می‌کنید:
import React, { Component } from "react";

import { getMovies } from "../services/fakeMovieService";

class Movies extends Component {
  state = {
    movies: getMovies()
  };

  handleDelete = movie => {
    const movies = this.state.movies.filter(m => m._id !== movie._id);
    this.setState({ movies });
  };

  render() {
    const { length: count } = this.state.movies;

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

    return (
      <React.Fragment>
        <p>Showing {count} movies in the database.</p>
        <table className="table">
          <thead>
            <tr>
              <th>Title</th>
              <th>Genre</th>
              <th>Stock</th>
              <th>Rate</th>
              <th />
            </tr>
          </thead>
          <tbody>
            {this.state.movies.map(movie => (
              <tr key={movie._id}>
                <td>{movie.title}</td>
                <td>{movie.genre.name}</td>
                <td>{movie.numberInStock}</td>
                <td>{movie.dailyRentalRate}</td>
                <td>
                  <button
                    onClick={() => this.handleDelete(movie)}
                    className="btn btn-danger btn-sm"
                  >
                    Delete
                  </button>
                </td>
              </tr>
            ))}
          </tbody>
        </table>
      </React.Fragment>
    );
  }
}

export default Movies;
توضیحات:
همانطور که در ابتدای بحث نیز ذکر شد، هدف از این تمرین، مرور قسمت‌های قبل است و تمام نکات زیر را در قسمت‌های پیشین، با جزئیات بیشتری بررسی کرده‌ایم:

- ابتدا خاصیت state و سپس خاصیت movies شیء منتسب به آن، با لیست فیلم‌های موجود در سرویس مرتبط، مقدار دهی شده‌اند.
- سپس در ابتدای متد render، کار رندر شرطی انجام شده‌است. اگر تعداد فیلم‌های دریافتی صفر بود، پیام «فیلمی در بانک اطلاعاتی موجود نیست» نمایش داده می‌شود و در غیراینصورت، جدول اصلی بوت استرپی برنامه رندر خواهد شد.
در اینجا چون از خاصیت طول آرایه‌ی فیلم‌ها در چندین قسمت قرار است استفاده شود، آن‌را توسط Object Destructuring به یک متغیر نسبت داده‌ایم. همچنین توسط یک نام مستعار هم خاصیت length را با نام جدید count استفاده می‌کنیم.
- در ادامه بازگشت React.Fragment را مشاهده می‌کنید. علت اینجا است که نمی‌خواهیم div اضافه‌تری را در UI رندر کنیم. React.Fragment سبب می‌شود تا بتوانیم چندین فرزند را به المان جاری تبدیل شده‌ی به کدهای جاوا اسکریپتی اضافه کنیم، بدون اینکه خودش به المانی ترجمه شود.
- پس از return، یک () قابل مشاهده‌است. چون خروجی return ما چند سطری است، اگر در سطری که return قرار می‌گیرد، اطلاعاتی درج نشود، موتور جاوا اسکریپت آن‌را با یک سمی‌کالن خاتمه خواهد داد! و دیگر سطرهای بعدی دیده نمی‌شوند و پردازش نخواهند شد. به همین جهت از روش ذکر یک () پس از return در فایل‌های jsx زیاد استفاده می‌شود.
- در ابتدای return، همان خاصیت count را نمایش می‌دهیم.
- سپس کار رندر جدول اصلی برنامه که با کلاس‌های جداول بوت استرپ نیز مزین شده، انجام شده‌است. در React برای عدم تداخل ویژگی class با نام از پیش رزرو شده‌ی class، از خاصیت className برای ذکر کلاس‌های CSSای استفاده می‌شود.
- قسمت thead این جدول مشخص است و سرستو‌ن‌های جدول را مشخص می‌کند.
- پس از آن نیاز است ردیف‌های جدول را رندر کنیم. این‌کار را توسط متد Array.map، با نگاشت هر آیتم آرایه‌ی this.state.movies، به یک tr جدول انجام داده‌ایم.
- React برای اینکه بتواند DOM مجازی خودش را کنترل کند، نیاز دارد عناصر موجود در آن‌را به صورت منحصربفردی تشخص دهد. به همین جهت در اینجا ذکر key را بر روی المان tr که با movie._id مقدار دهی شده‌است، مشاهده می‌کنید.
- رندر مقادیر سلو‌ل‌های ردیف‌ها توسط درج {} و سپس ذکر مقداری از شیء movie دریافتی توسط متد Array.map انجام می‌شود.
- در اینجا ستون رندر دکمه‌ی Delete را نیز مشاهده می‌کنید. برای مدیریت this در آن و دسترسی به شیء movie جاری (ارسال پارامتر به رویداد گردان آن) و همچنین دسترسی به شیء this کلاس جاری برای کار با آرایه‌ی this.state.movies، از روش arrow functions برای تعریف رویدادگردان onClick استفاده کرده‌ایم.
- در متد handleDelete، یک آرایه‌ی جدید را که id ردیف‌های آن با id شیء ردیف انتخابی یکی نیست، بازگشت می‌دهیم. انتساب این آرایه‌ی جدید به آرایه‌ی this.state.movies، تغییری را در برنامه‌های React ایجاد نمی‌کند. در اینجا باید توسط متد this.setState که از کلاس پایه‌ی extends Component دریافت می‌شود، خاصیت movies را بازنویسی کرد تا React از تغییرات مطلع شده و DOM مجازی جدیدی را با مقایسه‌ی با نمونه جدید، محاسبه کرده و به DOM اصلی، جهت به روز رسانی UI اعمال کند.
- البته در اینجا this.setState({ movies }) را بجای this.setState({ movies: movies }) مشاهده می‌کنید. علت اینجا است که اگر عبارات key و value یکی باشند، می‌توان تنها همان عبارت key را جهت حذف تکرار واژه‌ها، ذکر کرد.



کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: sample-05.zip
مطالب دوره‌ها
تزریق خودکار وابستگی‌ها در برنامه‌های ASP.NET MVC
هدف از این قسمت، ارائه راه حلی برای حالت تزریق وابستگی‌ها در سازنده‌های کنترلرهای ASP.NET MVC به صورت خودکار است.
به صورت پیش فرض، ASP.NET MVC به کنترلرهایی نیاز دارد که سازنده آن‌ها فاقد پارامتر باشند. از این جهت که بتواند به صورت خودکار آن‌ها را وهله سازی کرده و مورد استفاده قرار دهد. بنابراین به نظر می‌رسد که در اینجا نیز به همان روش معروف استفاده از الگوی Service locator و تکرار مدام کدهایی مانند ObjectFactory.GetInstance در سراسر برنامه خواهیم رسید که آنچنان مطلوب نیست.
اما ... در ASP.NET MVC می‌توان وهله ساز پیش فرض کنترلر‌ها را با پیاده سازی کلاس DefaultControllerFactory به طور کامل تعویض کرد. یعنی اگر در اینجا بجای وهله ساز پیش فرض، از وهله سازی انجام شده توسط IoC Container خود بتوانیم استفاده کنیم، آنگاه کار تزریق وابستگی‌ها در سازنده‌های کنترلرها نیز خودکار خواهد گردید.
    public class StructureMapControllerFactory : DefaultControllerFactory
    {
        protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
        {
            if (controllerType == null)
                throw new InvalidOperationException(string.Format("Page not found: {0}", requestContext.HttpContext.Request.Url.AbsoluteUri.ToString(CultureInfo.InvariantCulture)));
            return ObjectFactory.GetInstance(controllerType) as Controller;
        }
    }
در کدهای فوق نمونه‌ای از این پیاده سازی را با استفاده از امکانات StructureMap ملاحظه می‌کنید. به این ترتیب در زمان وهله سازی خودکار یک کنترلر، اینبار StructureMap وارد عمل شده و وابستگی‌های برنامه را مطابق تعاریف ObjectFactory.Initialize ذکر شده، به سازنده کلاس کنترلر تزریق می‌کند.
برای استفاده از این ControllerFactory جدید تنها کافی است بنویسیم:
   protected void Application_Start()
  {
     //Set current Controller factory as StructureMapControllerFactory  
     ControllerBuilder.Current.SetControllerFactory(new StructureMapControllerFactory());
  }
و ... همین!
اکنون نوشتن یک چنین کنترلرهایی که سازند‌ه آن‌ها دارای پارامتر است، مجاز خواهد بود و تزریق وابستگی‌ها در سازنده‌ها به صورت خودکار توسط IoC Container مورد استفاده انجام می‌شود.
public partial class LoginController : Controller
{
    readonly IUsersService _usersService;
    public LoginController(IUsersService usersService)
    {
       _usersService = usersService;
    }
بدیهی است سایر مسایل مانند تنظیمات اولیه IoC Container، تهیه لایه سرویس و غیره مانند قبل است و تفاوتی نمی‌کند.


روش دوم تزریق خودکار وابستگی‌ها در برنامه‌های ASP.NET MVC

روش پیاده سازی و تعویض DefaultControllerFactory پیش فرض، متداول‌ترین روش خودکار سازی تزریق وابستگی‌ها در ASP.NET MVC است. روش دیگری نیز بر اساس پیاده سازی اینترفیس توکار IDependencyResolver معرفی شده در ASP.NET MVC 3.0 به بعد، وجود دارد. این روش علاوه بر ASP.NET MVC در کنترلرهای مخصوص Web API نیز کاربرد دارد. حتی SignalR نیز دارای کلاس پایه‌ای به نام DefaultDependencyResolver با امضای مشابه IDependencyResolver است.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.Mvc;
using StructureMap;

namespace Prog
{
    public class StructureMapDependencyResolver : IDependencyResolver
    {
        public object GetService(Type serviceType)
        {
            if (serviceType.IsAbstract || serviceType.IsInterface || !serviceType.IsClass)
                return ObjectFactory.TryGetInstance(serviceType);
            return ObjectFactory.GetInstance(serviceType);
        }

        public IEnumerable<object> GetServices(Type serviceType)
        {
            return ObjectFactory.GetAllInstances(serviceType).Cast<object>();
        }
    }
}
یک نمونه از پیاده سازی آن‌را به کمک StructureMap در اینجا ملاحظه می‌کنید. برای ثبت آن در برنامه خواهیم داشت:
protected void Application_Start()
{
   DependencyResolver.SetResolver(new StructureMapDependencyResolver());
}
در Web API باید GlobalConfiguration.Configuration.DependencyResolver تنظیم شود. البته IDependencyResolver آن در فضای نام دیگری به نام System.Web.Http.Dependencies قرار گرفته است؛ اما کلیات آن تفاوتی نمی‌کند. نمونه‌ی نهایی و تکمیل شده‌ی آن‌را در اینجا می‌توانید مطالعه کنید: «تزریق خودکار وابستگی‌ها در ASP.NET Web API به همراه رها سازی خودکار منابع IDisposable »   

دریافت مثال کامل بحث جاری:
DI06.zip
مطالب
تزریق وابستگی‌ها در ASP.NET Core - بخش 3 - ثبت و واکشی تنظیمات
همانطور که پیشتر گفتیم، Dependency Injection Container، ماژول اصلی ASP.NET Core است. تقریبا تمامی ماژول‌ها و سرویس‌های ASP.NET Core از DI Container Injection استفاده می‌کنند که بعضی از آنها عبارتند از:
  •   Configuration
  •   Routing
  •   MVC
  •   Application
  • و ...
بصورت درونی، چارچوب/ فریم ورک ASP.NET Core، مسئول ارائه‌ی وابستگی‌ها، در زمان فعال سازی ماژول‌های خود فریم ورک ASP.NET Core می‌باشد.
فرض کنید یک درخواست برای صفحه‌ی اول سایت به وبسایتی بر پایه‌ی ASP.NET Core می‌رسد. به صورت گام به گام، این مراحل برای پردازش داده به کار می‌روند:
  1. کاربر یک درخواست Http را توسط مرورگر ارسال می‌کند.
  2. یکی از اولین میان افزار‌ها یعنی میان افزار Routing، آدرس درخواست را می‌خواند، کنترلر و اکشن مورد نظر را می‌یابد و به‌وسیله‌ی Activator Utility، سعی در فعال سازی آن کنترلر می‌کند. 
  3.   DI Container لیست پارامترهای سازنده‌ی کنترلر را مشاهده می‌کند و سرویس‌های مورد نیاز را از درون خود واکشی کرده، از آنها نمونه سازی می‌کند و نمونه‌های ساخته شده را  به درون شیء کنترلر تزریق می‌کند.
  4.  Routing درخواست HttpRequest را تجزیه کرده و اکشن متد مورد نظر را برای اجرای آن فراخوانی کرده
  5. و نتیجه‌ی اجرای اکشن را به درخواست دهنده بر می‌گرداند.

هر چند که کنترلرها درون DI Container ثبت نشده‌اند، ولی توسط کلاس‌هایی درون فریم ورک، از آنها نمونه سازی می‌شود و در حین نمونه سازی، DI Container سرویس‌های مورد نظر آن‌ها را در صورت وجود، فراهم می‌کند.

ثبت تنظیمات وبسایت و فراخوانی آنها در برنامه
در تمام برنامه‌های ASP.NET Core شما نیاز به تنظیماتی برای پیکربندی کار برنامه‌ی خود دارید. این تنظیمات می‌توانند شامل Connection String اتصال به پایگاه داده، تنظیمات اتصال به سرویس‌های خارجی مثل درگاه‌های پرداخت آنلاین بانک‌ها و ... باشند. در اینجا ما تنظیمات اختصاصی را درون فایل AppSetting اضافه می‌کنیم. بعد برای هر بخش از تنظیمات، در پوشه‌ی Configs یک کلاس ساده‌ی سی شارپ را می‌سازیم  و سپس با گرفتن و تزریق کردن این فایل‌های Config درون DI Container، هر زمانی خواستیم، از آنها استفاده می‌کنیم.
ابتدا به سراغ تنظیمات کلی می‌رویم و دو تنظیم نام برنامه و پیغام خوش آمد گویی را به برنامه اضافه می‌کنیم (فایل appSettings را به صورت زیر تغییر می‌دهیم) :
"ApplicationName": "Dependency Injection Demo",
"GreetingMessage": "Welcome to Dependency Injection Demo",
"AllowedHosts": "*",

"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},

برای سادگی کار، با بخش Logging کاری نداریم . اکنون فایل AppConfig.cs را به برنامه اضافه می‌کنیم:

namespace AspNetCoreDependencyInjection.Configs
{
    public class AppConfig
    {
        public string ApplicationName { get; set; }
        public string GreetingMessage { get; set; }
        public string AllowedHosts { get; set; }
    }
}

برای دسترسی بهتر می‌توانیم سازنده‌ی کلاس Startup را تغییر دهیم:

public IWebHostEnvironment Environment { get; }
public IConfiguration Configuration { get; }
public IServiceCollection Services { get; set; }

public Startup(IWebHostEnvironment environment)
{
var builder = new ConfigurationBuilder()
        .SetBasePath(environment.ContentRootPath)
        .AddJsonFile("appsettings.json", optional: true)
        .AddEnvironmentVariables();
        this.Environment = environment;
        this.Configuration = builder.Build();
}
کد بالا برای زمانی کاربرد دارد که شما بخواهید چند تنظیمات مختلف را در برنامه داشته باشید؛ مثلا در کد بالا در هنگام ساخت متغیر builder، می‌توانید با چک کردن متغیر environment، یک تنظیمات دیگر را داشته باشید (داشتن دو یا چند تنظیمات به خصوص برای زمان  توسعه و انتشار برنامه ضروری است. در ساده‌ترین کاربرد، شما در حالت توسعه به یک پایگاه داده تست وصل می‌شوید، ولی در حالت انتشار به پایگاه داده‌ی اصلی متصل خواهید شد). در اینجا یکی از  ساده‌ترین روش‌ها، استفاده از دو فایل تنظیمات مختلف برای زمان انتشار و غیر انتشار ( توسعه و Staging ) است:
var appSettingsFile = environment.IsProduction() ? "appsettings.json" : "appsettings_dev.json";
var builder = new ConfigurationBuilder()
.SetBasePath(environment.ContentRootPath)
                .AddJsonFile( appSettingsFile , optional: true)
                .AddEnvironmentVariables();
حالا که این تغییرات را انجام دادیم، دوباره به سراغ ثبت سرویس تنظیمات برنامه می‌رویم. برای اینکار در متد ConfigureServices و زیر خط‌های کد قبلی، این خطوط کد را اضافه می‌کنیم: 
services.AddSingleton(services => new AppConfig { 
    ApplicationName = this.Configuration["ApplicationName"],
    GreetingMessage = this.Configuration["GreetingMessage"],
    AllowedHosts = this.Configuration["AllowedHosts"]
});

در کد بالا در هنگام اجرای برنامه، یک نمونه از کلاس AppConfig را با طول حیات Singleton ثبت کردیم و Property ‌های این شیء را به وسیله‌ی ایندکس Configuration[“FieldName”]، تک تک پر کردیم.

حالا می‌توانیم سرویس AppConfig را در هر کلاسی از برنامه‌ی خودمان تزریق و از آن استفاده کنیم. برای مثل در اینجا یک کنترلر به نام AppSettingsController ساختم و کلاس فوق را به آن تزریق کردم: 

public class AppSettingsController : Controller
{
        private readonly AppConfig _appConfig;
        public AppSettingsController(AppConfig appConfig)
        {
            _appConfig = appConfig;
        } 
 // codes here …
}

می توانیم از همین الگو برای تعریف، ثبت و استفاده از سایر تنظیمات نیز استفاده کنیم:
"UserOptionConfig": {
    "UsersAvatarsFolder": "avatars",
    "UserDefaultPhoto": "icon-user-default.png",
    "UserAvatarImageOptions": {
         "MaxWidth": 150,
         "MaxHeight": 150
    }
},

"LiteDbConfig": {
   "ConnectionString": "Filename=\\Data\\DependencyInjectionDemo.db;Connection=direct;Password=@123456;"
}

برای LiteDbConfig مانند AppConfig عمل می‌کنیم، ولی در هنگام ثبت آن، به روش زیر عمل می‌کنیم. تنها تفاوتی که وجود دارد، نحوه‌ی دستیابی به فیلدهای درونی فایل JSON به وسیله‌ی شیء Configuration است: 

services.AddSingleton(services => new LiteDbConfig
{
    ConnectionString = this.Configuration["LiteDbConfig:ConnectionString"],
});

اکنون برای استفاده‌ی از مدخل UserOptionConfig، کلاس‌های زیر را می‌سازیم:

namespace AspNetCoreDependencyInjection.Configs
{
    public class UserOptionConfig
    {
        public string UsersAvatarsFolder { get; set; }
        public string UserDefaultPhoto { get; set; }
        public UserAvatarImageOptions UserAvatarImageOptions { get; set; }
    }

    public class UserAvatarImageOptions
    {
        public int MaxHeight { get; set; }
        public int MaxWidth { get; set; }
    }
}
می‌خواهیم روش Option Pattern را که روش توصیه  شده‌ی Microsoft برای استفاده از پیکربندی برنامه است، بکار ببریم. به صورت خلاصه، Option Pattern بیان می‌کند که بخش‌های مختلف پیکربندی تنظیمات برنامه را از یکدیگر جدا کنیم و به ازای هر بخش، کلاس‌های مختص به خود را داشته باشیم و با ثبت جداگانه‌ی آنها در DI Container ، از  آن‌ها استفاده کنیم.

جداسازی بخش‌های مختلف تنظیمات پیکربندی باعث می‌شود تا بتوانیم دو اصل اساسی از طراحی نرم افزار را رعایت کنیم :

  • Interface Segregation Principle (ISP) or Encapsulation : کلاس‌هایی که به تنظیمات نیاز دارند، فقط به آن بخشی از تنظیمات دسترسی خواهند داشتند که واقعا مورد نیازشان باشد.
  •   Separation Of Concerns : تنظیمات بخش‌های مختلف برنامه، به یکدیگر وابسته و  جفت شده نیستند.

در اینجا  نیاز به استفاده از پکیج Microsoft.Extensions.Options.ConfigurationExtensions را داریم که به صورت درونی در ASP.NET Core تعبیه شده است.

برای ثبت این تنظیمات درون DI Container، از نمونه‌ی جنریک متد Configure در IServiceCollection به صورت زیر استفاده می‌کنیم:

services.Configure<UserOptionConfig>(this.Configuration.GetSection("UserOptionConfig"));

متد GetSection بر اساس نام بخش تنظیمات، خود آن تنظیم و تمامی تنظیمات درونی آن را به صورت یک IConfigurationSection بر می‌گرداند و متد Configure<TOption> یک IConfiguration را گرفته و به صورت خودکار به TOption اتصال می‌دهد و سپس این شیء را درون DI Container به عنوان یک IConfigurationOptions<TOption> و با طول حیات Singleton ثبت می‌کند.

برای دسترس به UserOptionConfig درون کلاس مورد نظر ما، اینترفیس <IOptionMonitor<TOption را به سازنده‌ی کلاس مورد نظر تزریق می‌کنیم. کد زیر را که نسخه‌ی تغییر یافته‌ی کلاس AppSettingsController است را مشاهده کنید: 
private readonly LiteDbConfig _liteDbConfig;
private readonly AppConfig _appConfig;
private readonly UserOptionConfig _userOptionConfig; 

public AppSettingsController(AppConfig appConfig ,
    LiteDbConfig liteDbConfig ,
    IOptionsMonitor<UserOptionConfig> userOptionConfig)
{
    _appConfig = appConfig;
    _liteDbConfig = liteDbConfig;
    _userOptionConfig = userOptionConfig.CurrentValue;
}
در اینجا و در سازنده برای گرفتن TOption ، از CurrentValue که یک property تعریف شده‌ی درون IOptionsMonitor<TOption> است، استفاده می‌کنیم.

نکته ای که وجود دارد، کلاس‌های تعریف شده برای استفاده‌ی از این الگو باید شرایط زیر را داشته باشند ( مثل کلاس UserOptionConfig ) :

  • باید سطح دسترسی public داشته باشند.
  • باید دارای سازنده‌ی پیش فرض باشند.
  •   باید نام Property ‌های آنها دقیقا همنام فیلدهای تنظیمات باشد تا فرایند mapping خودکار به درستی انجام شود.
  •   باید Property ها و Setter آنها ، سطح دسترسی public داشته باشند.

هر دو روش بالا که یکی به صورت عادی تنظیمات را ثبت می‌کند و دیگری با استفاده از Option Pattern بخش‌های مختلف را ثبت می‌کند، مناسب هستند. البته گاهی اوقات فایل‌های تنظیمات پروژه‌ی شما در لایه‌های زیرین (یا درونی‌تر اگر از onion architecture استفاده می‌کنید) قرار دارند و شما نمی‌خواهید در آن لایه‌ها و لایه‌های درونی‌تر، وابستگی به پکیج‌های ASP.NET Core ایجاد کنید. در این حالت با در نظر گرفتن دو اصل ISP و Separation of Concerns ، به ازای هر بخش مختلف از تنظیمات، فایل‌های تنظیمات را در لایه‌های زیرین/درونی تعریف کرده، بعد در لایه‌های بالاتر/بیرونی‌تر آنها را به درون سرویس‌ها یا کلاس‌های مورد نیاز، تزریق کنید. البته مثل همین مثال، ثبت این سرویس‌ها درون برنامه‌ی ASP.NET Core که معمولا بالاترین/بیرونی‌ترین لایه از پروژه‌ی ما هست، انجام می‌شود.

مطالب
تبدیل فایل‌های pfx به snk
مرسوم است و توصیه شده است که جهت ارائه کتابخانه‌های دات نتی خود از امضای دیجیتال استفاده کنید. VS.NET برای این منظور در برگه signing خواص یک پروژه، چنین امکانی را به صورت توکار ارائه می‌دهد.
حال اگر بخواهیم همین پروژه را به صورت سورس باز ارائه دهیم، استفاده کنندگان نهایی به مشکل برخواهند خورد؛ زیرا فایل pfx حاصل، توسط کلمه عبور محافظت می‌شود و در سایر سیستم‌ها بدون درنظر گرفتن این ملاحظات قابل استفاده نخواهد بود.
معادل فایل‌های pfx، فایلهایی هستند با پسوند snk که تنها تفاوت مهم آن‌ها با فایل‌های pfx، عدم محافظت توسط کلمه عبور است و ... برای کارهای خصوصا سورس باز انتخاب مناسبی به شمار می‌روند. اگر دقت کنید، اکثر پروژه‌های سورس باز دات نتی موجود در وب (مانند NHibernate، لوسین، iTextSharp و غیره) از فایل‌های snk برای اضافه کردن امضای دیجیتال به کتابخانه نهایی تولیدی استفاده می‌کنند و نه فایل‌های pfx محافظت شده.
در اینجا اگر فایل pfx ایی دارید و می‌خواهید معادل snk آن‌را تولید کنید، قطعه کد زیر چنین امکانی را مهیا می‌سازد:
using System.IO;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;

namespace PfxToSnk
{
    class Program
    {
        /// <summary>
        /// Converts .pfx file to .snk file.
        /// </summary>
        /// <param name="pfxData">.pfx file data.</param>
        /// <param name="pfxPassword">.pfx file password.</param>
        /// <returns>.snk file data.</returns>
        public static byte[] Pfx2Snk(byte[] pfxData, string pfxPassword)
        {
            var cert = new X509Certificate2(pfxData, pfxPassword, X509KeyStorageFlags.Exportable);
            var privateKey = (RSACryptoServiceProvider)cert.PrivateKey;
            return privateKey.ExportCspBlob(true);
        }

        static void Main(string[] args)
        {
            var pfxFileData = File.ReadAllBytes(@"D:\Key.pfx");
            var snkFileData = Pfx2Snk(pfxFileData, "my-pass");
            File.WriteAllBytes(@"D:\Key.snk", snkFileData);
        }
    }
}
 
مطالب
چگونه نرم افزارهای تحت وب سریعتری داشته باشیم؟ قسمت دوم
قسمت اول 

4. فشرده سازی HTTP را فعال کنید
اطمینان حاصل کنید که HTTP Compression در تمامی بخش‌های اصلی برنامه شما فعال است. حداقل کاری که می‌توانید در این رابطه بکنید این است که خروجی HTML که توسط برنامه شما تولید می‌شود را فشرده سازی کنید. جهت فعال سازی فشرده سازی در برنامه خود بهتر است در اولویت اول از ماژول ویژه ای که جهت این کار در IIS در نظر گرفته شده استفاده کنید. این ماژول تمامی کارها را به صورت خودکار برای شما انجام می‌دهد. اگر دسترسی به IIS جهت فعال سازی آن را ندارید، می‌توانید از ماژول‌های ASP.NET که جهت این کار تهیه شده استفاده کنید. می‌توانید کمی جستجو کنید و یا خودتان یکی تهیه کنید!

5.تنظیم CacheControlMaxAge
مقدار CacheControlMaxAge را در فایل web.config را طوری تنظیم کنید تا هیچ کاربری هیچ فایل static را دیگر درخواست نکند. مثلا می‌توانید این مقدار را بر روی چند ماه تنظیم کنید و البته فراموش نکنید این مقدار را در صفحات پویای خود بازنویسی (override) کنید تا مشکلی در رابطه با کش شدن فرم‌های اصلی برنامه (همانطور که در نکته اول بخش اول ذکر شد) پدید نیاید. البته کش کردن فایل‌های استاتیک برنامه بار مالی نیز برای شما و کاربرانتان خواهد داشت. دیگر هزینه پهنای باند اضافی جهت دانلود این فایل‌ها در هر درخواست برای شما (در سمت سرور) و کاربرانتان (در سمت کاربر) پرداخت نخواهد شد!

6. استفاده از OutputCache 
اگر از MVC استفاده می‌کنید، فراموش نکنید که از OutputCache در کنترل‌های MVC استفاده نمایید. اگر سرور شما بتواند اطلاعات را از رم خود بازیابی کند بهتر از آن است که آن را مجدد از دیتابیس واکشی نماید و عملیاتی نیز بر روی آن انجام دهد. البته مدیریت حافظه .NET به صورت خودکار کمبود حافظه را مدیریت کرده و از نشت حافظه جلوگیری خواهد کرد. برای توضیحات بیشتر در این رابطه می‌توانید از این مقاله کمک بگیرید.

7. بهره برداری از ORM Profiler
ORM Profiler ها تمامی فعالیت‌های ORM تحت نظر گرفته، دستورات T-SQL ارسالی به بانک اطلاعاتی را واکشی کرده و برای شما نمایش می‌دهند. تعدادی از آنها نیز این دستورات را آنالیز کرده پیشنهاداتی در رابطه با بهبود کارایی به شما ارائه می‌دهند. برای مثال به جای اینکه شما 2000 رکورد را یکی یکی از بانک بازیابی کنید، می‌توانید آن را به صورت یک query به بانک ارسال کنید. این موضوع به سادگی توسط ORM Profiler‌ها قابل بررسی است. نمونه ای از این نرم افزارها را می‌توانید در این سایت یا این سایت   پیدا کنید. البته در صورتی که نمی‌خواهید از نرم افزارهای جانبی استفاده کنید، می‌توانید از ابزارهای توکار بانک‌های اطلاعاتی مانند SQL Profiler نیز استفاده کنید (راهنمایی).
نظرات مطالب
ارتقاء به ASP.NET Core 1.0 - قسمت 19 - بومی سازی
با سلام؛ فایل‌ها رو در یک پروژه جدید اینطور تغییر دادم:
            _stringLocalizer = stringLocalizerFactory.Create(
                baseName: "Project3.Core.MetadataCore.TagsService.en.resx" ,
                location: "Project.Apps.Office");
این خط رو به پروژه اضافه کردم : 
string TNTNT= _stringLocalizer["Title"].Value;
و آدرسی که _stringLocalizer  بهش اشاره می‌کنه اینه: 
"Project.Apps.Office.Resources.Project.Core.MetadataCore.TagsService.en.resx"
ولی مقداری که برای Title در نظر گرفتم رو بر نمی‌گردونه. 
مطالب
چرا در سازمان‌ها برنامه‌های وب جایگزین برنامه‌های دسکتاپ شده‌اند (یا می‌شوند)؟

- برنامه‌های وب نیازی به نصب بر روی تک تک کلاینت‌ها و همچنین به روز رسانی مداوم کلاینت‌ها را ندارند. به این صورت مدیریت چند صد کاربر در یک سازمان ساده‌تر از قبل خواهد بود. دیگر نگران این نخواهید بود که آیا فلان کاربر آخرین به روز رسانی‌ها را نصب کرده (دریافت کرده) یا خیر.
- امکان دسترسی از راه دور، برای مثال از طریق اینترنت یا VPN یا RRAS و خطوط دایال آپ (برای مثال دسترسی ساده‌تر دفاتر مختلف یک سازمان به اطلاعات یکدیگر یا امکان داشتن کارکنانی که از راه دور برای شما کار می‌کنند).
- امکان ذخیره سازی داده‌ها در سازمانی دیگر (هاست کردن این برنامه‌ها در محیط‌های ابری(!) (cloud computing) هزینه‌های تهیه و نگهداری سخت افزارهای یک سازمان را نیز کاهش می‌دهند).
- کاهش هزینه‌های سازمان با توجه به اینکه اگر از سرورهای قدرتمندی استفاده شود؛ از یک برنامه‌ی وب چندین هزار یا چند میلیون کاربر می‌توانند استفاده کنند بدون اینکه نگران تامین هزینه مجوز استفاده از برنامه‌ی تهیه شده به ازای هر کاربر باشید.
- امکان دسترسی به برنامه‌ی وب تهیه شده در انواع و اقسام سیستم عامل‌هایی که تنها مجهز به یک مرورگر وب هستند (نتیجه نهایی قابل استفاده مستقل از سکو است). برای مثال این‌ روزها به کمک Adobe AIR ، Silverlight و یا کتابخانه‌های اسکریپتی مانند jQuery و ASP.Net Ajax، بسیاری از توانایی‌های نمایشی برنامه‌های دسکتاپ را در وب نیز می‌توان شاهد بود با این خصوصیت که نتیجه‌ی نهایی مستقل از سکو است.
- در این حالت کلاینت‌ها نیازی به داشتن سخت افزارهای قوی ندارند (که در کاهش هزینه‌های یک سازمان مؤثر است). همچنین این برنامه‌ها مشکلات ناسازگاری با سخت افزارها و نگارش‌های مختلف سیستم عامل‌ها را نیز ندارند. بنابراین یک سازمان می‌تواند بودجه‌ی خود را صرف تهیه‌ی سرورهای بهتری کند.
- کلاینت‌ها با توجه به محدود بودن دسترسی‌های امنیتی اعمالی توسط مرورگرها، امنیت بیشتری خواهند داشت. به همین ترتیب کاربران برای استفاده از این برنامه‌ها نیز نیازی به دسترسی بالا در یک سازمان برای اجرای مرورگر خود نخواهند داشت (کمتر جمله‌ی "من دسترسی ادمین می‌خواهم" را خواهید شنید).
- امکان مونیتور کردن ساده‌تر فعالیت کاربران در برنامه.
- در صورت محافظت از سرور، کدهای شما از خطر دزدیده شدن مصون‌(تر) هستند.
- مدیریت ساده‌تر و مجتمع اطلاعات تولیدی با توجه به اینکه همه چیز باید بر روی سرور ذخیره شود. به این صورت مدیریت نقل مکان کاربران از یک کامپیوتر به کامپیوتری دیگر نیز ساده‌تر می شود؛ زیرا چیزی را قرار نیست جابجا کنند (نه اطلاعات و نه برنامه ‌را). اگر یکی از کامپیوترهای کلاینت‌ها قابل استفاده نباشد، به سادگی می‌تواند از کامپیوتری دیگر در شبکه استفاده کند، بدون اینکه معطل تیم فنی شود تا برنامه‌ای را برای او نصب و راه اندازی کنند. به علاوه تهیه پشتیبان از اطلاعات سرورها نیز همیشه ساده‌تر است از تهیه پشتیبان از 100 ها کامپیوتر موجود در شبکه.
- اگر خروجی برنامه‌ی وب شما تنها از صفحات وب و جاوا اسکریپت تشکیل شده باشد، امکان دسترسی آن در دستگاه‌های موبایل به سادگی میسر است.

مطالب
اصلاح Urlها در فایل‌های PDF با استفاده از iTextSharp
نحوه ایجاد لینک در فایل‌های PDF به کمک iTextSharp

حداقل دو نوع لینک را در فایل‌های PDF می‌توان ایجاد کرد:
الف) لینک به منابع خارجی؛ مانند یک وب سایت
ب) لینک به صفحه‌ای داخل فایل PDF
در ادامه مثالی را مشاهده خواهید نمود که شامل هر دو نوع لینک است:
        void WriteFile()
        {
            using (var doc = new Document(PageSize.LETTER))
            {
                using (var fs = new FileStream("test.pdf", FileMode.Create))
                {
                    using (var writer = PdfWriter.GetInstance(doc, fs))
                    {
                        doc.Open();
                        var blueFont = FontFactory.GetFont("Arial", 12, Font.NORMAL, BaseColor.BLUE);
                        doc.Add(new Chunk("Go to URL", blueFont).SetAction(new PdfAction("http://www.google.com/", false)));

                        doc.NewPage();
                        doc.Add(new Chunk("Go to Test", blueFont).SetLocalGoto("entry1"));

                        doc.NewPage();
                        doc.Add(new Chunk("Test").SetLocalDestination("entry1"));

                        doc.Close();
                    }
                }
            }
        }
حاصل این مثال، یک فایل PDF است با سه صفحه. در صفحه اول لینکی به سایت Google وجود دارد. در صفحه دوم، لینکی به صفحه سوم تهیه شده است.
در صفحه سوم یک Local Destination تعبیه شده است. در صفحه دوم به کمک یک Local Goto، لینکی به این مقصد داخلی ایجاد خواهد شد.


اصلاح لینک‌ها در فایل‌های PDF

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




در تصویر اول نحوه ذخیره شدن named destinationها را در یک فایل PDF مشاهده می‌کنید.
در تصویر دوم، ساختار دو نوع لینک تعریف شده در صفحات، مشخص هستند. یکی بر اساس Uri کار می‌کند و دیگری بر اساس GoTo.
کاری را که در ادامه قصد داریم انجام دهیم، تبدیل حالت Uri به GoTo است. برای مثال، در ادامه می‌خواهیم لینک مثال فوق را ویرایش کرده و آن‌را تبدیل به لینکی نمائیم که به entry1 اشاره می‌کند. کدهای انجام اینکار را در ادامه ملاحظه می‌کنید:
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using iTextSharp.text.pdf;

namespace ReplaceLinks
{
    public class ReplacePdfLinks
    {
        Dictionary<string, PdfObject> _namedDestinations;
        PdfReader _reader;

        public string InputPdf { set; get; }
        public string OutputPdf { set; get; }
        public Func<Uri, string> UriToNamedDestination { set; get; }

        public void Start()
        {
            updatePdfLinks();
            saveChanges();
        }

        private PdfArray getAnnotationsOfCurrentPage(int pageNumber)
        {
            var pageDictionary = _reader.GetPageN(pageNumber);
            var annotations = pageDictionary.GetAsArray(PdfName.ANNOTS);
            return annotations;
        }

        private static bool hasAction(PdfDictionary annotationDictionary)
        {
            return annotationDictionary.Get(PdfName.SUBTYPE).Equals(PdfName.LINK);
        }

        private static bool isUriAction(PdfDictionary annotationAction)
        {
            return annotationAction.Get(PdfName.S).Equals(PdfName.URI);
        }

        private void replaceUriWithLocalDestination(PdfDictionary annotationAction)
        {
            var uri = annotationAction.Get(PdfName.URI) as PdfString;
            if (uri == null)
                return;

            if (string.IsNullOrWhiteSpace(uri.ToString()))
                return;

            var namedDestination = UriToNamedDestination(new Uri(uri.ToString()));
            if (string.IsNullOrWhiteSpace(namedDestination))
                return;

            PdfObject entry;
            if (!_namedDestinations.TryGetValue(namedDestination, out entry))
                return;

            annotationAction.Remove(PdfName.S);
            annotationAction.Remove(PdfName.URI);

            var newLocalDestination = new PdfArray();
            annotationAction.Put(PdfName.S, PdfName.GOTO);
            var xRef = ((PdfArray)entry).First(x => x is PdfIndirectReference);
            newLocalDestination.Add(xRef);
            newLocalDestination.Add(PdfName.FITH);
            annotationAction.Put(PdfName.D, newLocalDestination);
        }

        private void saveChanges()
        {
            using (var fileStream = new FileStream(OutputPdf, FileMode.Create, FileAccess.Write, FileShare.None))
            using (var stamper = new PdfStamper(_reader, fileStream))
            {
                stamper.Close();
            }
        }

        private void updatePdfLinks()
        {
            _reader = new PdfReader(InputPdf);
            _namedDestinations = _reader.GetNamedDestinationFromStrings();

            var pageCount = _reader.NumberOfPages;
            for (var i = 1; i <= pageCount; i++)
            {
                var annotations = getAnnotationsOfCurrentPage(i);
                if (annotations == null || !annotations.Any())
                    continue;

                foreach (var annotation in annotations.ArrayList)
                {
                    var annotationDictionary = (PdfDictionary)PdfReader.GetPdfObject(annotation);

                    if (!hasAction(annotationDictionary))
                        continue;

                    var annotationAction = annotationDictionary.Get(PdfName.A) as PdfDictionary;
                    if (annotationAction == null)
                        continue;

                    if (!isUriAction(annotationAction))
                        continue;

                    replaceUriWithLocalDestination(annotationAction);
                }
            }
        }
    }
}
توضیح این کدها بدون ارجاع به تصاویر ارائه شده میسر نیست. کار از متد updatePdfLinks شروع می‌شود. با استفاده از متد GetNamedDestinationFromStrings به کلیه named destinationهای تعریف شده دسترسی خواهیم داشت (تصویر اول). در ادامه Annotations هر صفحه دریافت می‌شوند. اگر به تصویر دوم دقت کنید، به ازای هر صفحه یک سری Annot وجود دارد. داخل اشیاء Annotations، لینک‌ها قرار می‌گیرند. در ادامه این لینک‌ها استخراج شده و تنها مواردی که دارای Uri هستند بررسی خواهند شد.
کار تغییر ساختار PDF در متد replaceUriWithLocalDestination انجام می‌شود. در اینجا آدرس استخراجی به استفاده کننده ارجاع شده و named destination مناسبی دریافت می‌شود. اگر این «مقصد نام دار» در مجموعه مقاصد نام دار PDF جاری وجود داشت، خواص لینک قبلی مانند Uri آن حذف شده و با GoTo به آدرس این مقصد جدید جایگزین می‌شود.
در آخر، توسط یک PdfStamper، اطلاعات تغییر کرده را در فایلی جدید ثبت خواهیم کرد.

یک نمونه از استفاده از کلاس فوق به شرح زیر است:
            new ReplacePdfLinks
            {
                InputPdf = @"test.pdf",
                OutputPdf = "mod.pdf",
                UriToNamedDestination = uri =>
                {
                    if (uri.Host.ToLowerInvariant().Contains("google.com"))
                    {
                        return "entry1";
                    }

                    return string.Empty;
                }
            }.Start();
در این مثال، اگر لینکی به آدرس Google.com اشاره کند، ویرایش شده و اینبار به مقصدی داخلی به نام entry1 ختم خواهد شد.

چند نکته تکمیلی
- اگر قصد داشته باشیم تا لینکی را ویرایش کرده اما تنها Uri آن‌را تغییر دهیم، تنها کافی است URI آن‌را به نحو زیر در متد replaceUriWithLocalDestination ویرایش کنیم:
annotationAction.Put(PdfName.URI, new PdfString("http://www.bing.com/"));
- اگر بجای یک مقصد نام دار، تنها قرار است لینک موجود، به صفحه‌ای مشخص اشاره کند، تغییرات متد replaceUriWithLocalDestination به نحو زیر خواهد بود:
newLocalDestination.Add((PdfObject)_reader.GetPageOrigRef(pageNum: 2));
RemovePdfLinks.7z
مطالب
مراحل ارسال یک پروژه‌ی Visual Studio به GitHub
از نگارش 2012 ویژوال استودیو، امکان کار با مخازن Git، به صورت یکپارچه و توکار و بدون نیاز به ابزارهای جانبی، توسط آن فراهم شده‌است. در ادامه قصد داریم به کمک این ویژگی توکار، نحوه‌ی ارسال یک پروژه‌ی از پیش موجود VS.NET را برای اولین بار به GitHub بررسی کنیم.


تنظیمات مقدماتی GitHub

در ابتدا نیاز است یک مخزن کد خالی را در GitHub ایجاد کنید. برای این منظور به برگه‌ی Repositories در اکانت GitHub خود مراجعه کرده و بر روی دکمه‌ی New کلیک کنید:


سپس در صفحه‌ی بعدی، نام پروژه را به همراه توضیحاتی وارد نمائید و بر روی دکمه‌ی Create repository کلیک کنید. در اینجا سایر گزینه‌ها را انتخاب نکنید. نیازی به انتخاب گزینه‌ی READ ME و یا انتخاب مجوز و غیره نیست. تمام این کارها را در سمت پروژه‌ی اصلی می‌توان انجام داد و یا VS.NET فایل‌های ignore را به صورت خودکار ایجاد می‌کند. در اینجا صرفا هدف، ایجاد یک مخزن کد خالی است.


از اطلاعات صفحه‌ی بعدی، تنها به آدرس مخصوص GitHub آن نیاز داریم. از این آدرس در VS.NET برای ارسال اطلاعات به سرور استفاده خواهیم کرد:



تنظیمات VS.NET برای ارسال پروژه به مخزن GitHub

پس از ایجاد یک مخزن کد خالی در GitHub، اکنون می‌توانیم پروژه‌ی خود را به آن ارسال کنیم. برای این منظور از منوی File، گزینه‌ی Add to source control را انتخاب کنید و در صفحه‌ی باز شده، گزینه‌ی Git را انتخاب نمائید:



سپس در کنار برگه‌ی Solution Explorer، برگه‌ی Team Explorer را انتخاب کنید. در اینجا بر روی دکمه‌ی Home در نوار ابزار آن کلیک کرده و سپس بر روی دکمه‌ی Unsynced commits کلیک نمائید.


در ادامه در صفحه‌ی باز شده، همان آدرس مخصوص مخزن کد جدید را در GitHub وارد کرده و بر روی دکمه‌ی Publish کلیک کنید:


در اینجا بلافاصله صفحه‌ی لاگینی ظاهر می‌شود که باید در آن مشخصات اکانت GitHub خود را وارد نمائید:


به این ترتیب عملیات Publish اولیه انجام شده و تصویر ذیل نمایان خواهد شد:


در اینجا بر روی دکمه‌ی Sync کلیک کنید. به این ترتیب مخزن کد GitHub به پروژه‌ی جاری متصل خواهد شد:


سپس نیاز است فایل‌های موجود را به مخزن کد GitHub ارسال کرد. بنابراین پس از مشاهده‌ی پیام موفقیت آمیز بودن عملیات همگام سازی، بر روی دکمه‌ی Home در نوار ابزار کلیک کرده و اینبار گزینه‌ی Changes را انتخاب کنید:


در اینجا پیام اولین ارسال را وارد کرده و سپس بر روی دکمه‌ی Commit کلیک کنید:


پس از مشاهده‌ی پیام موفقیت آمیز بودن commit محلی، نیاز است تا آن‌را با سرور نیز هماهنگ کرد. به همین جهت در اینجا بر روی لینک Sync کلیک کرده و در صفحه‌ی بعدی بر روی دکمه‌ی Sync کلیک کنید:



اندکی صبر کنید تا فایل‌ها به سرور ارسال شوند. اکنون اگر به GitHub مراجعه کنید، فایل‌های ارسالی قابل مشاهده هستند:



اعمال تغییرات بر روی پروژه‌ی محلی و ارسال به سرور

در ادامه می‌خواهیم دو فایل README.md و LICENSE.md را به پروژه اضافه کنیم. پس از افزودن آن‌ها، یا هر تغییر دیگری در پروژه، اینبار برای ارسال تغییرات به سرور، تنها کافی است به برگه‌ی Team explorer مراجعه کرده و ابتدا بر روی دکمه‌ی Home کلیک کرد تا منوی انتخاب گزینه‌‌های آن ظاهر شود. در اینجا تنها کافی است گزینه‌ی Changes را انتخاب و دقیقا همان مراحل عنوان شده‌ی پیشین را تکرار کرد. ابتدا ورود پیام Commit و سپس Commit. در ادامه Sync محلی و سپس Sync با سرور.