مطالب
نحوه کاهش مصرف حافظه EF Code first حین گزارشگیری از اطلاعات
تمام ORMهای خوب، دارای سطح اول کش هستند. از این سطح جهت نگهداری اطلاعات تغییرات صورت گرفته روی اشیاء و سپس اعمال نهایی آن‌ها در پایان یک تراکنش استفاده می‌شود. بدیهی است جمع آوری این اطلاعات اندکی بر روی سرعت انجام کار و همچنین بر روی میزان مصرف حافظه برنامه تاثیرگذار است. به علاوه یک سری از اعمال مانند گزارشگیری نیازی به این سطح اول کش ندارند. اطلاعات مورد استفاده در آن‌ها مانند نمایش لیستی از اطلاعات در یک گرید، حالت فقط خواندنی دارد. در EF Code first برای یک چنین مواردی استفاده از متد الحاقی AsNoTracking تدارک دیده شده است که سبب خاموش شدن سطح اول کش می‌شود. در ادامه در طی یک مثال، اثر این متد را بر روی سرعت و میزان مصرف حافظه برنامه بررسی خواهیم کرد.

کدهای کامل این مثال را در ذیل ملاحظه می‌کنید:

using System;
using System.Data.Entity;
using System.Data.Entity.Migrations;
using System.Diagnostics;
using System.Linq;

namespace EFGeneral
{
    public class User
    {
        public int Id { get; set; }
        public string Name { get; set; }
    }

    public class MyContext : DbContext
    {
        public DbSet<User> Users { get; set; }
    }

    public class Configuration : DbMigrationsConfiguration<MyContext>
    {
        public Configuration()
        {
            AutomaticMigrationsEnabled = true;
            AutomaticMigrationDataLossAllowed = true;
        }

        protected override void Seed(MyContext context)
        {            
            for (int i = 0; i < 21000; i++)
            {
                context.Users.Add(new User { Name = "name " + i });
                if (i % 1000 == 0)
                    context.SaveChanges();
            }
            base.Seed(context);
        }
    }

    public class PerformanceHelper
    {
        public static string RunActionMeasurePerformance(Action action)
        {
            GC.Collect();
            long initMemUsage = Process.GetCurrentProcess().WorkingSet64;

            var stopwatch = new Stopwatch();
            stopwatch.Start();

            action();

            stopwatch.Stop();

            var currentMemUsage = Process.GetCurrentProcess().WorkingSet64;
            var memUsage = currentMemUsage - initMemUsage;
            if (memUsage < 0) memUsage = 0;

            return string.Format("Elapsed time: {0}, Memory Usage: {1:N2} KB", stopwatch.Elapsed, memUsage / 1024);
        }
    }

    public static class Test
    {
        public static void RunTests()
        {
            Database.SetInitializer(new MigrateDatabaseToLatestVersion<MyContext, Configuration>());
            StartDb();            

            for (int i = 0; i < 3; i++)
            {
                Console.WriteLine("\nRun {0}", i + 1);

                var memUsage = PerformanceHelper.RunActionMeasurePerformance(() => LoadWithTracking());
                Console.WriteLine("LoadWithTracking:\n{0}", memUsage);

                memUsage = PerformanceHelper.RunActionMeasurePerformance(() => LoadWithoutTracking());
                Console.WriteLine("LoadWithoutTracking:\n{0}", memUsage);
            }
        }

        private static void StartDb()
        {
            using (var ctx = new MyContext())
            {
                var user = ctx.Users.Find(1);
                if (user != null)
                {
                    // keep the object in memory
                }
            }
        }

        private static void LoadWithTracking()
        {
            using (var ctx = new MyContext())
            {
                var list = ctx.Users.ToList();
                if (list.Any())
                {
                    // keep the list in memory
                }
            }
        }

        private static void LoadWithoutTracking()
        {
            using (var ctx = new MyContext())
            {
                var list = ctx.Users.AsNoTracking().ToList();
                if (list.Any())
                {
                    // keep the list in memory
                }
            }
        }
    }
}

توضیحات:
مدل برنامه یک کلاس ساده کاربر است به همراه id و نام او.
سپس این کلاس توسط Context برنامه در معرض دید EF Code first قرار می‌گیرد.
در کلاس Configuration تعدادی رکورد را در ابتدای کار برنامه در بانک اطلاعاتی ثبت خواهیم کرد. قصد داریم میزان مصرف حافظه بارگذاری این اطلاعات را بررسی کنیم.
کلاس PerformanceHelper معرفی شده، دو کار اندازه گیری میزان مصرف حافظه برنامه در طی اجرای یک فرمان خاص و همچنین مدت زمان سپری شدن آن‌را اندازه‌گیری می‌کند.
در کلاس Test فوق چندین متد به شرح زیر وجود دارند:
متد StartDb سبب می‌شود تا تنظیمات ابتدایی برنامه به بانک اطلاعاتی اعمال شوند. تا زمانیکه کوئری خاصی به بانک اطلاعاتی ارسال نگردد، EF Code first بانک اطلاعاتی را آغاز نخواهد کرد.
در متد LoadWithTracking اطلاعات تمام رکوردها به صورت متداولی بارگذاری شده است.
در متد LoadWithoutTracking نحوه استفاده از متد الحاقی AsNoTracking را مشاهده می‌کنید. در این متد سطح اول کش به این ترتیب خاموش می‌شود.
و متد RunTests، این متدها را در سه بار متوالی اجرا کرده و نتیجه عملیات را نمایش خواهد داد.

برای نمونه این نتیجه در اینجا حاصل شده است:


همانطور که ملاحظه کنید، بین این دو حالت، تفاوت بسیار قابل ملاحظه است؛ چه از لحاظ مصرف حافظه و چه از لحاظ سرعت.

نتیجه گیری:
اگر قصد ندارید بر روی اطلاعات دریافتی از بانک اطلاعاتی تغییرات خاصی را انجام دهید و فقط قرار است از آن‌ها به صورت فقط خواندنی گزارشگیری شود، بهتر است سطح اول کش را به کمک متد الحاقی AsNoTracking خاموش کنید.

 
مطالب
فرم‌های مبتنی بر قالب‌ها در Angular - قسمت پنجم - ارسال اطلاعات به سرور
تا اینجا تنظیمات اصلی فرم ثبت اطلاعات کارمندان را انجام دادیم. اکنون نوبت به ارسال این اطلاعات به سمت سرور است. پیشنیاز آن نیز تدارک مواردی است که در مطلب «یکپارچه سازی Angular CLI و ASP.NET Core در VS 2017» پیشتر بحث شدند. از این مطلب تنها تنظیمات موارد ذیل را نیاز خواهیم داشت و از تکرار آن‌ها در اینجا صرفنظر می‌شود تا هم مطلب کوتاه‌تر شود و هم بتوان بر روی اصل موضوع جاری، تمرکز کرد:
- ایجاد یک پروژه‌ی جدید ASP.NET Core در VS 2017
- تنظیمات یک برنامه‌ی ASP.NET Core خالی برای اجرای یک برنامه‌ی Angular CLI
- تنظیمات فایل آغازین یک برنامه‌ی ASP.NET Core جهت ارائه‌ی برنامه‌های Angular
- ایجاد ساختار اولیه‌ی برنامه‌ی Angular CLI در داخل پروژه‌ی جاری: این مورد را تاکنون انجام داده‌ایم و تکمیل کرده‌ایم. بنابراین تنها کاری که نیاز است انجام شود، cut و paste محتوای پوشه‌ی angular-template-driven-forms-lab (پروژه‌ی این سری) به ریشه‌ی پروژه‌ی ASP.NET Core است.
- تنظیم محل خروجی نهایی Angular CLI به پوشه‌ی wwwroot
- روش اول و یا دوم اجرای برنامه‌های مبتنی بر ASP.NET Core و Angular CLI

البته سورس کامل تمام این تنظیمات را از انتهای بحث نیز می‌توانید دریافت کنید.
ضمن اینکه هیچ نیازی هم به استفاده از VS 2017 نیست و هر دوی برنامه‌ی Angular و ASP.NET Core را می‌توان توسط VSCode به خوبی مدیریت و اجرا کرد.


ایجاد ساختار مقدماتی سرویس ارسال اطلاعات به سرور

در برنامه‌های Angular مرسوم است جهت کاهش مسئولیت‌های یک کلاس و امکان استفاده‌ی مجدد از کدها، منطق ارسال اطلاعات به سرور، به درون کلاس یک سرویس منتقل شود و سپس این سرویس به کلاس‌های کامپوننت‌ها، برای مثال یک فرم ثبت اطلاعات، برای ارسال و یا دریافت اطلاعات، تزریق گردد. به همین جهت، ابتدا ساختار ابتدایی این سرویس و تنظیمات مرتبط با آن‌را انجام می‌دهیم.
ابتدا از طریق خط فرمان به پوشه‌ی ریشه‌ی برنامه وارد شده (جائیکه فایل Startup.cs قرار دارد) و سپس دستور ذیل را اجرا می‌کنیم:
 >ng g s employee/FormPoster -m employee.module
با این خروجی
 installing service
  create src\app\employee\form-poster.service.spec.ts
  create src\app\employee\form-poster.service.ts
  update src\app\employee\employee.module.ts
همانطور که در سطر آخر نیز ملاحظه می‌کنید، فایل employee.module.ts را جهت درج کلاس جدید FormPosterService در قسمت providers ماژول آن به روز رسانی می‌کند؛ تا بتوانیم این سرویس را در کامپوننت‌های این ماژول تزریق کرده و استفاده کنیم.
ساختار ابتدایی این سرویس را نیز به نحو ذیل تغییر می‌دهیم:
import { Injectable } from '@angular/core';
import { Http } from '@angular/http';

import { Employee } from './employee';

@Injectable()
export class FormPosterService {

    constructor(private http:Http) {
    }

    postEmployeeForm(employee: Employee) {
    }
}
در اینجا سرویس Http انگیولار به سازنده‌ی کلاس تزریق شده‌است و این نحوه‌ی تعریف سبب می‌شود تا بتوان به پارامتر http، به صورت یک فیلد خصوصی تعریف شده‌ی در سطح کلاس نیز دسترسی پیدا کنیم.
چون این کلاس از ماژول توکار Http استفاده می‌کند، نیاز است این ماژول را نیز به قسمت imports فایل src\app\app.module.ts اضافه کنیم:
import { HttpModule } from "@angular/http";

@NgModule({
  imports: [
    BrowserModule,
    FormsModule,
    HttpModule,
    EmployeeModule,
    AppRoutingModule
  ]
اکنون می‌توانیم این سرویس جدید FormPosterService را به سازنده‌ی کامپوننت EmployeeRegisterComponent در فایل src\app\employee\employee-register\employee-register.component.ts تزریق کنیم:
import { FormPosterService } from "../form-poster.service";

export class EmployeeRegisterComponent implements OnInit {

  constructor(private formPoster: FormPosterService) {}

}

در ادامه برای آزمایش برنامه، به ریشه‌ی پروژه وارد شده و دو پنجره‌ی کنسول مجزا را باز کنید. در اولی، دستورات:
>npm install
>ng build --watch
و در دومی، دستورات ذیل را اجرا کنید:
>dotnet restore
>dotnet watch run
دستورات اول کار بازیابی وابستگی‌های سمت کلاینت و سپس ساخت تدریجی برنامه‌ی Angular را دنبال می‌کند. دستورات دوم، وابستگی‌های برنامه‌ی ASP.NET Core را دریافت و نصب کرده و سپس برنامه را در حالت watch ساخته و بر روی پورت 5000 ارائه می‌کند (بدون نیاز به اجرای VS 2017؛ این دستور عمومی است).
به همین جهت برای آزمایش ابتدایی آن، آدرس http://localhost:5000 را در مرورگر باز کنید. برگه‌ی developer tools مرورگر را نیز بررسی کنید تا خطایی در آن ظاهر نشده باشد. برای مثال اگر فراموش کرده باشید تا HttpModule را به app.module اضافه کنید، خطای no provider for HttpModule را مشاهده خواهید کرد.


مدیریت رخداد submit فرم در Angular

تا اینجا کار برپایی تنظیمات اولیه‌ی کار با سرویس Http را انجام دادیم. مرحله‌ی بعد مدیریت رخداد submit فرم است. به همین جهت فایل src\app\employee\employee-register\employee-register.component.html را گشوده و سپس رخدادگردان submit را به فرم آن اضافه کنید:
<form #form="ngForm" (submit)="submitForm(form)" novalidate>
در حین رخدادگردانی submit می‌توان به template reference variable تعریف شده‌ی form# برای دسترسی به وهله‌ای از ngForm نیز کمک گرفت.
export class EmployeeRegisterComponent implements OnInit {
  submitForm(form: NgForm) {
    console.log(this.model);
    console.log(form.value);
  }
}
امضای متد submitForm را در اینجا مشاهده می‌کنید. form دریافتی آن از نوع NgForm است که در ابتدای فایل import شده‌است.
در همین حال اگر بر روی دکمه‌ی ok کلیک کنیم، چنین خروجی را در کنسول developer مروگر می‌توان مشاهده کرد:


اولین مورد، محتوای this.model است و دومی محتوای form.value را گزارش کرده‌است. همانطور که مشاهده می‌کنید، مقدار form.value بسیار شبیه است به وهله‌ای از مدلی که در سطح کلاس تعریف کرده‌ایم و این مقدار همواره توسط Angular نگهداری و مدیریت می‌شود. بنابراین حتما الزامی نیست تا مدلی را جهت کار با فرم‌های مبتنی بر قالب‌ها به صورت جداگانه‌ای تهیه کرد. توسط شیء form نیز می‌توان به تمام اطلاعات فیلدها دسترسی یافت.


تکمیل سرویس ارسال اطلاعات به سرور

در ادامه می‌خواهیم اطلاعات مدل فرم را به سرور ارسال کنیم. برای این منظور سرویس FormPoster را به صورت ذیل تکمیل می‌کنیم:
import { Injectable } from "@angular/core";
import { Http, Response, Headers, RequestOptions } from "@angular/http";

import { Observable } from "rxjs/Observable";
import "rxjs/add/operator/do";
import "rxjs/add/operator/catch";
import "rxjs/add/observable/throw";
import "rxjs/add/operator/map";
import "rxjs/add/observable/of";

import { Employee } from "./employee";

@Injectable()
export class FormPosterService {
  private baseUrl = "api/employee";

  constructor(private http: Http) {}

  private extractData(res: Response) {
    const body = res.json();
    return body.fields || {};
  }

  private handleError(error: Response): Observable<any> {
    console.error("observable error: ", error);
    return Observable.throw(error.statusText);
  }

  postEmployeeForm(employee: Employee): Observable<Employee> {
    const body = JSON.stringify(employee);
    const headers = new Headers({ "Content-Type": "application/json" });
    const options = new RequestOptions({ headers: headers });

    return this.http
      .post(this.baseUrl, body, options)
      .map(this.extractData)
      .catch(this.handleError);
  }
}
برای کار با Observables یا می‌توان نوشت 'import 'rxjs/Rx که تمام بسته‌ی RxJS را import می‌کند، یا همانند این مثال بهتر است تنها اپراتورهایی را که به آن‌ها نیاز پیدا می‌کنیم، import نمائیم. به این ترتیب حجم نهایی ارائه‌ی برنامه نیز کاهش خواهد یافت.
در متد postEmployeeForm، ابتدا توسط JSON.stringify محتوای شیء کارمند encode می‌شود. البته متد post اینکار را به صورت توکار نیز می‌تواند مدیریت کند. سپس ذکر هدر مناسب در اینجا الزامی است تا در سمت سرور بتوانیم اطلاعات دریافتی را به شیء متناظری نگاشت کنیم. در غیراینصورت model binder سمت سرور نمی‌داند که چه نوع فرمتی را دریافت کرده‌است و چه نوع decoding را باید انجام دهد.
در قسمت map، کار بررسی اطلاعات دریافتی از سرور را انجام خواهیم داد و اگر در این بین خطایی وجود داشت، توسط متد handleError در کنسول developer مرورگر نمایش داده می‌شود.
خروجی متد postEmployeeForm یک Observable است. بنابراین تا زمانیکه یک subscriber نداشته باشد، اجرا نخواهد شد. به همین جهت به کلاس EmployeeRegisterComponent مراجعه کرده و متد submitForm را به نحو ذیل تکمیل می‌کنیم:
  submitForm(form: NgForm) {
    console.log(this.model);
    console.log(form.value);

    // validate form
    this.validatePrimaryLanguage(this.model.primaryLanguage);
    if (this.hasPrimaryLanguageError) {
      return;
    }

    this.formPoster
      .postEmployeeForm(this.model)
      .subscribe(
        data => console.log("success: ", data),
        err => console.log("error: ", err)
      );
  }
در اینجا ابتدا اعتبارسنجی سفارشی drop down را که در قسمت قبل بررسی کردیم، قرار داده‌ایم. پس از آن متد postEmployeeForm سرویس formPoster فراخوانی شده‌است و در اینجا کار subscribe به نتیجه‌ی عملیات صورت گرفته‌است که می‌تواند حاوی اطلاعاتی از سمت سرور و یا خطایی در این بین باشد.

یک نکته: اگر علاقمند باشید تا ساختار واقعی شیء NgForm را مشاهده کنید، در ابتدای متد فوق، console.log(form.form) را فراخوانی کنید و سپس شیء حاصل را در کنسول developer مرورگر بررسی نمائید.


تکمیل Web API برنامه‌ی ASP.NET Core جهت دریافت اطلاعات از کلاینت‌ها

در ابتدای سرویس formPoster، یک چنین تعریفی را داریم:
export class FormPosterService {
  private baseUrl = "api/employee";
به همین جهت نیاز است سرویس Web API سمت سرور خود را بر این مبنا تکمیل کنیم.
ابتدا مدل زیر را به پروژه‌ی ASP.NET Core جاری، معادل نمونه‌ی تایپ‌اسکریپتی سمت کلاینت آن اضافه می‌کنیم. البته در اینجا یک Id نیز اضافه شده‌است:
namespace AngularTemplateDrivenFormsLab.Models
{
    public class Employee
    {
        public int Id { set; get; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public bool IsFullTime { get; set; }
        public string PaymentType { get; set; }
        public string PrimaryLanguage { get; set; }
    }
}

سپس کنترلر جدید EmployeeController را با محتوای ذیل اضافه خواهیم کرد:
using Microsoft.AspNetCore.Mvc;
using AngularTemplateDrivenFormsLab.Models;

namespace AngularTemplateDrivenFormsLab.Controllers
{
    [Route("api/[controller]")]
    public class EmployeeController : Controller
    {
        public IActionResult Post([FromBody] Employee model)
        {
            //todo: save model

            model.Id = 100;
            return Created("", new { fields = model });
        }
    }
}
این کنترلر با شیوه‌ی Web API تعریف شده‌است. مسیریابی آن با api شروع می‌شود تا با مسیر baseUrl سرویس formPoster تطابق پیدا کند.
در اینجا پس از ثبت فرضی مدل، Id آن به همراه اطلاعات مدل، به نحوی که ملاحظه می‌کنید، بازگشت داده شده‌است. این نوع خروجی، یک چنین JSON ایی را تولید می‌کند:
{"fields":{"id":100,"firstName":"Vahid","lastName":"N","isFullTime":true,"paymentType":"FullTime","primaryLanguage":"Persian"}}
به همین جهت است که در متد extractData، دسترسی به body.fields را مشاهده می‌کنید. این fields در اینجا دربرگیرنده‌ی اطلاعات بازگشتی از سرور است (نام آن دلخواه است و درصورت تغییر آن در سمت سرو، باید این نام را در متد extractData نیز اصلاح کنید).
  private extractData(res: Response) {
    const body = res.json();
    return body.fields || {};
  }
اکنون اگر برنامه را با دستورات dotnet watch build و ng build --watch اجرا کنیم، بر روی پورت 5000 قابل دسترسی خواهد بود و پس از ارسال فرم به سرور، چنین خروجی را می‌توان در کنسول developer مرورگر مشاهده کرد:


نمایش success به همراه شیءایی که از سمت سرور دریافت شده‌است؛ که حاصل اجرای سطر ذیل در متد submitForm است:
 data => console.log("success: ", data)
همانطور که مشاهده می‌کنید، این شیء به همراه Id نیز هست. بنابراین درصورت نیاز به آن در سمت کلاینت، خاصیت معادل آن‌را به کلاس کارمند اضافه کرده و در همین سطر فوق می‌توان به آن دسترسی یافت.


بارگذاری اطلاعات drop down از سرور

تا اینجا اطلاعات drop down نمایش داده شده از یک آرایه‌ی مشخص سمت کلاینت تامین شدند. در ادامه قصد داریم تا آن‌ها را از سرور دریافت کنیم. به همین جهت اکشن متد ذیل را به کنترلر سمت سرور برنامه اضافه کنید:
[HttpGet("/api/[controller]/[action]")]
public IActionResult Languages()
{
    string[] languages = { "Persian", "English", "Spanish", "Other" };
    return Ok(languages);
}
که برای آزمایش آن می‌توانید مسیر http://localhost:5000/api/employee/languages را جداگانه در مرورگر درخواست کنید.
پس از آن در سمت کلاینت این تغییرات نیاز هستند:
ابتدا به سرویس FormPosterService دو متد ذیل را اضافه می‌کنیم که کار آن‌ها دریافت و پردازش اطلاعات از api/employee/languages سمت سرور هستند:
  private extractLanguages(res: Response) {
    const body = res.json();
    return body || {};
  }

  getLanguages(): Observable<any> {
    return this.http
      .get(`${this.baseUrl}/languages`)
      .map(this.extractLanguages)
      .catch(this.handleError);
  }
اینبار چون خروجی سمت سرور را مانند قبل (متد extractData) داخل فیلدی مانند fields محصور نکردیم، همان body دریافتی بازگشت داده شده‌است.
پس از آن دو تغییر ذیل را نیاز است به EmployeeRegisterComponent اعمال کنیم:
  languages = [];

  ngOnInit() {
    this.formPoster
      .getLanguages()
      .subscribe(
        data => this.languages = data,
        err => console.log("get error: ", err)
      );
  }
ابتدا آرایه‌ی زبان‌ها با یک آرایه‌ی خالی مقدار دهی شده‌است و سپس در متد ngOnInit، کار دریافت اطلاعات آن از سرور، صورت گرفته‌است.

مشکل! ممکن است مدت زمانی طول بکشد تا این اطلاعات از سمت سرور دریافت شوند. در این حالت می‌توان به شکل زیر در فایل employee-register.component.html فرم را تا زمان پر شدن دراپ داون آن مخفی کرد:
<h3 *ngIf="languages.length == 0">Loading...</h3>
<div class="container" *ngIf="languages.length > 0">
در این حالت هر زمانیکه آرایه‌ی زبان‌ها پر شد، loading حذف شده و div نمایان می‌گردد.

کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: angular-template-driven-forms-lab-05.zip
برای اجرای آن فرض بر این است که پیشتر Angular CLI را نصب کرده‌اید. سپس به ریشه‌ی پروژه وارد شده و دو پنجره‌ی کنسول مجزا را باز کنید. در اولی دستورات:
>npm install
>ng build --watch
و در دومی دستورات ذیل را اجرا کنید:
>dotnet restore
>dotnet watch run
اکنون می‌توانید برنامه را در آدرس http://localhost:5000 مشاهده و اجرا کنید.
مطالب
نامرئی کردن Watermarkهای صفحات فایل‌های PDF توسط iTextSharp
احتمالا بارها با PDFهایی که یک Watermark بزرگ را در میانه صفحات خود دارند، برخورد داشته‌اید و متاسفانه در اغلب اوقات استفاده ناصحیحی از این قابلیت صورت می‌گیرد. هدف از Watermark دار کردن صفحات PDF، ذکر جملاتی مانند «آزمایشی بودن» یا «محرمانه بودن» است که در هر دو حالت نباید به صورت عمومی منتشر شوند. اما اگر قرار است مطلبی را به صورت عمومی منتشر کنیم، این روش، بدترین حالت تبلیغی برای یک شخص یا شرکت خواهد بود؛ چون مانع خواندن روان متن شده و اعصاب مصرف کننده را به هم خواهد ریخت. بنابراین هیچگونه جنبه تبلیغی مثبتی را در نهایت برای تهیه کننده به همراه نخواهد داشت.
برای نمونه فایل نمونه سؤالات مصاحبات دات را از اینجا دریافت کنید. یک چنین شکلی دارد:

خوب! چطور می‌توان این Watermark را حذف یا حداقل نامرئی کرد؟
برای پاسخ به این سؤال نیاز است ابتدا با نحوه Watermark دار کردن صفحات یک فایل PDF آشناشویم.

الف) ایجاد یک فایل PDF ساده
        private static void createPdfFile(string pdfFile)
        {
            using (var fs = new FileStream(pdfFile, FileMode.Create, FileAccess.Write, FileShare.None))
            {
                using (var doc = new Document(PageSize.A4))
                {
                    using (var witier = PdfWriter.GetInstance(doc, fs))
                    {
                        doc.Open();
                        for (int i = 1; i <= 5; i++)
                        {
                            doc.NewPage();
                            doc.Add(new Paragraph(String.Format("This is page {0}", i)));
                        }
                        doc.Close();
                    }
                }
            }
        }
در اینجا یک فایل PDF با 5 صفحه ایجاد می‌شود.

ب) افزودن Watermark به فایل PDF تهیه شده
        private static void addWatermark(string pdfFile, string watermarkedFile, string watermarkText)
        {
            FontFactory.Register("c:\\windows\\fonts\\tahoma.ttf");
            var tahoma = FontFactory.GetFont("Tahoma", BaseFont.IDENTITY_H, 40, Font.NORMAL, BaseColor.BLACK);

            var reader = new PdfReader(pdfFile);
            using (var fileStream = new FileStream(watermarkedFile, FileMode.Create, FileAccess.Write, FileShare.None))
            {
                using (var stamper = new PdfStamper(reader, fileStream))
                {
                    int pageCount = reader.NumberOfPages;
                    for (int i = 1; i <= pageCount; i++)
                    {
                        var rect = reader.GetPageSize(i);
                        var cb = stamper.GetUnderContent(i);
                        var gState = new PdfGState();
                        gState.FillOpacity = 0.25f;
                        cb.SetGState(gState);

                        ColumnText.ShowTextAligned(
                            canvas: cb,
                            alignment: Element.ALIGN_CENTER,
                            phrase: new Phrase(watermarkText, tahoma),
                            x: rect.Width / 2,
                            y: rect.Height / 2,
                            rotation: 45f,
                            runDirection: PdfWriter.RUN_DIRECTION_LTR,
                            arabicOptions: 0);
                    }
                }
            }
        }
در متد فوق pdfFile نام و مسیر فایل PDF ایی است که قرار است به صفحات آن Watermark اضافه شود. نام و مسیر فایل خروجی توسط watermarkedFile مشخص می‌شود و watermarkText متنی است که در میانه صفحه نمایش داده خواهد شد.
در اینجا توسط PdfReader، فایل موجود گشوده می‌شود. به این ترتیب می‌توان به تک تک صفحات این فایل دسترسی یافت. از PdfStamper برای نوشتن در این فایل باز شده استفاده می‌کنیم.  متد stamper.GetUnderContent، لایه زیرین متن صفحات را در اختیار ما قرار می‌دهد. اگر علاقمند به نوشتن بر روی لایه رویی متون هستید از متد stamper.GetOverContent استفاده کنید. در اینجا از PdfGState برای مشخص سازی میزان شفافیت متن درحال نمایش، با مقدار دهی FillOpacity آن استفاده شده است. نهایتا از متد ColumnText.ShowTextAligned برای نمایش متن مورد نظر در مکان و زاویه‌ای مشخص استفاده می‌کنیم. این متد با زبان فارسی سازگاری دارد و run direction آن قابل تنظیم است.


ج) آشنایی با ساختار سطح پایین Watermark اضافه شده به صفحات

تقریبا اکثر Watermarkهایی که بر روی صفحات PDF درج می‌شوند، نیمه شفاف هستند تا بتوان متن زیر آن‌ها را مطالعه کرد. این شفافیت همانطور که ذکر شد توسط مقدار دهی شیء PdfGState حاصل می‌شود. اگر فایل PDF تولیدی در قسمت ب را توسط برنامه iText Rups باز کنیم، به شکل زیر خواهیم رسید:


هدف ما این است که به شیء PdfGState موجود در هر صفحه، دسترسی یافته و مقدار FillOpacity آن‌را صفر کنیم. به این ترتیب این Watermark، کاملا شفاف یا نامرئی خواهد شد. در PDF نهایی، چیزی به نام شیء PdfGState وجود ندارد، بلکه با یک سری Dictionary و Array سر و کار داریم.
همانطور که در شکل فوق ملاحظه می‌کنید، برای رسیدن به Gstateها باید مراحل زیر طی شوند:
1- فایل PDF گشوده شده و سپس به هر صفحه دسترسی یافت.
2- نیاز است RESOURCES صفحه جاری استخراج شوند.
3- در این منابع، باید EXTGSTATE را که همان PdfGStateها هستند، بیابیم.
4- سپس مقدار ca این EXTGSTATE یافت شده را به صفر مقدار دهی کنیم.

        private static void removeWatermark(string watermarkedFile, string unwatermarkedFile)
        {
            PdfReader.unethicalreading = true;
            PdfReader reader = new PdfReader(watermarkedFile);
            reader.RemoveUnusedObjects();
            int pageCount = reader.NumberOfPages;
            for (int i = 1; i <= pageCount; i++)
            {
                var page = reader.GetPageN(i);
                PdfDictionary resources = page.GetAsDict(PdfName.RESOURCES);
                PdfDictionary extGStates = resources.GetAsDict(PdfName.EXTGSTATE);
                if (extGStates == null)
                    continue;

                foreach (PdfName name in extGStates.Keys)
                {
                    var obj = extGStates.Get(name);
                    PdfDictionary extGStateObject = (PdfDictionary)PdfReader.GetPdfObject(obj);
                    var stateNumber = extGStateObject.Get(PdfName.ca_);
                    if (stateNumber == null)
                        continue;

                    var caNumber = (PdfNumber)PdfReader.GetPdfObject(stateNumber);
                    if (caNumber.FloatValue != 1f)
                    {
                        extGStateObject.Remove(PdfName.ca_);
                        //با تنظیم مقدار به صفر، نامرئی خواهد شد
                        extGStateObject.Put(PdfName.ca_, new PdfNumber(0f));
                    }
                }
            }

            using (FileStream fs = new FileStream(unwatermarkedFile, FileMode.Create, FileAccess.Write, FileShare.None))
            {
                using (PdfStamper stamper = new PdfStamper(reader, fs))
                {
                    stamper.SetFullCompression();
                    stamper.Close();
                }
            }
        }
نحوه پیاده سازی مراحل یاد شده را در کدهای فوق ملاحظه می‌کنید. ابتدا توسط  PdfReader فایل موجود باز شده و سپس تک تک صفحات آن‌را بررسی می‌کنیم. در این صفحات اگر EXTGSTATE ایی یافت شد، مقدار ca آن‌را به صفر تنظیم خواهیم کرد. از مقدار 1 صرفنظر شده، چون این مقدار عموما برای Watermark دار کردن صفحات استفاده نمی‌شود.
در این متد، watermarkedFile فایلی است که باید watermark آن نامرئی شود و unwatermarkedFile فایل تولیدی حاصل است.

مطالب
دریافت اطلاعات بیشتر از Social Provider ها در VS 2013
هنگامی که یک پروژه جدید ASP.NET را در VS 2013 می‌سازید و متد احراز هویت آن را Individual User Accounts انتخاب می‌کنید، قالب پروژه، امکانات لازم را برای استفاده از تامین کنندگان ثالث، فراهم می‌کند، مثلا مایکروسافت، گوگل، توییتر و فیسبوک. هنگامی که توسط یکی از این تامین کننده‌ها کاربری را احراز هویت کردید، می‌توانید اطلاعات بیشتری درخواست کنید. مثلا عکس پروفایل کاربر یا لیست دوستان او. سپس اگر کاربر به اپلیکیشن شما سطح دسترسی کافی داده باشد می‌توانید این اطلاعات را دریافت کنید و تجربه کاربری قوی‌تر و بهتری ارائه کنید.

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

برای دریافت اطلاعات بیشتر از فیسبوک مراحل زیر را دنبال کنید.

  • یک اپلیکیشن جدید ASP.NET MVC با تنظیمات Individual User Accounts بسازید.
  • احراز هویت فیسبوک را توسط کلید هایی که از Facebook دریافت کرده اید فعال کنید. برای اطلاعات بیشتر در این باره می‌توانید به این لینک مراجعه کنید.
  • برای درخواست اطلاعات بیشتر از فیسبوک، فایل Startup.Auth.cs را مطابق لیست زیر ویرایش کنید.
 List<string> scope = newList<string>() { "email", "user_about_me", "user_hometown", "friends_about_me", "friends_photos" };
 var x = newFacebookAuthenticationOptions();
 x.Scope.Add("email");
 x.Scope.Add("friends_about_me");
 x.Scope.Add("friends_photos");
 x.AppId = "636919159681109";
 x.AppSecret = "f3c16511fe95e854cf5885c10f83f26f";
 x.Provider = newFacebookAuthenticationProvider()
{
    OnAuthenticated = async context =>
    {
         //Get the access token from FB and store it in the database and
        //use FacebookC# SDK to get more information about the user
        context.Identity.AddClaim(
        new System.Security.Claims.Claim("FacebookAccessToken",
                                             context.AccessToken));
    }
};
 x.SignInAsAuthenticationType = DefaultAuthenticationTypes.ExternalCookie;
 app.UseFacebookAuthentication(x);

در خط 1 مشخص می‌کنیم که چه scope هایی از داده را می‌خواهیم درخواست کنیم.
از خط 10 تا 17 رویداد OnAuthenticated را مدیریت می‌کنیم که از طرف Facebook OWIN authentication اجرا می‌شود. این متد هر بار که کاربری با فیسبوک خودش را احراز هویت می‌کند فراخوانی می‌شود. پس از آنکه کاربر احراز هویت شد و به اپلیکیشن سطح دسترسی لازم را اعطا کرد، تمام داده‌ها در FacebookContext ذخیره می‌شوند. 
خط 14 شناسه FacebookAccessToken را ذخیره می‌کند. ما این آبجکت را از فیسبوک دریافت کرده و از آن برای دریافت لیست دوستان کاربر استفاده می‌کنیم.
نکته: در این مثال تمام داده‌ها بصورت Claims ذخیره می‌شوند، اما اگر بخواهید می‌توانید از ASP.NET Identity برای ذخیره آنها در دیتابیس استفاده کنید.
در قدم بعدی لیست دوستان کاربر را از فیسبوک درخواست می‌کنیم. ابتدا فایل Views/Shared/_LoginPartial.cshtml را باز کنید و لینک زیر را به آن بیافزایید.
 <li>
      @Html.ActionLink("FacebookInfo", "FacebookInfo","Account")
</li>

 هنگامی که کاربری وارد سایت می‌شود و این لینک را کلیک می‌کند، ما لیست دوستان او را از فیسبوک درخواست می‌کنیم و بهمراه عکس‌های پروفایل شان آنها را لیست می‌کنیم.
تمام Claim‌ها را از UserIdentity بگیرید و آنها را در دیتابیس ذخیره کنید. در این قطعه کد ما تمام Claim هایی که توسط OWIN دریافت کرده ایم را می‌خوانیم، و شناسه FacebookAccessToken را در دیتابیس عضویت ASP.NET Identity ذخیره می‌کنیم.
//
        // GET: /Account/LinkLoginCallback
        publicasyncTask<ActionResult> LinkLoginCallback()
        {
            var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync(XsrfKey, User.Identity.GetUserId());
            if (loginInfo == null)
            {
                return RedirectToAction("Manage", new { Message = ManageMessageId.Error });
            }
            var result = await UserManager.AddLoginAsync(User.Identity.GetUserId(), loginInfo.Login);
            if (result.Succeeded)
            {
                var currentUser = await UserManager.FindByIdAsync(User.Identity.GetUserId());
                //Add the Facebook Claim
                await StoreFacebookAuthToken(currentUser);
                return RedirectToAction("Manage");
            }
            return RedirectToAction("Manage", new { Message = ManageMessageId.Error });
        }
خط 14-15 شناسه FacebookAccessToken را در دیتابیس ذخیره می‌کند.
StoreFacebookAuthToken تمام اختیارات (claim)‌های کاربر را از UserIdentity می‌گیرد و Access Token را در قالب یک User Claim در دیتابیس ذخیره می‌کند. اکشن LinkLoginCallback هنگامی فراخوانی می‌شود که کاربر وارد سایت شده و یک تامین کننده دیگر را می‌خواهد تنظیم کند.
اکشن ExternalLoginConfirmation هنگام اولین ورود شما توسط تامین کنندگان اجتماعی مانند فیسبوک فراخوانی می‌شود.
در خط 26 پس از آنکه کاربر ایجاد شد ما یک FacebookAccessToken را بعنوان یک Claim برای کاربر ذخیره می‌کنیم.
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> ExternalLoginConfirmation(ExternalLoginConfirmationViewModel model, string returnUrl)
        {
            if (User.Identity.IsAuthenticated)
            {
                return RedirectToAction("Manage");
            }
 
            if (ModelState.IsValid)
            {
                // Get the information about the user from the external login provider
                var info = await AuthenticationManager.GetExternalLoginInfoAsync();
                if (info == null)
                {
                    return View("ExternalLoginFailure");
                }
                var user = newApplicationUser() { UserName = model.Email };
                var result = await UserManager.CreateAsync(user);
                if (result.Succeeded)
                {
                    result = await UserManager.AddLoginAsync(user.Id, info.Login);
                    if (result.Succeeded)
                    {
                        await StoreFacebookAuthToken(user);
                        await SignInAsync(user, isPersistent: false);
                        return RedirectToLocal(returnUrl);
                    }
                }
                AddErrors(result);
            }
 
            ViewBag.ReturnUrl = returnUrl;
            return View(model);
        }


اکشن ExternalLoginCallback هنگامی فراخوانی می‌شود که شما برای اولین بار یک کاربر را به یک تامین کننده اجتماعی اختصاص می‌دهید. در خط 17 شناسه دسترسی فیسبوک را بصورت یک claim برای کاربر ذخیره می‌کنیم.
//
        // GET: /Account/ExternalLoginCallback
        [AllowAnonymous]
        publicasyncTask<ActionResult> ExternalLoginCallback(string returnUrl)
        {
            var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync();
            if (loginInfo == null)
            {
                return RedirectToAction("Login");
            }
 
            // Sign in the user with this external login provider if the user already has a login
            var user = await UserManager.FindAsync(loginInfo.Login);
            if (user != null)
            {
                //Save the FacebookToken in the database if not already there
                await StoreFacebookAuthToken(user);
                await SignInAsync(user, isPersistent: false);
                return RedirectToLocal(returnUrl);
            }
            else
            {
                // If the user does not have an account, then prompt the user to create an account
                ViewBag.ReturnUrl = returnUrl;
                ViewBag.LoginProvider = loginInfo.Login.LoginProvider;
                return View("ExternalLoginConfirmation", newExternalLoginConfirmationViewModel { Email = loginInfo.Email });
            }
        }
در آخر شناسه FacebookAccessToken را در دیتابیس ASP.NET Identity ذخیره کنید.
privateasyncTask StoreFacebookAuthToken(ApplicationUser user)
        {
            var claimsIdentity = await AuthenticationManager.GetExternalIdentityAsync(DefaultAuthenticationTypes.ExternalCookie);
            if (claimsIdentity != null)
            {
                // Retrieve the existing claims for the user and add the FacebookAccessTokenClaim
                var currentClaims = await UserManager.GetClaimsAsync(user.Id);
                var facebookAccessToken = claimsIdentity.FindAll("FacebookAccessToken").First();
                if (currentClaims.Count() <=0 )
                {
                    await UserManager.AddClaimAsync(user.Id, facebookAccessToken);
                }

پکیج Facebook C#SDK را نصب کنید. http://nuget.org/packages/Facebook
فایل AccountViewModel.cs را باز کنید و کد زیر را اضافه کنید.
    public class FacebookViewModel
     {
         [Required]
         [Display(Name = "Friend's name")]
         public string Name { get; set; }
  
        public string ImageURL { get; set; }
    }

کد زیر را به کنترلر Account اضافه کنید تا عکس‌های دوستان تان را دریافت کنید.
//GET: Account/FacebookInfo
[Authorize]
publicasyncTask<ActionResult> FacebookInfo()
{
    var claimsforUser = await UserManager.GetClaimsAsync(User.Identity.GetUserId());
    var access_token = claimsforUser.FirstOrDefault(x => x.Type == "FacebookAccessToken").Value;
    var fb = newFacebookClient(access_token);
    dynamic myInfo = fb.Get("/me/friends");
    var friendsList = newList<FacebookViewModel>();
    foreach (dynamic friend in myInfo.data)
    {
        friendsList.Add(newFacebookViewModel()
           {
               Name = friend.name,
               ImageURL = @"https://graph.facebook.com/" + friend.id + "/picture?type=large"
           });
    }
 
    return View(friendsList);
}

در پوشه Views/Account یک نمای جدید با نام FacebookInfo.cshtml بسازید و کد Markup آن را مطابق لیست زیر تغییر دهید.
@model IList<WebApplication96.Models.FacebookViewModel>
 @if (Model.Count > 0)
 {
     <h3>List of friends</h3>
     <div class="row">
             @foreach (var friend in Model)
             {
               <div class="col-md-3">
                <a href="#" class="thumbnail">
                  <img src=@friend.ImageURL alt=@friend.Name />
                 </a>
               </div>
              }
     </div>
 }
در این مرحله، شما می‌توانید لیست دوستان خود را بهمراه عکس‌های پروفایل شان دریافت کنید.
پروژه را اجرا کنید و توسط Facebook وارد سایت شوید. باید به سایت فیسبوک هدایت شوید تا احراز هویت کنید و دسترسی لازم را به اپلیکیشن اعطا کنید. پس از آن مجددا به سایت خودتان باید هدایت شوید.
حال هنگامی که روی لینک FacebookInfo کلیک می‌کنید باید صفحه ای مشابه تصویر زیر ببینید.

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