مطالب
بررسی تصویر امنیتی (Captcha) سایت - قسمت اول

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

تصویر آشنای کاربران سایت برای ورود به قسمت مدیریت که در صفحه مورد نظر از تصویر امنیتی استفاده شده است:

  با توجه به آدرس لینک تصویر مشخص است که برای تولید این تصویر از هندلر استفاده شده و با توجه به پارامترهایی که به آن داده شده تصویر مورد نظر را ایجاد میکند و با فرمت مشخصی بر میگرداند: 

https://www.dntips.ir
/CaptchaImage/Show?
text=H2yL5iOXIuu0dsBvu%2F405AnXkeacis3dMQ%2FHXAH8h4A%2BjwDM0f7%2FRZMHvBG5pXYJ
&foreColor=%231B0172
&fontSize=12
&fontName=Tahoma

پارامتر‌های پاس داده شده به ترتیب ذیل برای اهدافی قرار داده شده اند:

نام

مورد استفاده

مقدار

text

متنی که بصورت کد شده حاوی متن مورد استفاده تصویر امنیتی است

H2yL5iOXIuu0dsBvu%2F405AnXkeacis3dMQ%2FHXAH8h4A%2BjwDM0f7%2FRZMHvBG5pXYJ

foreColor

رنگ مربوط به نوشته‌های تصویر

%231B0172

fontSize

سایز فونت

12

fontName

نام فونت

Tahoma

بنظر اهم پارامتر مورد نیاز همان متن است که بصورت کد شده در بالا به آن اشاره شد. برای این منظور باید کلاسی تهیه شود که حاوی متدهای:
1- انتخاب یه عدد تصادفی از بین دامنه ای که به آن داده میشود.
    برای مثال یه عد از بین 100 تا 9999 که بصورت تصادفی انتخاب میشود
2- تبدیل به معادل حرفی آن
    برای مثال از عدد "7062" به حروف آن یعنی "هفت هزار و شصت و دو" استخراج شود
3- کد کردن حرف تولید شده
    حرف "هفت هزار و شصت و دو" را کد کرده و بصورت متنی شبیه
    "H2yL5iOXIuu0dsBvu%2F405AnXkeacis3dMQ%2FHXAH8h4A%2BjwDM0f7%2FRZMHvBG5pXYJ "
    تبدیل کند
4- دیکد کردن و استخراج
    البته این مورد با توجه به استفاده ای که ما داریم نیازی به پیاده سازی نیست

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

مطالب
iTextSharp و استفاده از قلم‌های محدود فارسی

عموما قلم‌های فارسی، خصوصا مواردی که با B شروع می‌شوند مانند B Zar و امثال آن، فاقد تعاریف حروف مرتبط با glyphs الفبای انگلیسی است. نتیجه این خواهد شد که اگر متن شما مخلوطی از کلمات و حروف فارسی و انگلیسی باشد، فقط قسمت فارسی نمایش داده می‌شود و از قسمت انگلیسی صرفنظر خواهد شد. مرورگرها در این حالت هوشمندانه عمل می‌کنند و به یک قلم پیش فرض مانند Times و همانند آن جهت نمایش اینگونه متون مراجعه خواهند کرد؛ اما اینجا چنین اتفاقی نخواهد افتاد.
برای حل این مشکل، کلاسی به نام FontSelector در کتابخانه‌ی iTextSharp وجود دارد. مثالی در این رابطه:

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

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

FontFactory.Register("c:\\windows\\fonts\\bzar.ttf");
Font bZar = FontFactory.GetFont("b zar", BaseFont.IDENTITY_H);

FontFactory.Register("c:\\windows\\fonts\\tahoma.ttf");
Font tahoma = FontFactory.GetFont("tahoma", BaseFont.IDENTITY_H);

FontSelector fontSelector = new FontSelector();

//قلم اصلی
if (bZar.Familyname != "unknown")
{
fontSelector.AddFont(bZar);
}

//قلم پیش فرض در صورت نبود تعاریف مناسب در قلم اصلی
if (tahoma.Familyname != "unknown")
{
fontSelector.AddFont(tahoma);
}

var table1 = new PdfPTable(1);
table1.WidthPercentage = 100;
table1.RunDirection = PdfWriter.RUN_DIRECTION_RTL;

var pdfCell = new PdfPCell { RunDirection = PdfWriter.RUN_DIRECTION_RTL, Border = 0 };
pdfCell.Phrase = fontSelector.Process("نمایش مخلوطی از متن فارسی و English با هم توسط قلمی که کاراکترهای انگلیسی را پشتیبانی نمی‌کند");

table1.AddCell(pdfCell);
pdfDoc.Add(table1);

}

//open the final file with adobe reader for instance.
Process.Start("Test.pdf");
}
}
}

در این مثال از قلم B Zar استفاده شده است. اولین قلمی که به یک FontSelector اضافه می‌شود، قلم اصلی خواهد بود. قلم‌ بعدی اضافه شده، قلم پیش فرض نام خواهد گرفت؛ به این معنا که در مثال فوق اگر قلم B Zar توانایی نمایش حرف جاری را داشت که خیلی هم خوب، در غیراینصورت به قلم بعدی مراجعه خواهد کرد و همینطور الی آخر. بنابراین این ترتیب اضافه کردن قلم‌ها به FontSelector مهم است. نحوه استفاده نهایی از FontSelector تعریف شده هم در قسمت pdfCell.Phrase = fontSelector.Process مشخص است.



مطالب
کنترل DatePicker شمسی مخصوص Silverlight 4

Silverlight 4 تاریخ شمسی را از دات نت فریم ورک به ارث نبرده است (+). اما اضافه کردن آن کار خاصی نیست. مجموعه‌ی سورس باز Silverlight toolkit هم دارای DatePicker تاریخ میلادی است اما به دلایلی که عرض شد، تاریخ شمسی را پشتیبانی نمی‌کند.

کارهایی که توسط سایر برنامه نویس‌های ایرانی تابحال در این مورد انجام شده است:
- اضافه کردن DatePicker فارسی به مجموعه‌ی Silverlight toolkit : (+)
به دو دلیل من از این راه حل استفاده نخواهم کرد:
الف) patch ارائه شده هنوز با Silverlight toolkit یکپارچه نشده است و هربار باید این تغییرات را اعمال کرد و غیره ...
ب) شاید من اصلا نخواهم که از Silverlight toolkit استفاده کنم. آن وقت چه باید کرد؟
این تنها کاری است که جهت Silverlight انجام شده است.

دو نمونه‌ی خوب دیگر هم برای WPF موجود است که تبدیل آن‌ها به Silverlight کار ساده‌ای نیست (چون Silverlight تمام کلاس‌های WPF را نیز به ارث نبرده است):
- Farsi Library - Working with Dates, Calendars, and DatePickers
- PersianDate and some WPF controls for it

به همین جهت یک کنترل DatePicker و تقویم شمسی مستقل را برای Silverlight 4 آماده کرده‌ام که از آدرس ذیل قابل دریافت است:





نحوه استفاده:
الف) ارجاعی را به اسمبلی SilverlightPersianDatePicker.dll به پروژه خود اضافه کنید. اگر مباحث library caching هم برای شما مهم است، فایل SilverlightPersianDatePicker.extmap.xml پیوست شده را نیز فراموش نکنید.
ب) xmlns آن باید به XAML جاری اضافه شود؛ برای مثال:
xmlns:dp="clr-namespace:SilverlightPersianDatePicker.Views;assembly=SilverlightPersianDatePicker"
ج) سپس استفاده از آن به سادگی یک سطر زیر خواهد بود:
<dp:PDatePicker x:Name="txtDate" TextBoxWidth="100"  Margin="5"  />

خاصیت SelectedDate آن تاریخ میلادی و خاصیت SelectedPersianDate آن تاریخ شمسی را بر می‌گرداند.


کتابخانه‌های کمکی که در حین توسعه‌ی آن استفاده شدند:
کلاس تقویم شمسی امید خندان راد (که برای روزهای دات نت 1 تهیه شده بود).
پنل UniformGrid که در Silverlight موجود نیست.(+)
رفتار StaysOpen مرتبط با Popup که در Silverlight از WPF به ارث نرسیده است.(+)
استفاده از کلاس DelegateCommand جان پاپا (برای سهولت Commanding در الگوی MVVM). (+)


مطالب
لینک‌های هفته‌ی دوم اسفند

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

امنیت

Visual Studio

ASP. Net

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

PHP

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

سی شارپ

VB

CPP

عمومی دات نت

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

متفرقه
مطالب
آیا از وضعیت رویه‌های ذخیره شده‌ی دیتابیس‌های اس کیوال سرور خود خبر دارید؟

به لطف امکانات سیستمی اس کیوال سرورهای 2005 به بعد و DMV های آن‌ها، آمارگیری از ریز اتفاقات رخ داده در یک اس کیوال سرور این روزها بسیار ساده شده است و نیازی به ابزارهای جانبی برای انجام این نوع عملیات نیست (یا کمتر هست). در ادامه مروری خواهیم داشت بر یک سری کوئری که اطلاعات جالبی را در مورد وضعیت رویه‌های ذخیره شده‌ی دیتابیس‌های شما ارائه می‌دهند. لازم به ذکر است که اکثر این آمارها با هر بار ری استارت سرور، صفر خواهند شد.

آیا می‌دانید در یک دیتابیس خاص کدامیک از رویه‌های ذخیره شده‌ی شما بیش از سایرین مورد استفاده بود و آماری از این دست؟

use dbName;
SELECT TOP(100) qt.text AS 'SP Name',
qs.execution_count AS 'Execution Count',
qs.execution_count / DATEDIFF(Second, qs.creation_time, GETDATE()) AS
'Calls/Second',
qs.total_worker_time / qs.execution_count AS 'AvgWorkerTime',
qs.total_worker_time AS 'TotalWorkerTime',
qs.total_elapsed_time / qs.execution_count AS 'AvgElapsedTime',
qs.max_logical_reads,
qs.max_logical_writes,
qs.total_physical_reads,
DATEDIFF(Minute, qs.creation_time, GETDATE()) AS 'Age in Cache'
FROM sys.dm_exec_query_stats AS qs
CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) AS qt
WHERE qt.dbid = DB_ID() -- Filter by current database
ORDER BY
qs.execution_count DESC

البته مرتب سازی پیش فرض این کوئری بر اساس تعداد بار اجرا است (رویه‌های ذخیره شده‌ی محبوب!)، می‌شود آن‌را بر اساس total_worker_time (فشار بر روی CPU سیستم)، total_logical_reads (فشار بر روی حافظه)، total_physical_reads (فشار I/O کوئری‌ها)، total_logical_writes نیز مرتب کرد و نتایج جالب توجهی را بدست آورد.


آیا می‌دانید کدامیک از رویه‌های ذخیره شده‌ی شما بیش از سایرین کامپایل مجدد شده است؟

select top 50
sql_text.text,
sql_handle,
plan_generation_num,
execution_count,
dbid,
objectid
from
sys.dm_exec_query_stats a
cross apply sys.dm_exec_sql_text(sql_handle) as sql_text
where
plan_generation_num >1
order by plan_generation_num desc

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

SELECT NAME,
create_date,
modify_date
FROM sys.objects
WHERE TYPE = 'P'
ORDER BY
Modify_Date DESC,
NAME

مطالب
واکشی اطلاعات سرویس Web Api با استفاده از TypeScript و AngularJs
در پست‌های قبلی با TypeScript، AngularJs و Web Api آشنا شدید. در این پست قصد دارم از ترکیب این موارد برای پیاده سازی عملیات واکشی اطلاعات سرویس Web Api در قالب یک پروژه استفاده نمایم. برای شروع ابتدا یک پروژه Asp.Net MVC ایجاد کنید.
در قسمت مدل ابتدا یک کلاس پایه برای مدل ایجاد خواهیم کرد:
public abstract class Entity
    {
        public Guid Id { get; set; }
    }
حال کلاسی به نام Book ایجاد می‌کنیم:
public class Book : EntityBase
    {
        public string Name { get; set; }
        public decimal Author { get; set; }
    }
در پوشه مدل یک کلاسی به نام BookRepository ایجاد کنید و کد‌های زیر را در آن کپی نمایید(به جای پیاده سازی بر روی بانک اطلاعاتی، عملیات بر روی لیست درون حافظه انجام می‌گیرد):
 public class BookRepository
    {
        private readonly ConcurrentDictionary<Guid, Book> result = new ConcurrentDictionary<Guid, Book>();

        public IQueryable<Book> GetAll()
        {
            return result.Values.AsQueryable();
        }        

        public Book Add(Book entity)
        {
            if (entity.Id == Guid.Empty) entity.Id = Guid.NewGuid();

            if (result.ContainsKey(entity.Id)) return null;

            if (!result.TryAdd(entity.Id, entity)) return null;

            return entity;
        }     
    }

نوبت به کلاس کنترلر می‌رسد. یک کنترلر Api به نام BooksController ایجاد کنید و سپس کد‌های زیر را در آن کپی نمایید:
 public class BooksController : ApiController
    {
        public static BookRepository repository = new BookRepository();       

public BooksController()
        {
            repository.Add(new Book 
            {
                Id=Guid.NewGuid(),
                Name="C#",
                Author="Masoud Pakdel"
            });

            repository.Add(new Book
            {
                Id = Guid.NewGuid(),
                Name = "F#",
                Author = "Masoud Pakdel"
            });

            repository.Add(new Book
            {
                Id = Guid.NewGuid(),
                Name = "TypeScript",
                Author = "Masoud Pakdel"
            });
        }

        public IEnumerable<Book> Get()
        {
            return repository.GetAll().ToArray();
        }          
    }

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

حال نویت به عملیات سمت کلاینت میرسد. برای استفاده از قابلیت‌های TypeScript و AngularJs در Vs.Net از این مقاله کمک بگیرید. بعد از آماده سازی در فولدر script، پوشه ای به نام app می‌سازیم و یک فایل TypeScript به نام  BookModel  در آن ایجاد می‌کنیم:
module Model {
    export class Book{
        Id: string;
        Name: string;
        Author: string;
    }
}
واضح است که ماژولی به نام Model داریم که در آن کلاسی به نام Book ایجاد شده است. برای انتقال اطلاعات از طریق سرویس http$ در Angular نیاز به سریالایز کردن این کلاس به فرمت Json خواهیم داشت. قصد داریم View مورد نظر را به صورت زیر ایجاد نماییم:
 <div ng-controller="Books.Controller">       
        <table class="table table-striped table-hover" style="width: 500px;">
            <thead>
                <tr>
                    <th>Name</th>
                    <th>Author</th>              
                </tr>
            </thead>
            <tbody>
                <tr ng-repeat="book in books">
                    <td>{{book.Name}}</td>
                    <td>{{book.Author}}</td>                                     
                </tr>
            </tbody>
        </table>
    </div>

توضیح کد‌های بالا:
ابتدا یک کنترلری که به نام Controller که در ماژولی به نام Book تعریف شده است باید ایجاد شود. اطلاعات تمام کتب ثبت شده باید از سرویس مورد نظر دریافت و با یک ng-repeat در جدول نمایش داده خواهند شود.
در پوشه app یک فایل TypeScript دیگر برای تعریف برخی نیازمندی‌ها به نام  AngularModule ایجاد می‌کنیم که کد آن به صورت زیر خواهد بود:
declare module AngularModule {
    export interface HttpPromise {
        success(callback: Function) : HttpPromise;       
    }
    export interface Http {
        get(url: string): HttpPromise;   
    }
}
در این ماژول دو اینترفیس تعریف شده است. اولی به نام HttpPromise است که تابعی به نام success  دارد. این تابع باید بعد از موفقیت آمیز بودن  عملیات فراخوانی شود. ورودی آن از نوع Function است. بعنی اجازه تعریف یک تابع را به عنوان ورودی برای این توابع دارید.
در اینترفیس Http نیز تابعی به نام get تعریف شده  است که  برای دریافت اطلاعات از سرویس api، مورد استفاده قرار خواهد گرفت. از آن جا که تعریف توابع در اینترفیس فاقد بدنه است در نتیجه این جا فقط امضای توابع مشخص خواهد شد. پیاده سازی توابع به عهده کنترلر‌ها خواهد بود:
مرحله بعد مربوط است به تعریف کنترلری  به نام BookController تا اینترفیس بالا را پیاده سازی نماید. کد‌های آن به صورت زیر خواهد بود:
/// <reference path='AngularModule.ts' />
/// <reference path='BookModel.ts' />

module Books {
    export interface Scope {        
        books: Model.Book[];
    }

    export class Controller {
        private httpService: any;

        constructor($scope: Scope, $http: any) {
            this.httpService = $http;

            this.getAllBooks(function (data) {
                $scope.books = data;
            });
            var controller = this;
    }

        getAllBooks(successCallback: Function): void {
            this.httpService.get('/api/books').success(function (data, status) {
                successCallback(data);
            });
        }
    }
}


توضیح کد‌های بالا:
برای دسترسی به تعاریف انجام شده در سایر ماژول‌ها باید ارجاعی به فایل تعاریف ماژول‌های مورد نظر داشته باشیم. در غیر این صورت هنگام استفاده از این ماژول‌ها با خطای کامپایلری روبرو خواهیم شد. عملیات ارجاع به صورت زیر است:
/// <reference path='AngularModule.ts' />
/// <reference path='BookModel.ts' />
در پست قبلی توضیح داده شد که برای مقید سازی عناصر بهتر است یک اینترفیس به نام Scope تعریف کنیم تا بتوانیم متغیر‌های مورد نظر برای مقید سازی را در آن تعریف نماییم در این جا تعریف آن به صورت زیر است:
export interface Scope {  
        books: Model.Book[];      
    }
در این جا فقط نیاز به لیستی از کتاب‌ها داریم تا بتوان در جدول مورد نظر در View آنرا پیمایش کرد. تابعی به نام getAllBooks در کنترلر مورد نظر نوشته شده است که ورودی آن یک تابع خواهد بود که باید بعد از واکشی اطلاعات از سرویس، فراخوانی شود. اگر به کد‌های بالا دقت کنید می‌بینید که در ابتدا سازنده کنترلر،سرویس http$ موجود در Angular به متغیری به نام httpService نسبت داده می‌شود. با فراخوانی تابع get و ارسال آدرس سرویس که با توجه به مقدار مسیر یابی پیش فرض کلاس WebApiConfig باید با api شروع شود به راحتی اطلاعات مورد نظر به دست خواهد آمد. بعد از واکشی در صورت موفقیت آمیز بودن عملیات تابع success اجرا می‌شود که نتیجه آن انتساب مقدار به دست آمده به متغیر books تعریف شده در scope$ می‌باشد.

در نهایت خروجی به صورت زیر خواهد بود:


سورس پیاده سازی مثال بالا در Visual Studio 2013
مطالب
تخمین مدت زمان خوانده شدن یک مطلب
پس از انتشار مطلب «Pro Agile .NET Development With Scrum - قسمت اول» شاید این سؤال در ابتدای کار برای خواننده پیش بیاید که ... چقدر باید برای خواندن آن وقت بگذارم؟ برای پاسخ به این سؤال باید درنظر داشت که یک انسان معمولی، می‌تواند بین 200 تا 250 کلمه را در دقیقه، مطالعه کند. بنابراین در ابتدا باید محاسبه کرد که یک متن، چه تعدادی کلمه دارد؟
شاید عنوان کنید که کافی است متن ورودی را بر اساس فاصله‌ی بین کلمات تقسیم بندی کرده و سپس تعداد کلمات بدست آمده را محاسبه کنیم:
 var words = text.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
return words.Length;
این روش با آزمون زیر کار نکرده و با شکست مواجه می‌شود:
[TestMethod]
public void TestInvalidChars()
{
    const string data = "To be . ! < > ( ) ! ! , ; : ' ? + -";
    Assert.AreEqual(2, data.WordsCount());
}
در اینجا ! ، و امثال آن نیز یک کلمه درنظر گرفته می‌شوند. برای حل این مشکل کافی است آرایه‌ی split را کمی تکمیل‌تر کنیم تا حروف غیرمجاز را درنظر نگیرد:
 var words = text.Split(
    new[] { ' ', ',', ';', '.', '!', '"', '(', ')', '?', ':', '\'', '«' , '»', '+', '-' },
    StringSplitOptions.RemoveEmptyEntries);
return words.Length;
تا اینجا مشکل !، >< حل شد، اما در مورد متن ذیل چطور؟
[TestMethod]
public void TestSimpleHtmlSpacesWithNewLine()
{
    const string data = "<b>this is&nbsp;a&nbsp;&nbsp;test.</b>\n\r<b>this is&nbsp;a&nbsp;&nbsp;test.</b>";
    Assert.AreEqual(8, data.WordsCount());
}
مطالب ثبت شده، عموما توسط HTML Editorها ثبت می‌شوند. بنابراین دارای انواع و اقسام تگ‌ها بوده و همچنین ممکن است در این بین new line هم وجود داشته باشد که در این حالت، test\n\rtest باید دو کلمه محاسبه شود و نه یک کلمه.
اگر این موارد را در نظر بگیریم، به کلاس ذیل خواهیم رسید:
using System;
using System.Text.RegularExpressions;
 
namespace ReadingTime
{
    public static class CalculateWordsCount
    {
        private static readonly Regex _matchAllTags =
            new Regex(@"<(.|\n)*?>", RegexOptions.IgnoreCase | RegexOptions.Compiled);
 
        public static int WordsCount(this string text)
        {
            if (string.IsNullOrWhiteSpace(text))
            {
                return 0;
            }
 
            text = text.cleanTags().Trim();
            text = text.Replace("\t", " ");
            text = text.Replace("\n", " ");
            text = text.Replace("\r", " ");
 
            var words = text.Split(
                new[] { ' ', ',', ';', '.', '!', '"', '(', ')', '?', ':', '\'', '«' , '»', '+', '-' },
                StringSplitOptions.RemoveEmptyEntries);
            return words.Length;
        }
 
        private static string cleanTags(this string data)
        {
            return data.Replace("\n", "\n ").removeHtmlTags();
        }
 
        private static string removeHtmlTags(this string text)
        {
            return string.IsNullOrEmpty(text) ?
                        string.Empty :
                        _matchAllTags.Replace(text, " ").Replace("&nbsp;", " ");
        }
    }
}
در اینجا حذف تگ‌های HTML و همچنین پردازش خطوط جدید و حروف غیرمجاز درنظر گرفته شده‌اند.

پس از اینکه موفق به شمارش تعداد کلمات یک متن HTML ایی شدیم، اکنون می‌توان این تعداد را تقسیم بر 180 (یک عدد معمول و متداول) کرد تا زمان خواندن کل متن بدست آید. سپس با استفاده از متد toReadableString می‌توان آن‌را به شکل قابل خواندن‌تری نمایش داد.
using System;
 
namespace ReadingTime
{
    public static class CalculateReadingTime
    {
        public static string MinReadTime(this string text, int wordsPerMinute = 180)
        {
            var wordsCount = text.WordsCount();
            var minutes = wordsCount / wordsPerMinute;
            return minutes == 0 ? "کمتر از یک دقیقه" : TimeSpan.FromMinutes(minutes).toReadableString();
        }
 
        private static string toReadableString(this TimeSpan span)
        {
            var formatted = string.Format("{0}{1}{2}{3}",
                span.Duration().Days > 0 ? string.Format("{0:0} روز و ", span.Days) : string.Empty,
                span.Duration().Hours > 0 ? string.Format("{0:0} ساعت و ", span.Hours) : string.Empty,
                span.Duration().Minutes > 0 ? string.Format("{0:0} دقیقه و ", span.Minutes) : string.Empty,
                span.Duration().Seconds > 0 ? string.Format("{0:0} ثانیه", span.Seconds) : string.Empty);
 
            if (formatted.EndsWith("و "))
            {
                formatted = formatted.Substring(0, formatted.Length - 2);
            }
 
            if (string.IsNullOrEmpty(formatted))
            {
                formatted = "0 ثانیه";
            }
            return formatted.Trim();
        }
    }
}

کدهای کامل این مثال را از اینجا می‌توانید دریافت کنید:
ReadingTime.zip
 
مطالب
آشنایی با تابع PATINDEX در SQL Server
قبل از مطالعه باید بگویم سطح مقاله مبتدی می‌باشد.
گاهی اوقات در زمان Migration یک دیتابیس شما با جداولی برخورد می‌نمایید که محتویات بعضی از فیلد هایشان ترکیبی از عدد و حروف می‌باشد، و شما برای انجام یکسری از عملیات نیاز دارید. که حروف را از اعداد متمایز نمایید، یا اینکه مکان اولین کاراکتر غیر عددی را بعد از هر عدد، بیابید. برای انجام چنین کاری می‌توان از تابعی به نام Patindex استفاده نمود. 
تابع PATINDEX به شما امکان، مکان یابی یک یا چند حرف در بین رشته‌های متنی را می‌دهد.
Syntax تابع PATINDEX بصورت زیر میباشد:
PATINDEX ( '%pattern%' , expression )
تابع PATINDEX شامل دو آرگومان می‌باشد که هر کدام را به اختصار توضیح می‌دهیم:
1- آرگومان اولPattern نامگذاری شده است، Pattern در واقع یک الگوی اختصاصی میباشد که توسط کاربر، جهت جستجو در یک متن تعیین می‌شود. به بیان ساده‌تر اگر شما دنبال مکان حرف یا کلمه خاصی در یک رشته متنی می‌گردید، می‌بایست آن را در آرگومان Pattern  قرار دهید.
  • لازم است در آرگومان اول حداقل یک % وجود داشته باشد.
  • حداکثر تعداد کاراکترهایی را که می‌توان در آرگومان اول قرار داد 8000 میباشد.
2- آرگومان دوم یا همان Expression : متنی که عملیات جستجو روی آن اعمال می‌گردد، در این آرگومان قرار می‌گیرد.
  • اگر تعداد کارکترهای آرگومان دوم (Varchar(Max یا (nVarchar(Max  باشد، در آن صورت Type خروجی تابع PATINDEX از نوع bigint می‌باشد، در غیر این صورت Type خروجی تابع PATINDEX از نوع Int است.
  • اگر مقدار آرگومان دوم Null باشد، تابع PATINDEX مقدار Null بر می‌گرداند.
یک مثال کاربردی از تابع PATINDEX
یافتن اولین کاراکتر غیر عددی در رکودهای یک جدول
ابتدا در دیتابیس tempdb یک جدول به نام UsingPATINDEX مطابق Script زیر ایجاد،و چندین رکورد درون آن درج می‌نماییم:
USE tempdb
GO
CREATE TABLE UsingPATINDEX (ID INT, Words VARCHAR (100))
GO
INSERT INTO UsingPATINDEX (ID, Words)
SELECT 1, '1one'
UNION ALL
SELECT 2, '11eleven'
UNION ALL
SELECT 3, '2two'
UNION ALL
SELECT 4, '22twentytwo'
UNION ALL
SELECT 5, '111oneeleven'
GO
در ادامه QUERY زیر را اجرا نمایید:
SELECT PATINDEX('%[a-z]%',Words) 'مکان اولین کاراکتر غیر عددی',
SUBSTRING(Words,PATINDEX('%[a-z]%',Words),1) 'نام اولین کاراکتر غیر عددی بعد از عدد',
Words 'متن اصلی'
FROM  UsingPATINDEX
خروجی آن به شکل زیر است:

توضیح درباره QUERY :
قطعه کد زیر دنبال تمامی حروف a تا z ، درون فیلد Words می‌گردد و به اولین کاراکتر غیر عددی که می‌رسد، مکان آن را بر می‌گرداند.
PATINDEX('%[a-z]%',Words) 'مکان اولین کاراکتر غیر عددی'
قطعه کد زیر با توجه به مکان کاراکتر، خود کاراکتر را بر می‌گرداند:
SUBSTRING(Words,PATINDEX('%[a-z]%',Words),1) 'نام اولین کاراکتر غیر عددی بعد از عدد'

مثالی دیگر: فرض کنید،دنبال کلمه ای همانندensure  می گردید، بطوریکه دو حرف اول و دو حرف آخر آن را بخاطر می‌آورید و حرف میانی آن را بخاطر نمی‌آورید، در آن صورت  نیز می‌توانید از تابع PATINDEX استفاده نمایید، بدین شکل که به جای حرفی که بخاطر نمی‌آورید از _ استفاده کنید، همانند QUERY زیر:
SELECT PATINDEX('%en_ure%', 'please ensure the door is locked');
خروجی عدد 8 میباشد، که مکان حرف e است.
  • در تابع PATINDEX می‌توانید براساس Collation دلخواه عملیات جستجو را انجام دهید، برای روش‌تر شدن مطلب شکل زیر را مشاهده نمایید:

همانطور که درابتدای مطلبم گفتم در آرگومان اول می‌توان از یک % استفاده نمود، به مثال زیر توجه نمایید:
Select PATINDEX('a%', 'abc')
خروجی آن مقدار یک است.
مثالی دیگر:
Select PATINDEX('%a', 'cba')
خروجی آن مقدار 3 می‌باشد.
  • باید متذکر شوم، با دیدن دو مثال آخر،این تصور ایجاد نشود که تابع PATINDEX شبیه به تابع LIKE می‌باشد، چرا که تابع PATINDEX موقعیت کاراکتر را بر می‌گرداند، نه خود کاراکتر را،عملکرد تابع PATINDEX شبیه به عملکرد تابع CHARINDEX می‌باشد.




مطالب
iTextSharp و نمایش صحیح تاریخ در متنی راست به چپ

خروجی PDF زیر را در نظر بگیرید:

مشکلی را در آن مشاهده می‌کنید؟ اصل آن یا صحیح آن باید به شکل زیر باشد:


و این وارونه نمایش دادن‌ها، دقیقا مشکلی است که حین کار با iTextSharp برای نمایش متنی مثلا به همراه یک تاریخ شمسی وجود دارد. البته این مشکل هم اساسا به خود استاندارد یونیکد برمی‌گرد که یک سری کاراکتر را «کاراکتر ضعیف» معرفی کرده؛ برای مثال کاراکتر اسلش بکار رفته در یک تاریخ هم از این دست است. بنابراین PDF تولیدی توسط iTextSharp از دید استاندارد یونیکد مشکلی ندارد، زیرا یک «نویسه ضعیف» مثل اسلش نمی‌تواند جهت را تغییر دهد؛ مگر اینکه از یک «نویسه قوی» برای دستکاری آن استفاده شود. برای مثال این نویسه‌ها قوی هستند:

U+202A:   LEFT-TO-RIGHT EMBEDDING (LRE) 
U+202B:   RIGHT-TO-LEFT EMBEDDING (RLE)
U+202D:   LEFT-TO-RIGHT OVERRIDE (LRO)
U+202E:   RIGHT-TO-LEFT OVERRIDE (RLO)
U+202C:   POP DIRECTIONAL FORMATTING (PDF)

برای رسیدن به تصویر صحیح نمایش داده شده در بالا، متد FixWeakCharacters زیر را تهیه کرده‌ام که حداقل با iTextSharp جواب می‌ده:

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

namespace RleTests
{
class Program
{
const char RightToLeftEmbedding = (char)0x202B;
const char PopDirectionalFormatting = (char)0x202C;

static string FixWeakCharacters(string data)
{
if (string.IsNullOrWhiteSpace(data)) return string.Empty;
var weakCharacters = new[] { @"\", "/", "+", "-", "=", ";", "$" };
foreach (var weakCharacter in weakCharacters)
{
data = data.Replace(weakCharacter, RightToLeftEmbedding + weakCharacter + PopDirectionalFormatting);
}
return data;
}

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

FontFactory.Register("c:\\windows\\fonts\\Arial.ttf");
Font tahoma = FontFactory.GetFont("Arial", BaseFont.IDENTITY_H);

var table1 = new PdfPTable(1);
table1.WidthPercentage = 100;

var pdfCell = new PdfPCell
{
RunDirection = PdfWriter.RUN_DIRECTION_RTL,
Border = 0,
Phrase = new Phrase(FixWeakCharacters(
"تاریخ: " + "1390/11/18" + Environment.NewLine +
"شماره پروژه: " + "1/2/3/4/56" + Environment.NewLine +
"اسلش: " + " 12/A/13 " + Environment.NewLine +
"بک اسلش: " + " 12\\13\\14 " + Environment.NewLine +
"مساوی و جمع: " + " 2+3=5 " + Environment.NewLine +
"سمی کولون: " + " 2=1+1; " + Environment.NewLine +
"دلار: " + "12$" + Environment.NewLine +
"کاما: " + "12,34,67" + Environment.NewLine +
"نقطه: " + "12.34" + Environment.NewLine +
"پرانتز: " + "متن (ساده)"
),
tahoma)
};

table1.AddCell(pdfCell);
pdfDoc.Add(table1);

}

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

از این نوع مشکلات حین کار با HTML هم هست؛ وارونه نمایش داده شدن تاریخ فارسی در بین یک متن راست به چپ. البته در آنجا راه حل زیر هم توصیه شده (بدون نیاز به دستکاری نویسه‌ها):

<span dir="ltr" style="display:inline">1390/11/19</span>

مطالب
iTextSharp و نمایش صحیح تاریخ در متنی راست به چپ

خروجی PDF زیر را در نظر بگیرید:

مشکلی را در آن مشاهده می‌کنید؟ اصل آن یا صحیح آن باید به شکل زیر باشد:


و این وارونه نمایش دادن‌ها، دقیقا مشکلی است که حین کار با iTextSharp برای نمایش متنی مثلا به همراه یک تاریخ شمسی وجود دارد. البته این مشکل هم اساسا به خود استاندارد یونیکد برمی‌گردد که یک سری کاراکتر را «کاراکتر ضعیف» معرفی کرده؛ برای مثال کاراکتر اسلش بکار رفته در یک تاریخ هم از این دست است. بنابراین PDF تولیدی توسط iTextSharp از دید استاندارد یونیکد مشکلی ندارد، زیرا یک «نویسه ضعیف» مثل اسلش نمی‌تواند جهت را تغییر دهد؛ مگر اینکه از یک «نویسه قوی» برای دستکاری آن استفاده شود. برای مثال این نویسه‌ها قوی هستند:

U+202A:   LEFT-TO-RIGHT EMBEDDING (LRE) 
U+202B:   RIGHT-TO-LEFT EMBEDDING (RLE)
U+202D:   LEFT-TO-RIGHT OVERRIDE (LRO)
U+202E:   RIGHT-TO-LEFT OVERRIDE (RLO)
U+202C:   POP DIRECTIONAL FORMATTING (PDF)

برای رسیدن به تصویر صحیح نمایش داده شده در بالا، متد FixWeakCharacters زیر را تهیه کرده‌ام که حداقل با iTextSharp جواب می‌ده:

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

namespace RleTests
{
class Program
{
const char RightToLeftEmbedding = (char)0x202B;
const char PopDirectionalFormatting = (char)0x202C;

static string FixWeakCharacters(string data)
{
if (string.IsNullOrWhiteSpace(data)) return string.Empty;
var weakCharacters = new[] { @"\", "/", "+", "-", "=", ";", "$" };
foreach (var weakCharacter in weakCharacters)
{
data = data.Replace(weakCharacter, RightToLeftEmbedding + weakCharacter + PopDirectionalFormatting);
}
return data;
}

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

FontFactory.Register("c:\\windows\\fonts\\Arial.ttf");
Font tahoma = FontFactory.GetFont("Arial", BaseFont.IDENTITY_H);

var table1 = new PdfPTable(1);
table1.WidthPercentage = 100;

var pdfCell = new PdfPCell
{
RunDirection = PdfWriter.RUN_DIRECTION_RTL,
Border = 0,
Phrase = new Phrase(FixWeakCharacters(
"تاریخ: " + "1390/11/18" + Environment.NewLine +
"شماره پروژه: " + "1/2/3/4/56" + Environment.NewLine +
"اسلش: " + " 12/A/13 " + Environment.NewLine +
"بک اسلش: " + " 12\\13\\14 " + Environment.NewLine +
"مساوی و جمع: " + " 2+3=5 " + Environment.NewLine +
"سمی کولون: " + " 2=1+1; " + Environment.NewLine +
"دلار: " + "12$" + Environment.NewLine +
"کاما: " + "12,34,67" + Environment.NewLine +
"نقطه: " + "12.34" + Environment.NewLine +
"پرانتز: " + "متن (ساده)"
),
tahoma)
};

table1.AddCell(pdfCell);
pdfDoc.Add(table1);

}

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

از این نوع مشکلات حین کار با HTML هم هست؛ وارونه نمایش داده شدن تاریخ فارسی در بین یک متن راست به چپ. البته در آنجا راه حل زیر هم توصیه شده (بدون نیاز به دستکاری نویسه‌ها):

<span dir="ltr" style="display:inline">1390/11/19</span>