مطالب
طراحی و پیاده سازی زیرساختی برای مدیریت خطاهای حاصل از Business Rule Validationها در ServiceLayer
بعد از انتشار مطلب «Defensive Programming - بازگشت نتایج قابل پیش بینی توسط متدها»، بخصوص بخش نظرات آن و همچنین R&D در ارتباط با موضوع مورد بحث، در نهایت قصد دارم نتایج بدست آماده را به اشتراک بگذارم.

پیش نیازها
در بخش نهایی مطلب «Defensive Programming - بازگشت نتایج قابل پیش بینی توسط متدها » پیشنهادی را برای استفاده از استثناءها برای bubble up کردن یکسری پیغام از داخلی‌ترین یا پایین‌ترین لایه، تا لایه Presentation، ارائه دادیم:
استفاده از Exception برای نمایش پیغام برای کاربر نهایی 
با صدور یک استثناء و مدیریت سراسری آن در بالاترین (خارجی ترین) لایه و نمایش پیغام مرتبط با آن به کاربر نهایی، می‌توان از آن به عنوان ابزاری برای ارسال هر نوع پیغامی به کاربر نهایی استفاده کرد. اگر قوانین تجاری با موفقیت برآورده نشده‌اند یا لازم است به هر دلیلی یک پیغام مرتبط با یک اعتبارسنجی تجاری را برای کاربر نمایش دهید، این روش بسیار کارساز می‌باشد و با یکبار وقت گذاشتن برای توسعه زیرساخت برای این موضوع، به عنوان یک Cross Cutting Concern تحت عنوان Exception Management، آزادی عمل زیادی در ادامه توسعه سیستم خود خواهید داشت. 

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

راه حل صحیح برای برخورد با این سناریوها بازگشت یک Result می‌باشد که در مطلب قبلی هم تحت عنوان OperationResult مطرح شد. 


کلاس Result
    public class Result
    {
        private static readonly Result SuccessResult = new Result(true, null);

        protected Result(bool succeeded, string message)
        {
            if (succeeded)
            {
                if (message != null)
                    throw new ArgumentException("There should be no error message for success.", nameof(message));
            }
            else
            {
                if (message == null)
                    throw new ArgumentNullException(nameof(message), "There must be error message for failure.");
            }

            Succeeded = succeeded;
            Error = message;
        }

        public bool Succeeded { get; }
        public string Error { get; }

        [DebuggerStepThrough]
        public static Result Success()
        {
            return SuccessResult;
        }

        [DebuggerStepThrough]
        public static Result Failed(string message)
        {
            return new Result(false, message);
        }

        [DebuggerStepThrough]
        public static Result<T> Failed<T>(string message)
        {
            return new Result<T>(default, false, message);
        }

        [DebuggerStepThrough]
        public static Result<T> Success<T>(T value)
        {
            return new Result<T>(value, true, string.Empty);
        }

        [DebuggerStepThrough]
        public static Result Combine(string seperator, params Result[] results)
        {
            var failedResults = results.Where(x => !x.Succeeded).ToList();

            if (!failedResults.Any())
                return Success();

            var error = string.Join(seperator, failedResults.Select(x => x.Error).ToArray());
            return Failed(error);
        }

        [DebuggerStepThrough]
        public static Result Combine(params Result[] results)
        {
            return Combine(", ", results);
        }

        [DebuggerStepThrough]
        public static Result Combine<T>(params Result<T>[] results)
        {
            return Combine(", ", results);
        }

        [DebuggerStepThrough]
        public static Result Combine<T>(string seperator, params Result<T>[] results)
        {
            var untyped = results.Select(result => (Result) result).ToArray();
            return Combine(seperator, untyped);
        }

        public override string ToString()
        {
            return Succeeded
                ? "Succeeded"
                : $"Failed : {Error}";
        }
    }

مشابه کلاس بالا، در فریمورک ASP.NET Identity کلاسی تحت عنوان IdentityResult برای همین منظور در نظر گرفته شده‌است.

پراپرتی Succeeded نشان دهنده موفقت آمیز بودن یا عدم موفقیت عملیات (به عنوان مثال یک متد ApplicationService) می‌باشد. پراپرتی Error دربرگیرنده پیغام خطایی می‌باشد که قبلا از طریق Message مربوط به یک استثناء صادر شده، در اختیار بالاترین لایه قرار می‌گرفت. با استفاده از متد Combine، امکان ترکیب چندین Result حاصل از عملیات مختلف را خواهید داشت. متدهای استاتیک Failed و Success هم برای درگیر نشدن برای وهله سازی از کلاس Result در نظر گرفته شده‌اند.

متد GetForEdit مربوط به MeetingService را در نظر بگیرید. به عنوان مثال وظیفه این متد بازگشت یک MeetingEditModel می‌باشد؛ اما با توجه به یکسری قواعد تجاری، به‌عنوان مثال «امکان ویرایش جلسه‌ای که پابلیش نهایی شده‌است، وجود ندارد و ...» لازم است خروجی این متد نیز در صورت Fail شدن، دلیل آن را به مصرف کننده ارائه دهد. از این رو کلاس جنریک Result را به شکل زیر خواهیم داشت:

    public class Result<T> : Result
    {
        private readonly T _value;

        protected internal Result(T value, bool succeeded, string error)
            : base(succeeded, error)
        {
            _value = value;
        }

        public T Value
        {
            get
            {
                if (!Succeeded)
                    throw new InvalidOperationException("There is no value for failure.");

                return _value;
            }
        }
    }
حال با استفاده از کلاس بالا امکان مهیا کردن خروجی به همراه نتیجه اجرای متد را خواهیم داشت.
در ادامه با استفاده از تعدادی متد الحاقی بر فراز کلاس Result، روش Railway-oriented Programming را که یکی از روش‌های برنامه نویسی تابعی برای مدیریت خطاها است، در سی شارپ اعمال خواهیم کرد. 
    public static class ResultExtensions
    {
        public static Result<TK> OnSuccess<T, TK>(this Result<T> result, Func<T, TK> func)
        {
            return !result.Succeeded ? Result.Failed<TK>(result.Error) : Result.Success(func(result.Value));
        }

        public static Result<T> Ensure<T>(this Result<T> result, Func<T, bool> predicate, string message)
        {
            if (!result.Succeeded)
                return Result.Failed<T>(result.Error);

            return !predicate(result.Value) ? Result.Failed<T>(message) : Result.Success(result.Value);
        }

        public static Result<TK> Map<T, TK>(this Result<T> result, Func<T, TK> func)
        {
            return !result.Succeeded ? Result.Failed<TK>(result.Error) : Result.Success(func(result.Value));
        }

        public static Result<T> OnSuccess<T>(this Result<T> result, Action<T> action)
        {
            if (result.Succeeded) action(result.Value);

            return result;
        }

        public static T OnBoth<T>(this Result result, Func<Result, T> func)
        {
            return func(result);
        }

        public static Result OnSuccess(this Result result, Action action)
        {
            if (result.Succeeded) action();

            return result;
        }

        public static Result<T> OnSuccess<T>(this Result result, Func<T> func)
        {
            return !result.Succeeded ? Result.Failed<T>(result.Error) : Result.Success(func());
        }

        public static Result<TK> OnSuccess<T, TK>(this Result<T> result, Func<T, Result<TK>> func)
        {
            return !result.Succeeded ? Result.Failed<TK>(result.Error) : func(result.Value);
        }

        public static Result<T> OnSuccess<T>(this Result result, Func<Result<T>> func)
        {
            return !result.Succeeded ? Result.Failed<T>(result.Error) : func();
        }

        public static Result<TK> OnSuccess<T, TK>(this Result<T> result, Func<Result<TK>> func)
        {
            return !result.Succeeded ? Result.Failed<TK>(result.Error) : func();
        }

        public static Result OnSuccess<T>(this Result<T> result, Func<T, Result> func)
        {
            return !result.Succeeded ? Result.Failed(result.Error) : func(result.Value);
        }

        public static Result OnSuccess(this Result result, Func<Result> func)
        {
            return !result.Succeeded ? result : func();
        }

        public static Result Ensure(this Result result, Func<bool> predicate, string message)
        {
            if (!result.Succeeded)
                return Result.Failed(result.Error);

            return !predicate() ? Result.Failed(message) : Result.Success();
        }

        public static Result<T> Map<T>(this Result result, Func<T> func)
        {
            return !result.Succeeded ? Result.Failed<T>(result.Error) : Result.Success(func());
        }


        public static TK OnBoth<T, TK>(this Result<T> result, Func<Result<T>, TK> func)
        {
            return func(result);
        }

        public static Result<T> OnFailure<T>(this Result<T> result, Action action)
        {
            if (!result.Succeeded) action();

            return result;
        }

        public static Result OnFailure(this Result result, Action action)
        {
            if (!result.Succeeded) action();

            return result;
        }

        public static Result<T> OnFailure<T>(this Result<T> result, Action<string> action)
        {
            if (!result.Succeeded) action(result.Error);

            return result;
        }

        public static Result OnFailure(this Result result, Action<string> action)
        {
            if (!result.Succeeded) action(result.Error);

            return result;
        }
    }
OnSuccess برای انجام عملیاتی در صورت موفقیت آمیز بودن نتیجه یک متد، OnFailed برای انجام عملیاتی در صورت عدم موفقت آمیز بودن نتیجه یک متد و OnBoth در هر صورت، عملیات مورد نظر شما را اجرا خواهد کرد. به عنوان مثال:
[HttpPost, AjaxOnly, ValidateAntiForgeryToken, ValidateModelState]
public virtual async Task<ActionResult> Create([Bind(Prefix = "Model")]MeetingCreateModel model)
{
    var result = await _service.CreateAsync(model);

    return result.OnSuccess(() => { })
                 .OnFailure(() => { })
                 .OnBoth(r => r.Succeeded ? InformationNotification("Messages.Save.Success") : ErrorMessage(r.Error));

}

یا در حالت‌های پیچیده تر:

var result = await _service.CreateAsync(new TenantAwareEntityCreateModel());

return Result.Combine(result, Result.Success(), Result.Failed("نتیجه یک متد دیگر به عنوان مثال"))
    .OnSuccess(() => { })
    .OnFailure(() => { })
    .OnBoth(r => r.Succeeded ? Json("OK") : Json(r.Error));


ترکیب با الگوی Maybe یا Option

قبلا مطلبی در رابطه با الگوی Maybe در سایت منتشر شده‌است. در نظرات آن مطلب، یک پیاده سازی به شکل زیر مطرح کردیم:
    public struct Maybe<T> : IEquatable<Maybe<T>>
        where T : class
    {
        private readonly T _value;

        private Maybe(T value)
        {
            _value = value;
        }

        public bool HasValue => _value != null;
        public T Value => _value ?? throw new InvalidOperationException();
        public static Maybe<T> None => new Maybe<T>();


        public static implicit operator Maybe<T>(T value)
        {
            return new Maybe<T>(value);
        }

        public static bool operator ==(Maybe<T> maybe, T value)
        {
            return maybe.HasValue && maybe.Value.Equals(value);
        }

        public static bool operator !=(Maybe<T> maybe, T value)
        {
            return !(maybe == value);
        }

        public static bool operator ==(Maybe<T> left, Maybe<T> right)
        {
            return left.Equals(right);
        }

        public static bool operator !=(Maybe<T> left, Maybe<T> right)
        {
            return !(left == right);
        }

        /// <inheritdoc />
        /// <summary>
        ///     Avoid boxing and Give type safety
        /// </summary>
        /// <param name="other"></param>
        /// <returns></returns>
        public bool Equals(Maybe<T> other)
        {
            if (!HasValue && !other.HasValue)
                return true;

            if (!HasValue || !other.HasValue)
                return false;

            return _value.Equals(other.Value);
        }

        /// <summary>
        ///     Avoid reflection
        /// </summary>
        /// <param name="obj"></param>
        /// <returns></returns>
        public override bool Equals(object obj)
        {
            if (obj is T typed)
            {
                obj = new Maybe<T>(typed);
            }

            if (!(obj is Maybe<T> other)) return false;

            return Equals(other);
        }

        /// <summary>
        ///     Good practice when overriding Equals method.
        ///     If x.Equals(y) then we must have x.GetHashCode()==y.GetHashCode()
        /// </summary>
        /// <returns></returns>
        public override int GetHashCode()
        {
            return HasValue ? _value.GetHashCode() : 0;
        }

        public override string ToString()
        {
            return HasValue ? _value.ToString() : "NO VALUE";
        }
    }

متد الحاقی زیر را در نظر بگیرید:
public static Result<T> ToResult<T>(this Maybe<T> maybe, string message)
    where T : class
{
    return !maybe.HasValue ? Result.Failed<T>(message) : Result.Success(maybe.Value);
}

فرض کنید خروجی متدی که در لایه سرویس مورد استفاده قرار می‌گیرد، Maybe باشد. در این حالت می‌توان با متد الحاقی بالا آن را به یک Result تبدیل کرد و در اختیار لایه بالاتر قرار داد. 
Result<Customer> customerResult = _customerRepository.GetById(model.Id)
    .ToResult("Customer with such Id is not found: " + model.Id);

همچنین متدهای الحاقی زیر را نیز برای ساختار داده Maybe می‌توان در نظر گرفت:

        public static T GetValueOrDefault<T>(this Maybe<T> maybe, T defaultValue = default)
            where T : class
        {
            return maybe.GetValueOrDefault(x => x, defaultValue);
        }

        public static TK GetValueOrDefault<T, TK>(this Maybe<T> maybe, Func<T, TK> selector, TK defaultValue = default)
            where T : class
        {
            return maybe.HasValue ? selector(maybe.Value) : defaultValue;
        }

        public static Maybe<T> Where<T>(this Maybe<T> maybe, Func<T, bool> predicate)
            where T : class
        {
            if (!maybe.HasValue)
                return default(T);

            return predicate(maybe.Value) ? maybe : default(T);
        }

        public static Maybe<TK> Select<T, TK>(this Maybe<T> maybe, Func<T, TK> selector)
            where T : class
            where TK : class
        {
            return !maybe.HasValue ? default : selector(maybe.Value);
        }

        public static Maybe<TK> Select<T, TK>(this Maybe<T> maybe, Func<T, Maybe<TK>> selector)
            where T : class
            where TK : class
        {
            return !maybe.HasValue ? default(TK) : selector(maybe.Value);
        }

        public static void Execute<T>(this Maybe<T> maybe, Action<T> action)
            where T : class
        {
            if (!maybe.HasValue)
                return;

            action(maybe.Value);
        }
    }

پیشنهادات
  • استفاده از الگوی Specification برای زمانیکه منطقی قرار است هم برای اعتبارسنجی درون حافظه‌ای استفاده شود و همچنین برای اعمال فیلتر برای واکشی داده‌ها؛ در واقع دو Use-case استفاده از این الگو حداقل یکجا وجود داشته باشد. استفاده از این مورد برای Domain Validation در سناریوهای پیچیده بسیار پیشنهاد می‌شود.
  • استفاده از Domain Eventها برای اعمال اعتبارسنجی‌های مرتبط با قواعد تجاری تنها در شرایط inter-application communication و در شرایط inner-application communication به صورت صریح، اعتبارسنجی‌های مرتبط با قواعد تجاری را در جریان اصلی برنامه پیاده سازی کنید. 

با تشکر از آقای «محسن خان»
پرسش‌ها
چگونه میتوانم یک پلاک خودرو را در سی شارپ بخوانم

درود بر همه عزیزان؛ زبان سی شارپ. اندازه پروژه کوچک.

برای خواندن متن (پلاک ماشین) از داخل تصویری که با OpenCVsharp گرفتم. (کیفیت تصاویر خوبه)

راه حل های طی شده :

1 :

با IronOCR و Tesseract کار کردم جواب نداد یا شاید بشه گفت من بلد نیستم چجوری از اینا استفاده کنم.

void Test1()
{
    var Ocr = new IronTesseract();
    Ocr.Language = OcrLanguage.Persian;
    using (var Input = new OcrInput("car-plate-iran.jpg"))
    {
       var Result = Ocr.Read(Input);
       var AllText = Result.Text;
       Console.WriteLine(AllText);
    }
}

2 :

امکانات آنلاین مثل api.ocr.space رو نمیتونم استفاده کنم

راه های مختلفی رو تست کردم، چت جی پی تی هم یسری کد میده همشون بدون خطا هستن ولی هیچ کدوم خروجی نمیدن. شاید قبلش کانفیگی چیزی لازمه و من بلد نیستم یا اینکه کلا سورس در رابطه با OCR بسیار کم هست؟! چیزی نمیدونم. ممنون میشم راهنمایی کنید

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

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

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

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

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


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

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




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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

                    if (!hasAction(annotationDictionary))
                        continue;

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

                    if (!isUriAction(annotationAction))
                        continue;

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

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

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

چند نکته تکمیلی
- اگر قصد داشته باشیم تا لینکی را ویرایش کرده اما تنها Uri آن‌را تغییر دهیم، تنها کافی است URI آن‌را به نحو زیر در متد replaceUriWithLocalDestination ویرایش کنیم:
annotationAction.Put(PdfName.URI, new PdfString("http://www.bing.com/"));
- اگر بجای یک مقصد نام دار، تنها قرار است لینک موجود، به صفحه‌ای مشخص اشاره کند، تغییرات متد replaceUriWithLocalDestination به نحو زیر خواهد بود:
newLocalDestination.Add((PdfObject)_reader.GetPageOrigRef(pageNum: 2));
RemovePdfLinks.7z
نظرات مطالب
OpenCVSharp #11
به طور مشخص با توجه به توضیحات بالا می‌توان گفت اگر مقدار k در الگوریتم k-means را برابر 2 در نظر بگیریم، تصویر ما به دو رنگ کوانتیزه  می‌شود
            using (var src = new Mat(@"test.jpg", LoadMode.Color))
            {
                image1.Source = KMeans(src, 2).ToWriteableBitmap();
                image2.Source = Binary(src).ToWriteableBitmap();
            }
که تقریبا با تصویر بدست آمده پس از باینری کردن آن معادل است 
        private static Mat Binary(Mat src)
        {
            // Convert the image to Gray
            src = src.CvtColor(ColorConversion.RgbToGray);
            // Convert the Gray to Binary with auto threshold
            Cv2.Threshold(src, src, 0, 255, ThresholdType.Binary | ThresholdType.Otsu);
            // Convert the Gray to Binary with manual threshold
            //Cv2.Threshold(src, src, 127, 255, ThresholdType.Binary);

            return src;
        }

        private static Mat KMeans(Mat src, int k)
        {
            var columnVector = src.Reshape(cn: 3, rows: src.Rows * src.Cols);
            var samples = new Mat();
            columnVector.ConvertTo(samples, MatType.CV_32FC3);

            var bestLabels = new Mat();
            var centers = new Mat();
            Cv2.Kmeans(
                data: samples,
                k: k,
                bestLabels: bestLabels,
                criteria: new TermCriteria(type: CriteriaType.Epsilon | CriteriaType.Iteration, maxCount: 10, epsilon: 1.0),
                attempts: 3,
                flags: KMeansFlag.PpCenters,
                centers: centers);


            var dst = new Mat(src.Rows, src.Cols, src.Type());
            for (var size = 0; size < src.Cols * src.Rows; size++)
            {
                var clusterIndex = bestLabels.At<int>(0, size);
                var newPixel = new Vec3b
                {
                    Item0 = (byte)(centers.At<float>(clusterIndex, 0)), // B
                    Item1 = (byte)(centers.At<float>(clusterIndex, 1)), // G
                    Item2 = (byte)(centers.At<float>(clusterIndex, 2)) // R
                };
                dst.Set(size / src.Cols, size % src.Cols, newPixel);
            }

            return dst;
        }

نتایج :

نظرات مطالب
پیاده سازی UnitOfWork به وسیله MEF
من کلاسهام به این شکله:
کلاس کانتکس‌های من
 public class VegaContext : DbContext, IUnitOfWork, IDbContext
    {
#region Constructors (2) 

        /// <summary>
        /// Initializes the <see cref="VegaContext" /> class.
        /// </summary>
        static VegaContext()
        {
            Database.SetInitializer<VegaContext>(null);
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="VegaContext" /> class.
        /// </summary>
        public VegaContext() : base("LocalSqlServer") { }

#endregion Constructors 

#region Properties (2) 

        /// <summary>
        /// Gets or sets the languages.
        /// </summary>
        /// <value>
        /// The languages.
        /// </value>
        public DbSet<Language> Languages { get; set; }

        /// <summary>
        /// Gets or sets the resources.
        /// </summary>
        /// <value>
        /// The resources.
        /// </value>
        public DbSet<Resource> Resources { get; set; }

#endregion Properties 

#region Methods (2) 

// Public Methods (1) 

        /// <summary>
        /// Setups the specified model builder.
        /// </summary>
        /// <param name="modelBuilder">The model builder.</param>
        public void Setup(DbModelBuilder modelBuilder)
        {
            //todo
            modelBuilder.Configurations.Add(new ResourceMap());
            modelBuilder.Configurations.Add(new LanguageMap());
            modelBuilder.Entity<Resource>().ToTable("Vega_Languages_Resources");
            modelBuilder.Entity<Language>().ToTable("Vega_Languages_Languages");
            //base.OnModelCreating(modelBuilder);
        }
// Protected Methods (1) 

        /// <summary>
        /// This method is called when the model for a derived context has been initialized, but
        /// before the model has been locked down and used to initialize the context.  The default
        /// implementation of this method does nothing, but it can be overridden in a derived class
        /// such that the model can be further configured before it is locked down.
        /// </summary>
        /// <param name="modelBuilder">The builder that defines the model for the context being created.</param>
        /// <remarks>
        /// Typically, this method is called only once when the first instance of a derived context
        /// is created.  The model for that context is then cached and is for all further instances of
        /// the context in the app domain.  This caching can be disabled by setting the ModelCaching
        /// property on the given ModelBuidler, but note that this can seriously degrade performance.
        /// More control over caching is provided through use of the DbModelBuilder and DbContextFactory
        /// classes directly.
        /// </remarks>
        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            modelBuilder.Configurations.Add(new ResourceMap());
            modelBuilder.Configurations.Add(new LanguageMap());
            modelBuilder.Entity<Resource>().ToTable("Vega_Languages_Resources");
            modelBuilder.Entity<Language>().ToTable("Vega_Languages_Languages");
            base.OnModelCreating(modelBuilder);
        }

#endregion Methods 

        #region IUnitOfWork Members
        /// <summary>
        /// Sets this instance.
        /// </summary>
        /// <typeparam name="TEntity">The type of the entity.</typeparam>
        /// <returns></returns>
        public new IDbSet<TEntity> Set<TEntity>() where TEntity : class
        {
            return base.Set<TEntity>();
        }
        #endregion
    }
در تعاریف کلاسهایی که از IDBContext ارث می‌برن اکسپورت شدن (این یک نمونه از کلاس‌های منه)
در طرف دیگر برای لود کردن کلاس زیر نوشتم
public class LoadContexts
    {
        public LoadContexts()
        {
            var directoryPath = HttpRuntime.BinDirectory;//AppDomain.CurrentDomain.BaseDirectory; //"Dll folder path";

            var directoryCatalog = new DirectoryCatalog(directoryPath, "*.dll");

            var aggregateCatalog = new AggregateCatalog();
            aggregateCatalog.Catalogs.Add(directoryCatalog);

            var container = new CompositionContainer(aggregateCatalog);
            container.ComposeParts(this);
        }

        //[Import]
        //public IPlugin Plugin { get; set; }

        [ImportMany]
        public IEnumerable<IDbContext> Contexts { get; set; }
    }
و در کانتکس اصلی برنامه این پلاگین هارو لود می‌کنم
public class MainContext : DbContext, IUnitOfWork
    {
        public MainContext() : base("LocalSqlServer") { }

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);
            var contextList = new LoadContexts(); //ObjectFactory.GetAllInstances<IDbContext>();
            foreach (var context in contextList.Contexts)
                context.Setup(modelBuilder);

            Database.SetInitializer(new MigrateDatabaseToLatestVersion<MainContext, Configuration>());
            //Database.SetInitializer(new DropCreateDatabaseAlways<MainContext>());
        }

        /// <summary>
        /// Sets this instance.
        /// </summary>
        /// <typeparam name="TEntity">The type of the entity.</typeparam>
        /// <returns></returns>
        public IDbSet<TEntity> Set<TEntity>() where TEntity : class
        {
            return base.Set<TEntity>();
        }
    }
با موفقیت همه پلاگین‌ها لود میشه و مشکلی در عملیات نیست. اما Attribute‌های کلاس هارو نمیشناسه. مثلا پیام خطا تعریف شده در MVC نمایش داده نمیشه چون وجود نداره ولی وقتی کلاس مورد نظر از IValidatableObject  ارث میبره خطای‌های من نمایش داده میشه. می‌خوام از خود متادیتاهای استاندارد استفاده کنم.



مطالب
مروری بر کتابخانه ReactJS - قسمت اول - آشنایی با ReactJS
در این سری مقالات، مروری بر کتابخانه ReactJS خواهیم داشت. به طور کلی با آن آشنا می‌شویم، برای Visual Studio Code پیکربندیش میکنیم و قابلیت‌های مختلف کتابخانه را بررسی میکنیم. هر چند که مثالها در کل ساده هستند، اما پیش نیاز درک کامل آنها، آشنا بودن خواننده با HTML DOM، JavaScript و  Ajax است. در قسمت اول، کتابخانه را معرفی و مثال‌هایی از امکانات اصلی آن‌را مرور میکنیم.  

React یک کتابخانه متن‌باز جاوااسکریپتی، برای ساخت رابط کاربری به صورت پویا، بر پایه تغییر وضعیت اولیه المانها (تگ‌ها) نسبت به داده‌های وارد شده از سمت سرور یا داده‌های ایجاد شده در سمت کاربر، برای ساخت برنامه‌های تک‌صفحه‌ای در بستر وب است. این کتابخانه توسط فیس بوک ساخته شده و توسط فیس‌بوک، اینستاگرام و جمعی از شرکت‌ها و اشخاص منفرد، توسعه داده شده و نگهداری میشود. 
کلمه React به معنای واکنش نشان دادن است و این دقیقا کاری است که این کتابخانه انجام میدهد. وقتی بخشی از برنامه تغییر می‌کند، این تغییرات باید در جایی منعکس شوند. مثلا اگر توسط Ajax داده‌هایی را از سرور دریافت کرده‌ایم، به چیزی بیشتر از یک جدول ثابت برای نمایش و تبادل با داده‌های رسیده احتیاج داریم. توسط React رابط کاربری (HTML) را با استفاده از JavaScript ایجاد میکنیم. React برای کار با Ajax فوق‌العاده است! 
مرورگر‌ها برای رندر کردن یک HTML DOM به صورت پویا مشکلی ندارند؛ اما به اندازه کافی سریع نیستند. بخصوص زمانیکه نیاز به به‌روز کردن DOM می‌رسد و مرورگر تغییرات جدید را در حافظه موقت خود ندارد. DOM یک گلوگاه است و بهتر است، از داشتن کدهای خیلی زیاد HTML در صفحه پرهیز کنیم. بخصوص در صفحه‌هایی با اطلاعات پویا بهتر است کار ساخت و تغییر رابط کاربری را به JavaScript بسپاریم. اگر تگ‌های HTML به صورت اشیاء JavaScript ارائه شوند، امکانات بیشتری برای کار با آنها خواهیم داشت. 
React متد createElement را برای ساخت تگ‌های HTML دارد که یک شیء JavaScript را ایجاد میکند. البته می‌شود همین کار را با JavaScript نیز انجام داد. ارزش ایجاد تگ‌های HTML با React زمانی است که میخواهیم  با داده‌ها و تغییرات آنها سر و کار داشته باشیم. در قطعه کد زیر ساخت تگ img، توسط JavaScript و React آورده شده. 
var image = document.createElement("img");
image.setAttribute("src", "logo.png");

React.createElement("img", { src : "logo.png" });
با ساخت تگ‌ها توسط React، نماینده‌ای از تگ ساخته شده را در حافظه داریم که از نمونه‌ای که در مرورگر به صورت ایستا وجود دارد، جداست. به این صورت می‌توانیم تغییراتی را که میخواهیم بر روی DOM انجام شوند، بر اساس ساختاری که در حافظه داریم، اعمال کنیم.  

Virtual DOM

تفاوت در ساخت تگ‌های HTML به صورت مجازی بین JavaScript و React این است که React وضعیت تگ‌هایی را که می‌سازد دنبال می‌کند. برای مثال فرض کنید نام سه محصول را در یک تگ < ul > نشان داده‌ایم. React وضعیت اصلی این تگ را که به مرورگر فرستاده، در حافظه دارد و همچنین در اثر تغییر منبع داده‌ای که برای < ul > مشخص کرده‌ایم (که میتواند ورود اطلاعات به صورت Ajax باشد (مثلا اضافه شدن یک محصول جدید)) وضعیت جدیدی را برای تگ < ul > در حافظه ایجاد میکند. با وجود دو وضعیت برای یک تگ در حافظه، React میتواند تفاوت بین آنها را تشخیص داده و تگ را به روز کند. به این حالت عملکرد React ، اصطلاحا Virtual DOM می‌گویند.

React رابط کاربری را به صورت یک مدل می‌بیند و این مدل را با توجه به وضعیت اصلی آن در حافظه دوباره می‌سازد. برای React مهم نیست که ماهیت تغییر چیست. فقط وضعیت‌ها را مثل دو عکس می‌بیند و میفهمد که آیا چیزی عوض شده‌است یا نه. دیالوگ React با مرورگر اینطور است: ای تگ < ul > این لیست را نشان بده (لیستی با سه محصول)، و بعد می‌گوید: ای تگ < ul > این لیست را نشان بده (لیستی با چهار محصول)!


کامپوننت‌های React

رابط‌های کاربری مثل تگ‌های HTML  برای React به معنای Component هستند. استفاده از این مؤلفه‌های مجزا، مزایای زیادی دارند که در زیر مثالی از نحوه ساخت یک Component را در React می‌بینیم.   
<a href = “http://google.com”>
     <img src=”google.png”/>
</a>

// Components
<clickableimage/>
<linkimage/>

در کد بالا، بخش اول واضح است. عکسی که قابلیت کلیک شدن را دارد. حال فرض کنید یکی از کامپوننت‌های  <clickableimage/> یا <linkimage/>، همان تصویر قابل کلیک را ایجاد کنند. با نام گذاری واضح کامپوننت‌ها، خوانایی برنامه بهتر می‌شود. یعنی میدانیم هر کامپوننت چه کاری را برای ما انجام میدهد. با این تصور که اگر تگ‌های زیاد و طولانی را در بخش رابط کاربری داریم، ارزش استفاده از کامپوننت‌های  React مشخص می‌شود.


قابلیت استفاده مجدد

در React کامپوننت‌ها برای اساس توابع ساخته می‌شوند. یعنی وقتی یک کامپوننت را صدا بزنیم، در واقع یک تابع را اجرا می‌کنیم. در نتیجه کامپوننت‌ها رفتار توابع را دارند؛ ورودی میگیرند و خروجی که یک DOM مجازی است را تحویل میدهند. اگر تابعی که مسئول ساخت کامپوننت است وابستگی به توابع یا متغیرهای بیرونی نداشته باشد، میتواند در جای دیگری از برنامه یا برنامه‌ای دیگر مجددا استفاده شود. کد زیر نشان میدهد که چطور کامپوننت‌های React ساخته می‌شوند.  
var ClickableImage = function(props) {
  return (
      <a href={props.href}>
         <img src={props.src} />
      </a>
    );
};

ReactDOM.render(
<ClickableImage href="http://google.com" src="logo.png" />,
document.getElementById("targetDivId"));
در قسمت‌های بعد، به هر یک از امکانات ReactJS نگاهی دقیق‌تر و مثال‌هایی بیشتر، خواهیم داشت.  
مطالب
فشرده سازی اطلاعات توسط الگوریتم Zstandard فیسبوک
Zstandard یا به اختصار Zstd، یک الگوریتم فشرده سازی سریع و بدون افت کیفیت است که از سرعت و قدرت خوبی برخوردار است. این الگوریتم توسط Facebook توسعه داده شده و استفاده می‌شود.
الگوریتم‌های دیگری مانند  Snappy (الگوریتم فشرده سازی گوگل)،  LZ4 (جز سریع‌ترین الگوریتم ها) و LZMA (جز قوی‌ترین الگوریتم‌ها) نیز وجود دارند ولی Zstd تعادل (trade-off) خوبی بین سرعت و میزان فشرده سازی را فراهم می‌کند.
در این مقاله برای فشرده سازی اطلاعات با الگوریتم Zstd، از کتابخانه دات نتی  ZstdNet استفاده خواهیم کرد. علاوه بر این، کتابخانه دیگری نیز به نام  Zstandard.Net  با پشتیبانی از Streaming نیز وجود دارد.
ابتدا لازم است کتابخانه مذکور از Nuget نصب شود.
Install-Package ZstdNet
این کتابخانه شامل کلاسی به نام Compessor برای فشرده سازی است که متد Wrap آن آرایه‌ای byte‌ها را دریافت کرده و پس از فشرده سازی، آرایه‌ای از byte‌ها را باز می‌گرداند. به عنوان مثال :
byte[] sourceData = GetData(); //for example : File.ReadAllBytes(filePath);
byte[] compressedData;
using (var compressor = new Compressor())
{
    compressedData = compressor.Wrap(sourceData);
}  
برای تنظیمات فشرده سازی، از جمله میزان فشرده سازی می‌توان وهله‌ای از CompressionOptions را به سازنده کلاس Compressor ارسال کرد.
کلاس  CompressionOptions در سازنده خود، پارامتری را به نام  compressionLevel از نوع int، دریافت می‌کند که میزان فشرده سازی را مشخص می‌کند. مقدار پیش‌فرض آن 3 است و می‌تواند بین 1 تا 22 ( MaxCompressionLevel) باشد. افزایش این مقدار به معنی افزایش میزان فشرده سازی و طبیعتا افزایش زمان فشرده سازی است. 
var compressor = new Compressor(new CompressionOptions(compressionLevel: 5))
برای خارج کردن از حالت فشرده نیز از کلاس Decompressor و متد Unwrap آن استفاده می‌کنیم که آرایه بایتی فشرده شده را دریافت و پس از Decompress کردن، آرایه بایتی اصلی آن (حالت عادی و غیر فشرده شده) را باز می‌گرداند.
using (var decompressor = new Decompressor())
{
    byte[] sourceData = decompressor.Unwrap(compressedData);
}  
با استفاده از روش فوق می‌توانید قبل از ذخیره سازی فایل‌هایتان آنها را فشرده سازی کنید؛ مخصوصا اگر تصمیم به ذخیره سازی درون دیتابیس را دارید، این امکان می‌تواند به کاهش حجم دیتابیس شما کمک کند.
برای فشرده سازی و ذخیره فایل روی هارد نیز می‌توانید از کلاس کمکی زیر استفاده کنید.
public static class ZstdCompression
{
    public static (decimal savedPercent, long savedSizeKB) Compress(string filePath, string savePath, int compressionLevel = 5)
    {
        var sourceData = File.ReadAllBytes(filePath);
        byte[] compressedData;

        using (var compressor = new Compressor(new CompressionOptions(compressionLevel)))
            compressedData = compressor.Wrap(sourceData);

        File.WriteAllBytes(savePath, compressedData);

        var diff = sourceData.LongLength - compressedData.LongLength;
        var savedpercent = (decimal)diff * 100 / sourceData.LongLength;
        var savedSizeKB = diff / 1024;

        return (savedpercent, savedSizeKB);
    }

    public static void Decompress(string filePath, string savePath)
    {
        var compressedData = File.ReadAllBytes(filePath);
        byte[] decompressedData;

        using (var decompressor = new Decompressor())
            decompressedData = decompressor.Unwrap(compressedData);

        File.WriteAllBytes(savePath, decompressedData);
    }
}  
و برای استفاده :
var compressInfo = ZstdCompression.Compress(@"c:\video.mp4", @"c:\compressedFile");
Console.Write($"Saved : {compressInfo.savedPercent}% ({compressInfo.savedSizeKB} KB)");
//Output => Saved : 57% (7530 KB)

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

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


// RandomGenerator.cs
using System;
using System.Security.Cryptography;

namespace PersianCaptchaHandler
{
    public class RandomGenerator
    {
        private static readonly byte[] Randb = new byte[4];
        private static readonly RNGCryptoServiceProvider Rand = new RNGCryptoServiceProvider();

        public static int Next()
        {
            Rand.GetBytes(Randb);
            var value = BitConverter.ToInt32(Randb, 0);
            if (value < 0) value = -value;
            return value;
        }
        public static int Next(int max)
        {
            Rand.GetBytes(Randb);
            var value = BitConverter.ToInt32(Randb, 0);
            value = value%(max + 1);
            if (value < 0) value = -value;
            return value;
        }
        public static int Next(int min, int max)
        {
            var value = Next(max - min) + min;
            return value;
        }
    }
}
و برای تبدیل عدد تصادفی بدست آمده به متن نیز از این کلاس میتوان استفاده کرد که به طرز ساده ای این کار را انجام میدهد:
// NumberToString.cs
namespace PersianCaptchaHandler
{
    public class NumberToString
    {

        #region Fields
        private static readonly string[] Yakan = new[] { "صفر", "یک", "دو", "سه", "چهار", "پنج", "شش", "هفت", "هشت", "نه" };
        private static readonly string[] Dahgan = new[] { "", "", "بیست", "سی", "چهل", "پنجاه", "شصت", "هفتاد", "هشتاد", "نود" };
        private static readonly string[] Dahyek = new [] { "ده", "یازده", "دوازده", "سیزده", "چهارده", "پانزده", "شانزده", "هفده", "هجده", "نوزده" };
        private static readonly string[] Sadgan = new [] { "", "یکصد", "دوصد", "سیصد", "چهارصد", "پانصد", "ششصد", "هفتصد", "هشتصد", "نهصد" };
        private static readonly string[] Basex = new [] { "", "هزار", "میلیون", "میلیارد", "تریلیون" };
        #endregion

        private static string Getnum3(int num3)
        {
            var s = "";
            var d12 = num3 % 100;
            var d3 = num3 / 100;
            if (d3 != 0)
                s = Sadgan[d3] + " و ";
            if ((d12 >= 10) && (d12 <= 19))
            {
                s = s + Dahyek[d12 - 10];
            }
            else
            {
                var d2 = d12 / 10;
                if (d2 != 0)
                    s = s + Dahgan[d2] + " و ";
                var d1 = d12 % 10;
                if (d1 != 0)
                    s = s + Yakan[d1] + " و ";
                s = s.Substring(0, s.Length - 3);
            }
            return s;
        }

        public static string ConvertIntNumberToFarsiAlphabatic(string snum)
        {
            var stotal = "";
            if (snum == "0") return Yakan[0];

            snum = snum.PadLeft(((snum.Length - 1) / 3 + 1) * 3, '0');
            var l = snum.Length / 3 - 1;
            for (var i = 0; i <= l; i++)
            {
                var b = int.Parse(snum.Substring(i * 3, 3));
                if (b != 0)
                    stotal = stotal + Getnum3(b) + " " + Basex[l - i] + " و ";
            }
            stotal = stotal.Substring(0, stotal.Length - 3);
            return stotal;
        }
    }
}
و برای کد کردن و دیکد کردن یعنی موارد سوم و چهارم مقاله قبلی، متن بدست آمده را که بعنوان قسمتی از آدرس تصویر در ادامه آدرس هندلر معرفی شده می‌آید تبدیل به یک string بی معنی برای بازدیدکننده میکند:

using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;

namespace PersianCaptchaHandler
{
    public class Encryptor
    {
        #region constraints
        private static string Password { get { return "Mehdi"; } }
        private static string Salt { get { return "Payervand"; } }
        #endregion

        public static string Encrypt(string clearText)
        {
            // Turn text to bytes
            var clearBytes = Encoding.Unicode.GetBytes(clearText);

            var pdb = new PasswordDeriveBytes(Password, Encoding.Unicode.GetBytes(Salt));

            var ms = new MemoryStream();
            var alg = Rijndael.Create();

            alg.Key = pdb.GetBytes(32);
            alg.IV = pdb.GetBytes(16);

            var cs = new CryptoStream(ms, alg.CreateEncryptor(), CryptoStreamMode.Write);

            cs.Write(clearBytes, 0, clearBytes.Length);
            cs.Close();

            var encryptedData = ms.ToArray();

            return Convert.ToBase64String(encryptedData);
        }
        public static string Decrypt(string cipherText)
        {
            // Convert text to byte
            var cipherBytes = Convert.FromBase64String(cipherText);

            var pdb = new PasswordDeriveBytes(Password, Encoding.Unicode.GetBytes(Salt));

            var ms = new MemoryStream();
            var alg = Rijndael.Create();

            alg.Key = pdb.GetBytes(32);
            alg.IV = pdb.GetBytes(16);

            var cs = new CryptoStream(ms, alg.CreateDecryptor(), CryptoStreamMode.Write);

            cs.Write(cipherBytes, 0, cipherBytes.Length);
            cs.Close();

            var decryptedData = ms.ToArray();

            return Encoding.Unicode.GetString(decryptedData);
        }
    }
}


و نیز برای اعتبار سنجی عدد دریافتی از کاربر میتوان از عبارت با قاعده زیر استفاده کرد:

// Utils.cs
using System.Text.RegularExpressions;

namespace PersianCaptchaHandler
{
    public class Utils
    {
        private static readonly Regex NumberMatch = new Regex(@"^([0-9]*|\d*\.\d{1}?\d*)$", RegexOptions.Compiled);
        public static bool IsNumber(string number2Match)
        {
            return NumberMatch.IsMatch(number2Match);
        }
    }
}
برای استفاده نیز کافیست که هندلر مربوط به این کتابخانه را بطریق زیر در وب کانفیگ رجیستر کرد:

<add verb="GET" path="/captcha/" type="PersianCaptchaHandler.CaptchaHandler, PersianCaptchaHandler, Version=1.0.0.0, Culture=neutral"  />
و در صفحه مورد نظرتان بطریق زیر میتوان از یک تگ تصویر برای نمایش تصویر تولیدی و از یک فیلد مخفی برای نگهداری مقدار کد شده معادل عدد تولیدی که در هنگام مقایسه با عدد ورودی کاربر مورد نیاز است استفاده شود:

<!-- ASPX -->
<dl>
    <dt>تصویر امنیتی</dt>
    <dd>
        <asp:Image ID="imgCaptchaText" runat="server" AlternateText="CaptchaImage" />
        <asp:HiddenField ID="hfCaptchaText" runat="server" />
        <asp:ImageButton ID="btnRefreshCaptcha" runat="server" ImageUrl="/img/refresh.png"
            OnClick="btnRefreshCaptcha_Click" />
        <br />
        <asp:TextBox ID="txtCaptcha" runat="server" AutoCompleteType="Disabled"></asp:TextBox>
    </dd>
    <dd>
        <asp:Button ID="btnSubmit" runat="server" Text="ثبت" OnClick="btnSubmit_Click" />
    </dd>
</dl>
<asp:Label ID="lblMessage" runat="server"></asp:Label>
در زمان لود صفحه، تصویر امنیتی مقدار دهی میشود و در زمان ورود عدد توسط کاربر با توجه به اینکه کاربر حتما باید عدد وارد کند با عبارت با قاعده این اعتبار سنجی انجام میشود:

        protected void Page_Load(object sender, EventArgs e)
        {
            if (!Page.IsPostBack)
                SetCaptcha();
        }

        private void SetCaptcha()
        {
            lblMessage.Text =
            txtCaptcha.Text = string.Empty;

            var newNumber =
                RandomGenerator.Next(100, 999)
                ;

            var farsiAlphabatic = NumberToString.ConvertIntNumberToFarsiAlphabatic(newNumber.ToString());

            hfCaptchaText.Value =
                HttpUtility
                .UrlEncode(
                    Encryptor.Encrypt(
                        farsiAlphabatic
                    )
                );

            txtCaptcha.Text = string.Empty;
            imgCaptchaText.ImageUrl =
                "/captcha/?text=" + hfCaptchaText.Value;
        }
و بعد از ورود عدد از سمت کاربر از متد تبدیل به حروف استفاده کرده و این مقدار تولیدی با مقدار فیلد مخفی مقایسه میشود:
        private string GetCaptcha()
        {
            var farsiAlphabatic = NumberToString.ConvertIntNumberToFarsiAlphabatic(txtCaptcha.Text);

            var encryptedString =
                HttpUtility
                .UrlEncode(
                    Encryptor.Encrypt(
                        farsiAlphabatic
                    )
                );

            return encryptedString;
        }

        private bool ValidateUserInputForLogin()
        {
            if (!Utils.IsNumber(txtCaptcha.Text))
            {
                lblMessage.Text = "تصویر امنیتی را بطور صحیح وارد نکرده اید";
                return false;
            }

            var strGetCaptcha =
                GetCaptcha();

            var strDecodedVAlue =
                hfCaptchaText.Value;

            if (strDecodedVAlue != strGetCaptcha)
            {
                lblMessage.Text = "کلمه امنیتی اشتباه است";
                SetCaptcha();
                return false;
            }
            return true;
        }

        protected void btnSubmit_Click(object sender, EventArgs e)
        {
            if (!ValidateUserInputForLogin()) return;
            lblMessage.Text = "کلمه امنیتی درست است";
        }

        protected void btnRefreshCaptcha_Click(object sender, ImageClickEventArgs e)
        {
            SetCaptcha();
        }
 در آخر این پروژه در کدپلکس قرار داده شده، و مشتاق نظرات و پیشنهادات شما هستم و نیز نمونه مثال بالا ضمیمه شده است
مطالب
jQuery Tips #1
چگونگی تغییر سایز فونت  صفحه با استفاده از jQuery

کد زیر را در نظر بگیرید
 $(function () {
            // اندازه واقعی فونت
            var originalFontSize = $('#test').css('font-size');
            $(".resetFont").click(function () {
                $('#test').css('font-size', originalFontSize);
            });
            // افزایش اندازه فونت
            $(".increaseFont").click(function () {
                var currentFontSize = $('#test').css('font-size');
                var currentFontSizeNum = parseFloat(currentFontSize);
                var newFontSize = currentFontSizeNum + 5;
                $('#test').css('font-size', newFontSize);
                return false;
            });
            // کاهش اندازه فونت
            $(".decreaseFont").click(function () {
                var currentFontSize = $('#test').css('font-size');
                var currentFontSizeNum = parseFloat(currentFontSize);
                var newFontSize = currentFontSizeNum - 5;
                $('#test').css('font-size', newFontSize);
                return false;
            });
        });
       

  و کد HTML زیر را
 <div id="test">
        jQuery Tips By Mohsen Bahrzadeh
    </div>
    <a href="#">decreaseFont</a>
    <a href="#">Increase</a>
    <a href="#">resetFont</a>
 در این کد ابتدا اندازه فونت را درون متغیر originalFontSize ذخیره و سپس 3 متد تعریف کرده‌ایم، که اولین متد اندازه فونت را به اندازه فونت اولیه بر می‌گرداند و در متد دوم می‌خواهیم اندازه فونت تگی با ID=test را افزایش دهیم.
برای این کار ابتدا اندازه جاری تگ را گرفته و درون متغیر currentFontSize قرار داده سپس مقدار متغیر currentFontSize را به Float تبدیل کرده و 5 واحد به آن اضافه  کرده ایم و سپس عدد بدست آمده را به عنوان اندازه فونت جدید در نظر گرفته ایم. برای متد سوم دقیقاً همین اتفاق می‌افتد فقط به جای + از - استفاده شده است

نظرات مطالب
تغییر اندازه تصاویر #2
دلیل استفاده از Handler برای تغیر اندازه تصویر چیه ؟ در  روش قبلی شما با یک تابع این کار رو انجام دادین ، آیا تفاوتی بین این دو روش وجود داره ؟ کدوم بهتره استفاده بشه ؟ اگه ممکنه در مورد Handler توضیحی بدین . با تشکر