۱۳۹۰/۰۹/۰۸

خلاصه اشتراک‌های روز سه شنبه 8 آذر 1390

استفاده از كنترل‌هاي Active-X در WPF


گاهي از اوقات شايد نياز شود تا از يك كنترل Active-X در WPF استفاده شود؛ مثلا هيچ نمايش دهنده‌ي PDF ايي را در ويندوز نمي‌توان يافت كه امكانات و كيفيت آن در حد Acrobat reader و Active-X آن باشد. يك روش استفاده از آن‌را به كمك كنترل WebBrowser در WPF پيشتر در اين سايت مطالعه كرده‌ايد. روش معرفي شده براي WinForm هم در WPF قابل استفاده است كه در ادامه شرح آ‌ن‌ خواهد آمد.

الف) بجاي اضافه كردن يك User control مخصوص WPF يك user control از نوع WinForms را به يك پروژه WPF اضافه كنيد.


سپس مراحل مشابهي را مانند حالت WinForms، بايد طي كرد:
ب) در VS.NET‌ از طريق منوي Tools گزينه‌ي Choose toolbox items ، برگه‌ي Com components را انتخاب كنيد.
ج) سپس گزينه‌ي Adobe PDF reader را انتخاب نمائيد و بر روي دكمه‌ي OK‌ كليك كنيد.


د) اكنون اين كنترل جديد را بر روي فرم user control قسمت الف برنامه قرار دهيد. به صورت خودكار COMReference هاي متناظر هم به پروژه اضافه مي‌شوند.
پس از اينكه كنترل بر روي فرم قرار گرفت بهتر است به خواص آن مراجعه كرده و خاصيت Dock آن‌را با Fill مقدار دهي كرد تا كنترل به صورت خودكار در هر اندازه‌اي كل ناحيه‌ي متناظر را پوشش دهد.


كد‌هاي مرتبط با نمايش فايل PDF اين كنترل هم به شرح زير است:

using System.Windows.Forms;

namespace WpfPdfViewer.Controls
{
    public partial class AcroReader : UserControl
    {
        public AcroReader(string fileName)
        {
            InitializeComponent();
            ShowPdf(fileName);
        }

        public void ShowPdf(string fileName)
        {
            if (string.IsNullOrWhiteSpace(fileName)) return;
            axAcroPDF1.LoadFile(fileName);
            axAcroPDF1.setShowToolbar(true);
            axAcroPDF1.Show();
        }
    }
}


خوب، ما تا اينجا يك كنترل Active-X را از طريق يك User controls مخصوص WinForms به پروژه‌ي WPF جاري اضافه كرده‌ايم. براي اينكه بتوانيم اين كنترل را درون مثلا يك User control از جنس WPF و XAML نمايش دهيم بايد از كنترل WindowsFormsHost استفاده كرد. براي اين منظور نياز است تا ارجاعي را به اسمبلي WindowsFormsIntegration اضافه كنيم. پس از آن كنترل ياد شده قابل استفاده خواهد بود.


براي نمونه كدهاي XAML پنجره اصلي برنامه مي‌تواند به صورت زير باشد:

<Window x:Class="WpfPdfViewer.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <WindowsFormsHost x:Name="WindowsFormsHost1" />
    </Grid>
</Window>

سپس جهت استفاده از كنترل WindowsFormsHost خواهيم داشت:

using WpfPdfViewer.Controls;

namespace WpfPdfViewer
{
    public partial class MainWindow
    {
        public MainWindow()
        {
            InitializeComponent();
            WindowsFormsHost1.Child = new AcroReader(@"PageSummary.pdf");
        }
    }
}

فقط كافي است شيء Child اين كنترل را با وهله‌اي از يوزركنترل AcroReader اضافه شده به برنامه مقدار دهي كنيم.

سؤال: اين روش زياد MVVM friendly نيست. به عبارتي Child را نمي‌توان از طريق Binding مقدار دهي كرد. آيا راهي براي آن وجود دارد؟
پاسخ: بله. روش متداول براي حل اين نوع مشكلات، نوشتن يك DependencyObject و Attached property مناسب مي‌باشد كه به آن‌ها Behaviors هم مي‌گويند. براي مثال يك نمونه از اين پياده سازي را در ذيل مشاهده مي‌كنيد:

using System;
using System.Windows;
using System.Windows.Forms;
using System.Windows.Forms.Integration;

namespace WpfPdfViewer.Behaviors
{
    public class WindowsFormsHostBehavior : DependencyObject
    {
        public static readonly DependencyProperty BindableChildProperty =
                                    DependencyProperty.RegisterAttached("BindableChild",
                                    typeof(Control),
                                    typeof(WindowsFormsHostBehavior),
                                    new UIPropertyMetadata(null, BindableChildPropertyChanged));

        public static Control GetBindableChild(DependencyObject obj)
        {
            return (Control)obj.GetValue(BindableChildProperty);
        }

        public static void SetBindableChild(DependencyObject obj, Control value)
        {
            obj.SetValue(BindableChildProperty, value);
        }

        public static void BindableChildPropertyChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
        {
            var windowsFormsHost = o as WindowsFormsHost;
            if (windowsFormsHost == null)
                throw new InvalidOperationException("This behavior can only be attached to a WindowsFormsHost.");

            var control = (Control)e.NewValue;
            windowsFormsHost.Child = control;
        }
    }
}

كه نهايتا براي استفاده از آن خواهيم داشت:

<WindowsFormsHost 
       Behaviors:WindowsFormsHostBehavior.BindableChild="{Binding ...}" />

و در ViewModel برنامه هم مانند مثال فوق، فقط كافي است يك وهله از new AcroReader به اين خاصيت قابل انقياد از نوع Control، انتساب داده شود.
يا حتي مي‌توان بجاي نوشتن يك BindableChild، براي مثال مسير فايل pdf را به DependencyObject تعريف شده ارسال كرد و سپس در همانجا اين وهله سازي و انتسابات صورت گيرد (بجاي ViewModel برنامه كه اينبار فقط مسير را تنظيم مي‌كند).


۱۳۹۰/۰۹/۰۵

خلاصه اشتراک‌های روز شنبه 5 آذر 1390

آشنايي با Refactoring - قسمت 14


در بسياري از زبان‌هاي برنامه نويسي امكان null بودن Reference types وجود دارد. به همين جهت مرسوم است پيش از استفاده از آن‌ها، بررسي شود آيا شيء مورد استفاده نال است يا خير و سپس براي مثال متد يا خاصيت مرتبط با آن فراخواني گردد؛ در غير اينصورت برنامه با يك استثناء خاتمه خواهد يافت.
مشكلي هم كه با اين نوع بررسي‌ها وجود دارد اين است كه پس از مدتي كد موجود را تبديل به مخزني از انبوهي از if و else ها خواهند كرد كه هم درجه‌ي پيچيدگي متدها را افزايش مي‌دهند و هم نگهداري ‌آن‌ها را در طول زمان مشكل مي‌سازند. براي حل اين مساله، الگوي استانداردي وجود دارد به نام null object pattern؛ به اين معنا كه بجاي بازگشت دادن null و يا سبب بروز يك exception شدن، بهتر است واقعا مطابق شرايط آن متد يا خاصيت، «هيچ‌كاري» را انجام نداد. در ادامه، توضيحاتي در مورد نحوه‌ي پياده سازي اين «هيچ‌كاري» را انجام ندادن، ارائه خواهد شد.


الف) حين معرفي خاصيت‌ها از محافظ استفاده كنيد.

براي مثال اگر قرار است خاصيتي به نام Name را تعريف كنيد كه از نوع رشته‌ است، حالت امن آن رشته بجاي null بودن، «خالي» بودن است. به اين ترتيب مصرف كننده مدام نگران اين نخواهد بود كه آيا الان اين Name نال است يا خير. مدام نياز نخواهد داشت تا if و else بنويسد تا اين مساله را چك كند. نحوه پياده سازي آن هم ساده است و در ادامه بيان شده است:

private string name = string.Empty;
public string Name
{
    get { return this.name; }
    set 
    {
        if (value == null)
        {
            this.name = "";
            return;
        }
        this.name = value; 
    }
}

دقيقا در زمان انتساب مقداري به اين خاصيت، بررسي مي‌شود كه آيا مثلا null است يا خير. اگر بود، همينجا و نه در كل برنامه، مقدار آن «خالي» قرار داده مي‌شود.

ب) سعي كنيد در متدها تا حد امكان null بازگشت ندهيد.

براي نمونه اگر متدي قرار است ليستي را بازگشت دهد:

public IList<string> GetCultures()
{
    //...
}

و حين تهيه اين ليست، عضوي مطابق منطق پياده سازي آن يافت نشد، null را بازگشت ندهيد؛ يك new List خالي را بازگشت دهيد. به اين ترتيب مصرف كننده ديگري نيازي به بررسي نال بودن خروجي اين متد نخواهد داشت.


ج) از متدهاي الحاقي بجاي if و else استفاده كنيد.

پياده سازي حالت الف زماني ميسر خواهد بود كه طراح اصلي ما باشيم و كدهاي برنامه كاملا در اختيار ما باشند. اما در شرايطي كه امكان دستكاري كدهاي يك كتابخانه پايه را نداريم چه بايد كرد؟ مثلا دسترسي به تعاريف كلاس XElement دات نت فريم ورك را نداريم (يا حتي اگر هم داريم، تغيير آن تا زمانيكه در كدهاي پايه اعمال نشده باشد، منطقي نيست). در اين حالت مي‌شود يك يا چند extension method را طراحي كرد:

public static class LanguageExtender
{
        public static string GetSafeStringValue(this XElement input)
        {
            return (input == null) ? string.Empty : input.Value;
        }

        public static DateTime GetSafeDateValue(this XElement input)
        {
            return (input == null) ? DateTime.MinValue : DateTime.Parse(input.Value);
        }
}

به اين ترتيب مي‌توان امكانات كلاس پايه‌‌اي را بدون نياز به دسترسي به كدهاي اصلي آن مطابق نياز‌هاي خود تغيير و توسعه داد.


۱۳۹۰/۰۹/۰۱

خلاصه اشتراک‌های روز سه شنبه 1 آذر 1390


ساخت يك گزارش ساز به كمك iTextSharp و Open Office


iTextSharp پايه كار با فايل‌هاي PDF را ارائه مي‌دهد اما ابزاري را جهت ساده‌تر سازي توليد فايل‌هاي PDF به همراه ندارد؛ هر چند مثلا امكان تبديل HTML به PDF را دارا است اما بايد گفت: «تا حدودي البته». اگر نياز باشد جدولي را ايجاد كنيم بايد كد نويسي كرد، اگر نياز باشد تصويري اضافه شود به همين ترتيب و الي آخر. البته اين را هم بايد در نظر داشت كه كد نويسي انعطاف قابل توجهي را در اختيار برنامه نويس قرار مي‌دهد؛ شايد به همين دليل اين روزها مباحث «Code first» بيشتر مورد توجه برنامه نويس‌ها است، تا مباحث «Wizard first» يك دهه قبل!
اما باز هم داشتن يك طراح بد نيست و مي‌تواند در كاهش مدت زمان توليد نهايي يك فايل PDF مؤثر باشد. براي اين منظور مي‌توان از برنامه‌ي رايگان و معروف Open office استفاده كرد. توسط آن مي‌توان يك فرم PDF را طراحي و سپس فيلدهاي آن‌را (اين قالب تهيه شده را) با iTextSharp پر كرد. اين مورد مي‌تواند براي تهيه گزارش‌هايي كه تهيه آن‌ها با ابزارهاي متداول گزارش سازي عموما ميسر نيست،‌ بسيار مناسب باشد.


طراحي يك فرم PDF با استفاده از برنامه Open Office

آخرين نگارش برنامه Open office را از اينجا مي‌توانيد دريافت كنيد و آنچنان حجمي هم ندارد؛ حدودا 154 مگابايت است.
پس از نصب و اجراي برنامه، حداقل به دو طريق مي‌توان يك فرم جديد را شروع كرد:
الف) آغاز يك XML Form document جديد در Open office سبب خواهد شد كه نوارهاي ابزار طراحي فرم، مانند قرار دادن TextBox ، CheckBox و غيره به صورت خودكار ظاهر شوند.
ب) و يا آغاز يك سند معمولي و سپس مراجعه به منوي View->Toolbars->Form Controls هم همان حالت را به همراه خواهد داشت.


در اينجا براي طراحي يك گزارش يا فرم جديد تنها كافي است همانند روش‌هاي متداول تهيه يك سند معمولي رفتار كنيم و مواردي را كه قرار است توسط iTextSharp مقدار دهي كنيم، با كنترل‌هاي نوار ابزار Form آن بر روي صفحه قرار دهيم كه نمونه‌ي ساده آن‌را در شكل زير ملاحظه مي‌كنيد:


براي گزارش‌هاي فارسي بهتر است Alignment يك كنترل به Right تنظيم شود و Border به حالت Without frame مقدار دهي گردد. نام اين كنترل را هم بخاطر بسپاريد و يا تغيير دهيد. از اين نام‌ها در iTextSharp استفاده خواهيم كرد. (صفحه خواص فوق با دوبار كليك بر روي يك كنترل قرار گرفته بر روي فرم ظاهر مي‌شود)

مرحله بعد، تبديل اين فرم به فايل PDF است. كليك بر روي دكمه تهيه خروجي به صورت PDF در نوار ابزار اصلي آن براي اينكار كفايت مي‌كند. اين گزينه در منوي File نيز موجود است.


فرم‌هاي PDF تهيه شده در اينجا، فقط خواندني هستند. مثلا يك كاربر مي‌تواند آن‌ها را پر كرده و چاپ كند. اما ما از آن‌ها در ادامه به عنوان قالب گزارشات استفاده خواهيم كرد. بنابراين جهت ويرايش فرم‌هاي تهيه شده بهتر است فايل‌هاي اصلي Open Office مرتبط را نيز درجايي نگهداري كرد و هر بار پس از ويرايش، نياز است تا خروجي جديد PDF آن‌ها تهيه شود.


استفاده از iTextSharp جهت مقدار دهي فيلدهاي يك فرم PDF

در ادامه مي‌خواهيم اين قالب گزارشي را كه تهيه كرديم با كمك iTextSharp پر كرده و يك فايل PDF جديد تهيه كنيم. سورس كامل اينكار را در ذيل مشاهده مي‌كنيد:


using System;
using System.Diagnostics;
using System.IO;
using iTextSharp.text;
using iTextSharp.text.pdf;

namespace PdfForm
{
    class Program
    {
        //روش صحيح ثبت و معرفي فونت در اين كتابخانه
        public static iTextSharp.text.Font GetTahoma()
        {
            var fontName = "Tahoma";
            if (!FontFactory.IsRegistered(fontName))
            {
                var fontPath = Environment.GetEnvironmentVariable("SystemRoot") + "\\fonts\\tahoma.ttf";
                FontFactory.Register(fontPath);
            }
            return FontFactory.GetFont(fontName, BaseFont.IDENTITY_H, BaseFont.EMBEDDED);
        }

        static void Main(string[] args)
        {
            string fileNameExisting = @"form.pdf";
            string fileNameNew = @"newform.pdf";

            using (var existingFileStream = new FileStream(fileNameExisting, FileMode.Open))
            using (var newFileStream = new FileStream(fileNameNew, FileMode.Create))
            {
                var pdfReader = new PdfReader(existingFileStream);
                using (var stamper = new PdfStamper(pdfReader, newFileStream))
                {
                    //نكته مهم جهت كار با اطلاعات فارسي
                    //در غيراينصورت شاهد ثبت اطلاعات نخواهيد بود
                    stamper.AcroFields.AddSubstitutionFont(GetTahoma().BaseFont);

                    //form.Fields.Keys = تمام فيلدهاي موجود در فرم
                    var form = stamper.AcroFields;                   

                    //مقدار دهي فيلدهاي فرم
                    form.SetField("TextBox1", "مقدار1");
                    form.SetField("TextBox2", "مقدار2");

                    // "Yes" and "Off" are valid values here
                    form.SetField("Check Box 1", "Yes");

                    // "" and "Off" are valid values here
                    form.SetField("Option Button 1", "");

                    // نحوه مقدار دهي ليست
                    form.SetListOption("ListBox1", new[] { "1مقدار يك", "مقدار دو1" }, null);
                    form.SetField("ListBox1", null);

                    // به اين ترتيب فرم ديگر توسط كاربر قابل ويرايش نخواهد بود
                    //stamper.PartialFormFlattening --> جهت غيرقابل ويرايش نمودن فيلدي مشخص
                    stamper.FormFlattening = true;

                    stamper.Close();
                    pdfReader.Close();
                }
            }

            Process.Start("newform.pdf");
        }
    }
}

توضيحات:
چون در اينجا فايل PDF، از پيش تهيه شده است، پس بايد از اشياء PdfReader و PdfStamper جهت خواندن و نوشتن اطلاعات در آن‌ها استفاده كرد. سپس توسط شيء stamper.AcroFields مي‌توان به اين فيلدها يا همان كنترل‌هايي كه در برنامه‌ي Open office بر روي فرم قرار داديم، دسترسي پيدا كنيم.
در ابتدا نياز است فونت اين فيلدها توسط متد AddSubstitutionFont مقدار دهي شود. اين مورد براي گزارش‌هاي فارسي الزامي است؛ در غيراينصورت متني را در خروجي مشاهده نخواهيد كرد.
ادامه كار هم مشخص است. توسط متد form.SetField مقداري را به كنترل‌هاي قرار گرفته بر روي فرم نسبت مي‌دهيم. آرگومان اول آن نام كنترل است و آرگومان دوم، مقدار مورد نظر مي‌باشد. اگر كنترل CheckBox را بر روي صفحه قرار داديد، تنها مقدارهاي Yes و Off را مي‌پذيرد (آن هم با توجه به اينكه به كوچكي و بزرگي حروف حساس است). اگر يك Radio button يا در اينجا Option button را بر روي فرم قرار داديد، تنها مقدارهاي خالي و Off را قبول خواهد كرد. نحوه‌ي مقدار دهي يك ليست هم در اينجا ذكر شده است.
در پايان چون نمي‌خواهيم كاربر نهايي قادر به ويرايش اطلاعات باشد، FormFlattening را true خواهيم كرد و به اين ترتيب، كنترل‌ها فقط خواندني خواهند شد. البته اگر همانطور كه ذكر شد، border كنترل‌ها را در حين طراحي حذف كنيد، PDF نهايي توليدي يكپارچه و يك دست به نظر مي‌رسد و اصلا مشخص نخواهد بود كه اين فايل پيشتر يك فرم قابل پر كردن بوده است.

۱۳۹۰/۰۸/۲۹

خلاصه اشتراک‌های روز يك شنبه 29 آبان 1390

اضافه كردن پيوست به فايل‌هاي PDF با استفاده از iTextSharp


فايل PDF موجود عجيب و غريبي است. مي‌شود به آن فايل پيوست اضافه كرد. مثلا اگر يك راهنماي آموزشي را با فرمت PDF تهيه مي‌كنيد، لازم نيست تا فايل‌هاي مرتبط با آن‌را جداگانه ارائه دهيد. مي‌شود تمام اين‌ها را داخل همان فايل PDF مدفون كرد. روش انجام اينكار به كمك iTextSharp ساده است اما چند نكته را نيز به همراه دارد:

using System.Diagnostics;
using System.IO;
using iTextSharp.text;
using iTextSharp.text.pdf;

namespace PDFAttachment
{
    class Program
    {
        static void Main(string[] args)
        {
            using (var pdfDoc = new Document(PageSize.A4))
            {
                var pdfWriter = PdfWriter.GetInstance(pdfDoc, new FileStream("Test.pdf", FileMode.Create));                
                pdfDoc.Open();

                pdfDoc.Add(new Phrase("Test"));

                var fs = PdfFileSpecification.FileEmbedded(pdfWriter, @"C:\path\logo.png", "logo.png", null);
                pdfWriter.AddFileAttachment("توضيحات",fs);
            }

            Process.Start("Test.pdf");
        }
    }
}

در ساده‌ترين حالت ممكن، با استفاده از متد AddFileAttachmen شيء PdfWriter مي‌توان پيوستي را به يك فايل PDF در حال توليد اضافه كرد. اگر به فايل نهايي مراجعه كنيم و همچنين قسمت attachments را هم دستي در Adobe reader انتخاب نمائيم، شكل زير حاصل خواهد شد:


روش متداول بكارگرفته شده دو مشكل را به همراه دارد:
  • قسمت modified مقدار دهي نشده است.
  • پنل مربوط به پيوست‌ها بايد دستي باز شود.

نحوه مقدار دهي ستون modified پس از تعريف يك PdfDictionary و قرار دادن PdfName.MODDATE در آن، به صورت زير است:

using System.Diagnostics;
using System.IO;
using iTextSharp.text;
using iTextSharp.text.pdf;

namespace PDFAttachment
{
    class Program
    {
        static void Main(string[] args)
        {
            using (var pdfDoc = new Document(PageSize.A4))
            {
                var pdfWriter = PdfWriter.GetInstance(pdfDoc, new FileStream("Test.pdf", FileMode.Create));                
                pdfDoc.Open();

                pdfDoc.Add(new Phrase("Test"));

                var filePath = @"C:\path\logo.png";
                var fileInfo = new FileInfo(filePath);
                var pdfDictionary = new PdfDictionary();
                pdfDictionary.Put(PdfName.MODDATE, new PdfDate(fileInfo.LastWriteTime));
                var fs = PdfFileSpecification.FileEmbedded(pdfWriter, filePath, fileInfo.Name, null, true, null, pdfDictionary);
                pdfWriter.AddFileAttachment("توضيحات", fs);
            }

            Process.Start("Test.pdf");
        }
    }
}

كه اينبار خروجي زير را به همراه دارد:


و براي نمايش خودكار پنل پيوست‌ها در Adobe reader به طوري كه كاربر نهايي متوجه وجود اين فايل‌هاي پيوست شده گردد، مي‌توان ViewerPreferences شيء pdfWriter را مقدار دهي نمود:

pdfWriter.ViewerPreferences = PdfWriter.PageModeUseAttachments;

در مورد فايل‌هاي موجود چطور؟ آيا مي‌توان به يك فايل PDF از پيش تهيه شده هم فايل پيوست كرد؟
پاسخ: بله. بايد از امكانات شيء PdfReader استفاده كرد:

using System.Diagnostics;
using System.IO;
using iTextSharp.text;
using iTextSharp.text.pdf;

namespace PDFAttachment
{
    class Program
    {
        static void Main(string[] args)
        {
            var reader = new PdfReader("Test.pdf");
            using (var stamper = new PdfStamper(reader, new FileStream("newTest.pdf", FileMode.Create)))
            {
                var filePath = @"C:\path\logo.png";
                addAttachment(stamper, filePath, "توضيحات");
                stamper.Close();
            }

            Process.Start("newTest.pdf");
        }

        private static void addAttachment(PdfStamper stamper, string filePath, string description)
        {
            var fileInfo = new FileInfo(filePath);
            var pdfDictionary = new PdfDictionary();
            pdfDictionary.Put(PdfName.MODDATE, new PdfDate(fileInfo.LastWriteTime));
            var pdfWriter = stamper.Writer;
            var fs = PdfFileSpecification.FileEmbedded(pdfWriter, filePath, fileInfo.Name, null, true, null, pdfDictionary);
            stamper.AddFileAttachment(description, fs);
        }
    }
}

در اينجا به كمك كلاس PdfReader، يك فايل موجود خوانده شده و سپس با استفاده از امكانات كلاس PdfStamper كه خاصيت Writer آن همان pdfWriter است مي‌توان فايل مورد نظر را به فايل موجود افزود.


۱۳۹۰/۰۸/۲۸

خلاصه اشتراک‌های روز شنبه 28 آبان 1390

راه حل ساده براي عكس‌هايي كه از كادر بيرون مي‌زنند


ارسال تصوير در سايت‌ها، انجمن‌ها و امثال آن هميشه مشكل زا است؛ خصوصا نمايش تصاويري در قطع يك تابلوي ديواري در يك كادر كوچك.
راه ساده و دم دستي زير، منهاي استفاده از اسكريپت‌هايي كه اندازه تصاوير را تشخيص داده و بر اين اساس خودشان آن‌ها را بند انگشتي نمايش مي‌دهند، هم براي حل اين مشكل وجود دارد:
فرض كنيد مطلبي كه قرار است ارسال شود در يك DIV نمايش داده مي‌شود و كلاس آن مثلا post-body است.
اولين كاري كه مي‌شود انجام داد اين است:
در CSS سايت قسمت مرتبط با post-body تعريف زير را اضافه مي‌كنيم تا اضافات عكس كه از كادر بيرون زده، خودبخود توسط مرورگر نمايش داده نشود:

overflow: hidden;

سپس در همين DIV كه كلاس آن post-body است، با استفاده از jQuery به دنبال تصاوير گشته و موارد يافت شده را داخل يك لينك پويا قرار مي‌دهيم (استفاده از متد wrap براي اين محصور سازي). اين لينك هم همان آدرس اصلي تصوير است. به اين ترتيب كاربر با كليك بر روي آن تصوير مي‌تواند نتيجه را در صفحه‌اي ديگر مشاهده كند:

$(document).ready(function(){  
 $('.post-body img').each(function(){
    var $img = $(this);
    var src = $img.attr("src");
    $img.attr({border:"0"}).wrap('<a target="_blank" alt="Click here to enlarge (opens new window)" title="Click here to enlarge (opens new window)" href="'+src+'" />');
 }); 
});

۱۳۹۰/۰۸/۲۳

تهيه گزارشات Crosstab به كمك LINQ - قسمت دوم


اگر به قسمت اول «تهيه گزارشات Crosstab به كمك LINQ» دقت كرده باشيد، يك مشكل كوچك دارد و آن هم لزوم مشخص سازي دقيق ستون‌هايي است كه مي‌خواهيم در گزارش ظاهر شوند. مثلا دقيقا مشخص كنيم كه نام واحد چيست يا دقيقا روز را مشخص كنيم. اين مورد براي گزارش‌هاي كوچك مشكلي ندارد؛ ولي اگر همان مثال دوم را در نظر گرفته و بازه را كمي بيشتر كنيم، مثلا يك ماه، آن وقت بايد حداقل 30 بار بنويسيم Day1IsPresent تا ... Day30IsPresent و يا اگر بازه‌ي گزارشگيري به اختيار كاربر باشد آن وقت چه بايد كرد؟ مثلا يكبار 7 روز پايان ماه را انتخاب كند، يكبار 14 روز را، شايد يك بار هم مثلا 90 روز را مد نظر داشته باشد (تعداد ستون‌ها متغير باشد يا به عبارتي Dynamic Crosstab نياز است ايجاد شود).
براي حل اين مساله، مي‌توان از متد الحاقي زير از سايت extensionmethod.net كمك گرفت:

using System;
using System.Collections.Generic;
using System.Linq;

namespace PivotExtensions
{
    public static class Ext
    {
        public static Dictionary<TKey1, Dictionary<TKey2, TValue>>
                        Pivot<TSource, TKey1, TKey2, TValue>
                        (
                            this IEnumerable<TSource> source,
                            Func<TSource, TKey1> key1Selector,
                            Func<TSource, TKey2> key2Selector,
                            Func<IEnumerable<TSource>, TValue> aggregate
                        )
        {
            return source.GroupBy(key1Selector)
                         .Select(
                            key1Group => new
                                {
                                    Key = key1Group.Key,
                                    Value = key1Group.GroupBy(key2Selector)
                                         .Select(
                                            key2Group => new
                                               {
                                                   K = key2Group.Key,
                                                   V = aggregate(key2Group)
                                               })
                                         .ToDictionary(e => e.K, o => o.V)
                                })
                         .ToDictionary(e => e.Key, o => o.Value);
        }
    }
}

در اين متد:
key1Selector مشخص كننده ستون‌هاي ثابت و مشخص سمت راست يا چپ (بر اساس جهت صفحه) گزارش است. در سيستم‌هاي مختلف اين ستون‌ها نام‌هايي مانند keyColumn ، leftColumn و Row Heading ممكن است داشته باشند.
key2Selector ستون‌هاي پوياي گزارش را تشكيل مي‌دهد. در ساير سيستم‌ها اين پارامتر، pivotNameColumn ،VariableColumn ، topField و يا Column Heading هم ناميده مي‌شود.
Aggregate در اينجا مشخص مي‌كند كه مقادير ستون‌هاي پوياي ياد شده چگونه بايد محاسبه شوند.

با توجه به اين متد، براي نمونه جهت حل مثال اول قسمت قبل خواهيم داشت:

var list = ExpenseDataSource.ExpensesDataSource();
var pivotList = list.Pivot(
                x =>
                    new
                    {
                        x.Date.Year,
                        x.Date.Month
                    },
                  x1 => x1.Department,
                  x2 => x2.Sum(x => x.Expenses));

با خروجي


فايل LINQPad آن از اينجا قابل دريافت است.


و براي حل مثال دوم قسمت قبل مي‌توان نوشت:

var list2 = StudentsStatDataSource.CreateWeeklyReportDataSource();
var lst = list2.Pivot(
                x =>
                    new 
                    {
                        x.Id,
                        x.Name
                    },
              x1 => "Day " + x1.Date.Day,
              x2 => x2.First().IsPresent);

با خروجي


فايل LINQPad آن از اينجا قابل دريافت است.

خلاصه اشتراک‌های روز دو شنبه 23 آبان 1390


۱۳۹۰/۰۸/۲۲

تهيه گزارشات Crosstab به كمك LINQ


در گزارشات Crosstab، رديف‌هاي يك گزارش، تبديل به ستون‌هاي آن مي‌شوند؛ به همين جهت به آن‌ها Pivot tables هم مي‌گويند.
براي مثال فرض كنيد كه قصد داريد گزارش تعداد ساعت كاركرد را به ازاي هر پروژه در طول چند ماه تعيين كنيد. گزارش متداول از اين نوع اطلاعات، يك ليست بلند بالاي بي‌مفهوم است. اين گزارش تشكيل شده از صدها ركورد به ازاي كاركنان مختلف در پروژه‌هاي مختلف و ... هيچ ارزش آماري خاصي ندارد. يك گزارش بدوي است. زمانيكه اين گزارش را تبديل به حالت crosstab مي‌كنيم، اولين ستون فقط يك شماره پروژه خواهد بود و ستون‌هاي بعدي، مثلا نام ماه‌ها و مقادير آن‌ها هم جمع كاركرد افراد بر روي يك پروژه مشخص.

مثال اول) تهيه گزارش Crosstab جمع هزينه‌هاي واحدهاي مختلف به تفكيك ماه

كلاس هزينه‌هاي زير را در نظر بگيريد كه به كمك آن مي‌توان به ازاي هر واحد يا دپارتمان در تاريخ‌هاي متفاوت، هزينه‌اي را مشخص ساخت:

using System;

namespace Pivot.Sample1
{
    public class Expense
    {
        public DateTime Date { set; get; }
        public string Department { set; get; }
        public decimal Expenses { set; get; }
    }
}

با توجه به اين كلاس، يك منبع داده آزمايشي جهت تهيه گزارشات، مي‌تواند به صورت زير باشد:

using System;
using System.Collections.Generic;

namespace Pivot.Sample1
{
    public class ExpenseDataSource
    {
        public static IList<Expense> ExpensesDataSource()
        {
            return new List<Expense>
            {
                new Expense { Date = new DateTime(2011,11,1), Department = "Computer", Expenses = 100 },
                new Expense { Date = new DateTime(2011,11,1), Department = "Math", Expenses = 200 },
                new Expense { Date = new DateTime(2011,11,1), Department = "Physics", Expenses = 150 },

                new Expense { Date = new DateTime(2011,10,1), Department = "Computer", Expenses = 75 },
                new Expense { Date = new DateTime(2011,10,1), Department = "Math", Expenses = 150 },
                new Expense { Date = new DateTime(2011,10,1), Department = "Physics", Expenses = 130 },

                new Expense { Date = new DateTime(2011,9,1), Department = "Computer", Expenses = 90 },
                new Expense { Date = new DateTime(2011,9,1), Department = "Math", Expenses = 95 },
                new Expense { Date = new DateTime(2011,9,1), Department = "Physics", Expenses = 100 }
            };
        }
    }
}

و اگر اين ليست را به همين شكلي كه هست نمايش دهيم، خروجي زير را خواهيم داشت:


كه ... خروجي مطلوبي نيست. در اينجا ما فقط 9 ركورد داريم؛ اما در عمل به ازاي هر روز، يك ركورد مي‌تواند وجود داشته باشد و اين ليست طولاني، هيچ ارزش آماري خاصي ندارد. مي‌خواهيم سرستون‌هاي گزارش ما مطابق جدول زير باشند:


يعني اگر سه ماه را در نظر بگيريم با هر تعداد ركورد، فقط سه رديف به ازاي هر ماه بايد حاصل شود و ستون‌هاي ديگر هم نام بخش‌ها يا واحدهاي موجود باشند.
براي رسيدن به اين خروجي Crosstab، مي‌توان كوئري LINQ زير را به كمك امكانات گروه بندي اطلاعات آن تهيه كرد:

using System.Collections;
using System.Linq;

namespace Pivot.Sample1
{
    public class PivotTable
    {
        public static IList ExpensesCrossTab()
        {
            return ExpenseDataSource
                        .ExpensesDataSource()
                        .GroupBy(t =>
                                   new
                                   {
                                       Year = t.Date.Year,
                                       Month = t.Date.Month
                                   })
                        .Select(myGroup =>
                                   new
                                   {
                                       //Year = myGroup.Key.Year,
                                       Month = myGroup.Key.Month,
                                       ComputerDepartment = myGroup.Where(x => x.Department == "Computer").Sum(x => x.Expenses),
                                       MathDepartment = myGroup.Where(x => x.Department == "Math").Sum(x => x.Expenses),
                                       PhysicsDepartment = myGroup.Where(x => x.Department == "Physics").Sum(x => x.Expenses)
                                   })
                        .ToList();
        }
    }
}

كه اينبار خروجي زير را توليد مي‌كند.


اگر علاقمند باشيد كه مثال فوق را در برنامه‌ي LINQPad آزمايش كنيد، اين فايل را دريافت نموده و در آن برنامه باز نمائيد.


مثال دوم) تهيه ليست Crosstab حضور و غياب افراد در طول يك هفته

كلاس StudentStat را جهت ثبت اطلاعات حضور يك دانشجو، مي‌توان به شكل زير تعريف كرد:

using System;

namespace Pivot.Sample2
{
    public class StudentStat
    {
        public int Id { set; get; }
        public string Name { set; get; }
        public DateTime Date { set; get; }
        public bool IsPresent { set; get; }
    }
}

و بر همين اساس يك منبع داده فرضي جهت انجام گزارشات مي‌تواند به نحو زير تهيه شود:

using System;
using System.Collections.Generic;

namespace Pivot.Sample2
{
    public class StudentsStatDataSource
    {
        public static IList<StudentStat> CreateMonthlyReportDataSource()
        {
            var result = new List<StudentStat>();
            var rnd = new Random();

            for (int day = 1; day < 6; day++)
            {
                for (int student = 1; student < 6; student++)
                {
                    result.Add(new StudentStat
                    {
                        Id = student,
                        Date = new DateTime(2011, 11, day),
                        IsPresent = rnd.Next(-1, 1) == 0 ? true : false,
                        Name = "student " + student
                    });
                }
            }

            return result;
        }
    }
}

خروجي اين گزارش هم در اين حالت ساده با 5 دانشجو و فقط 5 روز، 25 ركورد خواهد بود:


كه ... اين هم آنچنان از لحاظ آماري مطلوب و مفهوم نيست. مي‌خواهيم سطرهاي اين گزارش همانند ليست واقعي حضورغياب، فقط از نام افراد تشكيل شود و همچنين ستون‌ها مثلا شماره يا نام روزهاي يك هفته يا ماه باشند. مثلا به شكل زير:


براي رسيدن به اين خروجي Crosstab، مثلا مي‌توان از كوئري LINQ زير كمك گرفت كه بر اساس شماره دانشجويي اطلاعات را گروه بندي كرده است:

using System.Collections;
using System.Linq;

namespace Pivot.Sample2
{
    public class PivotTable
    {
        public static IList StudentsStatCrossTab()
        {
            return StudentsStatDataSource
                        .CreateWeeklyReportDataSource()
                        .GroupBy(x =>
                                  new
                                  {
                                      x.Id
                                  })
                        .Select(myGroup =>
                                  new
                                  {
                                      myGroup.Key.Id,
                                      Name = myGroup.First().Name,
                                      Day1IsPresent = myGroup.Where(x => x.Date.Day == 1).First().IsPresent,
                                      Day2IsPresent = myGroup.Where(x => x.Date.Day == 2).First().IsPresent,
                                      Day3IsPresent = myGroup.Where(x => x.Date.Day == 3).First().IsPresent,
                                      Day4IsPresent = myGroup.Where(x => x.Date.Day == 4).First().IsPresent,
                                      Day5IsPresent = myGroup.Where(x => x.Date.Day == 5).First().IsPresent,
                                      PresentsCount = myGroup.Where(x => x.IsPresent).Count(),
                                      AbsentsCount = myGroup.Where(x => !x.IsPresent).Count()
                                  })
                        .ToList();
        }
    }
}

و اين كوئري خروجي زير را توليد مي‌كند كه از هر لحاظ نسبت به ليست قبلي مفهوم‌تر است:


فايل LINQPad اين مثال را مي‌توانيد از اينجا دريافت كنيد.

خلاصه اشتراک‌های روز يك شنبه 22 آبان 1390

۱۳۹۰/۰۸/۲۱

خلاصه اشتراک‌های روز شنبه 21 آبان 1390

رمزنگاري فايل‌هاي PDF با استفاده از كليد عمومي توسط iTextSharp


دو نوع رمزنگاري را مي‌توان توسط iTextSharp به PDF توليدي و يا موجود، اعمال كرد:
الف) رمزنگاري با استفاده از كلمه عبور
ب) رمزنگاري توسط كليد عمومي

الف) رمزنگاري با استفاده از كلمه عبور
در اينجا امكان تنظيم read password و edit password به كمك متد SetEncryption شيء pdfWrite وجود دارد. همچنين مي‌توان مشخص كرد كه مثلا آيا كاربر مي‌تواند فايل PDF را چاپ كند يا خير (PdfWriter.ALLOW_PRINTING).
ذكر read password اختياري است؛ اما جهت اعمال permissions حتما نياز است تا edit password ذكر گردد:

using System.Diagnostics;
using System.IO;
using iTextSharp.text;
using iTextSharp.text.pdf;
using System.Text;

namespace EncryptPublicKey
{
    class Program
    {
        static void Main(string[] args)
        {
            using (var pdfDoc = new Document(PageSize.A4))
            {
                var pdfWriter = PdfWriter.GetInstance(pdfDoc, new FileStream("Test.pdf", FileMode.Create));

                var readPassword = Encoding.UTF8.GetBytes("123");//it can be null.
                var editPassword = Encoding.UTF8.GetBytes("456");
                int permissions = PdfWriter.ALLOW_PRINTING | PdfWriter.ALLOW_COPY;
                pdfWriter.SetEncryption(readPassword, editPassword, permissions, PdfWriter.STRENGTH128BITS);

                pdfDoc.Open();

                pdfDoc.Add(new Phrase("tst 0"));
                pdfDoc.NewPage();
                pdfDoc.Add(new Phrase("tst 1"));
            }

            Process.Start("TestEnc.pdf");
        }
    }
}


اگر read password ذكر شود، كاربران براي مشاهده محتويات فايل نياز خواهند داشت تا كلمه‌ي عبور مرتبط را وارد نمايند:


اين روش آنچنان امنيتي ندارد. هستند برنامه‌هايي كه اين نوع فايل‌ها را «آني» به نمونه‌ي غير رمزنگاري شده تبديل مي‌كنند (حتي نيازي هم ندارند كه از شما كلمه‌ي عبوري را سؤال كنند). بنابراين اگر كاربران شما آنچنان حرفه‌اي نيستند، اين روش خوب است؛ در غيراينصورت از آن صرفنظر كنيد.


ب) رمزنگاري توسط كليد عمومي
اين روش نسبت به حالت الف بسيار پيشرفته‌تر بوده و امنيت قابل توجهي هم دارد و «نيستند» برنامه‌هايي كه بتوانند اين فايل‌ها را بدون داشتن اطلاعات كافي، به سادگي رمزگشايي كنند.

براي شروع به كار با public key encryption نياز است يك فايل PFX يا Personal Information Exchange داشته باشيم. يا مي‌توان اين نوع فايل‌ها را از CA's يا Certificate Authorities خريد، كه بسيار هم نيكو يا اينكه مي‌توان فعلا براي آزمايش، نمونه‌ي self signed اين‌ها را هم تهيه كرد. مثلا با استفاده از اين برنامه.


در ادامه نياز خواهيم داشت تا اطلاعات اين فايل PFX را جهت استفاده توسط iTextSharp استخراج كنيم. كلاس‌هاي زير اينكار را انجام مي‌دهند و نهايتا كليدهاي عمومي و خصوصي ذخيره شده در فايل PFX را بازگشت خواهند داد:

using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.X509;

namespace EncryptPublicKey
{
    /// <summary>
    /// A Personal Information Exchange File Info
    /// </summary>
    public class PfxData
    {
        /// <summary>
        /// Represents an X509 certificate
        /// </summary>
        public X509Certificate[] X509PrivateKeys { set; get; }

        /// <summary>
        /// Certificate's public key
        /// </summary>
        public ICipherParameters PublicKey { set; get; }
    }
}

using System;
using System.IO;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Pkcs;
using Org.BouncyCastle.X509;

namespace EncryptPublicKey
{
    /// <summary>
    /// A Personal Information Exchange File Reader
    /// </summary>
    public class PfxReader
    {
        X509Certificate[] _chain;
        AsymmetricKeyParameter _asymmetricKeyParameter;
       
        /// <summary>
        /// Reads A Personal Information Exchange File.
        /// </summary>
        /// <param name="pfxPath">Certificate file's path</param>
        /// <param name="pfxPassword">Certificate file's password</param>
        public PfxData ReadCertificate(string pfxPath, string pfxPassword)
        {
            using (var stream = new FileStream(pfxPath, FileMode.Open, FileAccess.Read))
            {
                var pkcs12Store = new Pkcs12Store(stream, pfxPassword.ToCharArray());
                var alias = findThePublicKey(pkcs12Store);
                _asymmetricKeyParameter = pkcs12Store.GetKey(alias).Key;
                constructChain(pkcs12Store, alias);
                return new PfxData { X509PrivateKeys = _chain, PublicKey = _asymmetricKeyParameter };
            }
        }

        private void constructChain(Pkcs12Store pkcs12Store, string alias)
        {
            var certificateChains = pkcs12Store.GetCertificateChain(alias);
            _chain = new X509Certificate[certificateChains.Length];

            for (int k = 0; k < certificateChains.Length; ++k)
                _chain[k] = certificateChains[k].Certificate;
        }

        private static string findThePublicKey(Pkcs12Store pkcs12Store)
        {
            string alias = string.Empty;
            foreach (string entry in pkcs12Store.Aliases)
            {
                if (pkcs12Store.IsKeyEntry(entry) && pkcs12Store.GetKey(entry).Key.IsPrivate)
                {
                    alias = entry;
                    break;
                }
            }

            if (string.IsNullOrEmpty(alias))
                throw new NullReferenceException("Provided certificate is invalid.");

            return alias;
        }
    }
}


اكنون رمزنگاري فايل PDF توليدي توسط كليد عمومي، به سادگي چند سطر كد زير خواهد بود:

using System.Diagnostics;
using System.IO;
using iTextSharp.text;
using iTextSharp.text.pdf;

namespace EncryptPublicKey
{
    class Program
    {
        static void Main(string[] args)
        {
            using (var pdfDoc = new Document(PageSize.A4))
            {
                var pdfWriter = PdfWriter.GetInstance(pdfDoc, new FileStream("Test.pdf", FileMode.Create));

                var certs = new PfxReader().ReadCertificate(@"D:\path\cert.pfx", "123");
                pdfWriter.SetEncryption(
                          certs: certs.X509PrivateKeys,
                          permissions: new int[] { PdfWriter.ALLOW_PRINTING, PdfWriter.ALLOW_COPY },
                          encryptionType: PdfWriter.ENCRYPTION_AES_128);

                pdfDoc.Open();

                pdfDoc.Add(new Phrase("tst 0"));
                pdfDoc.NewPage();
                pdfDoc.Add(new Phrase("tst 1"));
            }

            Process.Start("Test.pdf");
        }
    }
}

پيش از فراخواني متد Open بايد تنظيمات رمزنگاري مشخص شوند. در اينجا ابتدا فايل PFX خوانده شده و كليدهاي عمومي و خصوصي آن استخراج مي‌شوند. سپس به متد SetEncryption جهت استفاده نهايي ارسال خواهند شد.

نحوه استفاده از اين نوع فايل‌هاي رمزنگاري شده:
اگر سعي در گشودن اين فايل رمزنگاري شده نمائيم با خطاي زير مواجه خواهيم شد:


كاربران براي اينكه بتوانند اين فايل‌هاي PDF را بار كنند نياز است تا فايل PFX شما را در سيستم خود نصب كنند. ويندوز فايل‌هاي PFX را مي‌شناسد و نصب آن‌ها با دوبار كليك بر روي فايل و چندبار كليك بر روي دكمه‌ي Next و وارد كردن كلمه عبور آن، به پايان مي‌رسد.

سؤال: آيا مي‌توان فايل‌هاي PDF موجود را هم به همين روش رمزنگاري كرد؟
بله. iTextSharp علاوه بر PdfWriter داراي PdfReader نيز مي‌باشد:

using System.Diagnostics;
using System.IO;
using iTextSharp.text;
using iTextSharp.text.pdf;

namespace EncryptPublicKey
{
    class Program
    {
        static void Main(string[] args)
        {
            PdfReader reader = new PdfReader("TestDec.pdf");
            using (var stamper = new PdfStamper(reader, new FileStream("TestEnc.pdf", FileMode.Create)))
            {
                var certs = new PfxReader().ReadCertificate(@"D:\path\cert.pfx", "123");
                stamper.SetEncryption(
                         certs: certs.X509PrivateKeys,
                         permissions: new int[] { PdfWriter.ALLOW_PRINTING, PdfWriter.ALLOW_COPY },
                         encryptionType: PdfWriter.ENCRYPTION_AES_128);
                stamper.Close();
            }

            Process.Start("TestEnc.pdf");
        }
    }
}


سؤال: آيا مي‌توان نصب كليد عمومي را خودكار كرد؟
سورس برنامه SelfCert كه معرفي شد، در دسترس است. اين برنامه قابليت انجام نصب خودكار مجوزها را دارد.

۱۳۹۰/۰۸/۱۹

خلاصه اشتراک‌های روز پنج شنبه 19 آبان 1390


آشنايي با Refactoring - قسمت 13


يكي از مواردي كه حين كار كردن با iTextSharp واقعا اعصاب خردكن است، طراحي نامناسب ثوابت اين كتابخانه مي‌باشد. براي مثال:


public class PdfWriter
{
        /** A viewer preference */
        public const int PageLayoutSinglePage = 1;
        /** A viewer preference */
        public const int PageLayoutOneColumn = 2;
        /** A viewer preference */
        public const int PageLayoutTwoColumnLeft = 4;
        /** A viewer preference */
        public const int PageLayoutTwoColumnRight = 8;
        /** A viewer preference */
        public const int PageLayoutTwoPageLeft = 16;
        /** A viewer preference */
        public const int PageLayoutTwoPageRight = 32;

        // page mode (section 13.1.2 of "iText in Action")
        
        /** A viewer preference */
        public const int PageModeUseNone = 64;
        /** A viewer preference */
        public const int PageModeUseOutlines = 128;
        /** A viewer preference */
        public const int PageModeUseThumbs = 256;
        /** A viewer preference */
        public const int PageModeFullScreen = 512;
        /** A viewer preference */
        public const int PageModeUseOC = 1024;
        /** A viewer preference */
        public const int PageModeUseAttachments = 2048;

        //...
        //...
}

6 ثابت اول مربوط به گروه PageLayout هستند و 6 ثابت دوم به گروه PageMode ارتباط دارند و اين كلاس پر است از اين نوع ثوابت (اين كلاس نزديك به 3200 سطر است!). اين نوع طراحي نامناسب است. بجاي گروه بندي خواص يا ثوابت با يك پيشوند، مثلا PageLayout يا PageMode، اين‌ها را به كلاس‌ها يا در اينجا (حين كار با ثوابت عددي) به enum‌هاي متناظر خود منتقل و Refactor كنيد. مثلا:

public enum ViewerPageLayout
{
     SinglePage = 1,
     OneColumn = 2,
     TwoColumnLeft = 4,
     TwoColumnRight = 8,
     TwoPageLeft = 16,
     TwoPageRight = 32
}

مزيت‌ها:
- طبقه بندي منطقي ثوابت در يك enum و گروه بندي صحيح آن‌ها، بجاي گروه بندي توسط يك پيشوند
- استفاده بهينه از intellisense در visual studio
- منسوخ سازي استفاده از اعداد بجاي معرفي ثوابت خصوصا عددي (در اين كتابخانه شما مي‌توانيد بنويسيد PdfWriter.PageLayoutSinglePage و يا 1 و هر دو صحيح هستند؛ اين خوب نيست. ترويج استفاده از اصطلاحا magic numbers هم حين طراحي يك كتابخانه مذموم است.)
- كم شدن حجم كلاس اوليه (مثلا كلاس PdfWriter در اينجا) و در نتيجه نگهداري ساده‌تر آن در طول زمان