بازخوردهای پروژه‌ها
نحوه تبدیل تاریخ میلادی به شمسی
با سلام و تشکر از پروژه عالی شما،
شما برای تبدیل تاریخ میلادی به شمسی از این کلاس استفاده کردید.
using Persia;

namespace Iris.Utilities.DateAndTime
{
    public class DateAndTime
    {
        public static DateTime GetDateTime()
        {
            return DateTime.Now;
        }

        public static string ConvertToPersian(DateTime dateTime, string mod = "")
        {
            SolarDate solar = Calendar.ConvertToPersian(dateTime);
            return string.IsNullOrEmpty(mod) ? solar.ToString() : solar.ToString(mod);
        }
    }
}
یک تابع استاتیک که تاریخ و مد استرینگ رو برای تبدیل میگیره و توسط یه DLL به نام Pershia تبدیل را انجام میده.
من یه روش دیگه میشناسم ( شاید شما هم بلد باشید)
استفاده از این کلاس
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Reflection;

namespace GSD.Globalization
{
    /// <summary>
    /// <Publisher>http://www.Sayan.ir</Publisher>
    /// <Author>Maziar Rezaie</Author>
    /// </summary>
    public class PersianCulture : CultureInfo
    {
        private readonly Calendar cal;
        private readonly Calendar[] optionals;

        /// <summary>
        /// کد رو بخوان تا بفهمی
        /// </summary>
        /// <param name="cultureName">fa-IR</param>
        /// <param name="useUserOverride">true</param>
        /// <remarks>لطفا در هنگام استفاده به سایت سایان اشاره کنید.</remarks>
        public PersianCulture()
            : this("fa-IR", true)
        {
        }

        public PersianCulture(string cultureName, bool useUserOverride)
            : base(cultureName, useUserOverride)
        {
            //Temporary Value for cal.
            cal = base.OptionalCalendars[0];

            //populating new list of optional calendars.
            var optionalCalendars = new List<Calendar>();
            optionalCalendars.AddRange(base.OptionalCalendars);
            optionalCalendars.Insert(0, new PersianCalendar());


            Type formatType = typeof(DateTimeFormatInfo);
            Type calendarType = typeof(Calendar);


            PropertyInfo idProperty = calendarType.GetProperty("ID", BindingFlags.Instance | BindingFlags.NonPublic);
            FieldInfo optionalCalendarfield = formatType.GetField("optionalCalendars",
                                                                  BindingFlags.Instance | BindingFlags.NonPublic);

            //populating new list of optional calendar ids
            var newOptionalCalendarIDs = new Int32[optionalCalendars.Count];
            for (int i = 0; i < newOptionalCalendarIDs.Length; i++)
                newOptionalCalendarIDs[i] = (Int32)idProperty.GetValue(optionalCalendars[i], null);

            optionalCalendarfield.SetValue(DateTimeFormat, newOptionalCalendarIDs);

            optionals = optionalCalendars.ToArray();
            cal = optionals[0];
            DateTimeFormat.Calendar = optionals[0];

            DateTimeFormat.MonthNames = new[] { "فروردین", "اردیبهشت", "خرداد", "تیر", "مرداد", "شهریور", "مهر", "آبان", "آذر", "دی", "بهمن", "اسفند", "" };
            DateTimeFormat.MonthGenitiveNames = new[] { "فروردین", "اردیبهشت", "خرداد", "تیر", "مرداد", "شهریور", "مهر", "آبان", "آذر", "دی", "بهمن", "اسفند", "" };
            DateTimeFormat.AbbreviatedMonthNames = new[] { "فروردین", "اردیبهشت", "خرداد", "تیر", "مرداد", "شهریور", "مهر", "آبان", "آذر", "دی", "بهمن", "اسفند", "" };
            DateTimeFormat.AbbreviatedMonthGenitiveNames = new[] { "فروردین", "اردیبهشت", "خرداد", "تیر", "مرداد", "شهریور", "مهر", "آبان", "آذر", "دی", "بهمن", "اسفند", "" };


            DateTimeFormat.AbbreviatedDayNames = new string[] { "ی", "د", "س", "چ", "پ", "ج", "ش" };
            DateTimeFormat.ShortestDayNames = new string[] { "ی", "د", "س", "چ", "پ", "ج", "ش" };
            DateTimeFormat.DayNames = new string[] { "یکشنبه", "دوشنبه", "ﺳﻪشنبه", "چهارشنبه", "پنجشنبه", "جمعه", "شنبه" };

            DateTimeFormat.AMDesignator = "ق.ظ";
            DateTimeFormat.PMDesignator = "ب.ظ";

            /*
            DateTimeFormat.ShortDatePattern = "yyyy/MM/dd";
            DateTimeFormat.LongDatePattern = "yyyy/MM/dd";
            
            DateTimeFormat.SetAllDateTimePatterns(new[] {"yyyy/MM/dd"}, 'd');
            DateTimeFormat.SetAllDateTimePatterns(new[] {"dddd, dd MMMM yyyy"}, 'D');
            DateTimeFormat.SetAllDateTimePatterns(new[] {"yyyy MMMM"}, 'y');
            DateTimeFormat.SetAllDateTimePatterns(new[] {"yyyy MMMM"}, 'Y');
             */

        }

        public override Calendar Calendar
        {
            get { return cal; }
        }

        public override Calendar[] OptionalCalendars
        {
            get { return optionals; }
        }
    }
}

این کلاس به این صورت کار میکنه که در تمام برنامه‌ی شما تاریخ رو به صورت شمسی نشون میده ولی در ذخیره در بانک اطلاعاتی و کار در برنامه به صورت میلادی کار میکنه.
برای استفاده از اون هم باید به Application_BeginRequest  فایل Global.asax این کد‌ها رو اضافه کنید.   
using GSD.Globalization;
using System.Threading; 
 protected void Application_BeginRequest(object sender, EventArgs e)
        {
            var persianCulture = new PersianCulture();
            Thread.CurrentThread.CurrentCulture = persianCulture;
            Thread.CurrentThread.CurrentUICulture = persianCulture;
        }

اینم نمونه کد استفاه شده ازش WebApplication2.zip  
سوال من اینه روشی که شما استفاده کردید بهتره ؟ یا این روش؟ البته در روشی که من ارسال کردم به نظرم چون در Application_BeginRequest استفاده شده سر بار بیشتری داره! ولی همیشه تبدیل رو انجام میده و نیاز هیچ کد نویسی بیشتری نداره (مثل فراخوانی ConvertToPersian رو نیاز نداره)
نظر شما چیه؟
با تشکر از شما و صد تشکر بابت پروژه‌ی خوب و کاربردیتون

اشتراک‌ها
دوره پیاده سازی minimal API با دات نت 7

.NET 7 minimal API from scratch | FULL COURSE | clean architecture, repository pattern, CQRS MediatR

In this course I want to provide you a project structure and code organization to get you started with real .NET 7 minimal API projects. It's a full course on this topic where I start from creating and explaining the project structure, setting up different layers using EF Core, repository pattern, CQRS and MediatR. The biggest part of the video is however around the .NET 7 minimal API, taking you from the initial setup, explaining route handlers, implementing all CRUD operations and so on. Last but not least, this course walks you through the process of refactoring the .NET 7 minimal API so that it becomes readable, maintainable and scalable. At the end, you'll have a full project structure organized according to modern architectural patterns that you can take as a template for your own projects.

Contents
1. Intro: 00:00
2. Structuring the solution: 01:00
3. Coding the domain layer: 05:25
4. Coding the data access layer: 08:22
5. Creating repositories: 11:17
6. Adding migrations and database update: 22:30
7. CQRS with MediatR: 29:07
8. Route and rout handlers: 52:06
9. Dependency injection: 55:52
10. Implementing GET by ID : 57:40
11. Implementing POST route: 01:00:26
12. Implementing GET all route: 01:03:41
13. Implement PUT and DELETE: 01:04:57
14. Testing with Postman: 01:09:01
15. Is there a problem? 01:12:41
16. Refactoring service registrations: 01:15:49
17. Refactoring route registrations: 01:20:01
18. Automatic registration of endpoints: 01:26:28
19. Introducing route groups:  01:31:43
20. Extract lambdas into instance methods: 01:34:31
21: Model validation with endpoint filters: 01:45:58
22. Global exception handling: 01:55:10
23. Conclusions: 01:59:49 

دوره پیاده سازی minimal API با دات نت 7
مطالب
بررسی علت CPU Usage بالای برنامه در حال اجرا

فرض کنید به یک سرور مراجعه کرده‌اید و شکایت از CPU Usage مربوط به پروسه w3wp.exe یا همان IIS Worker Process است که بالای 90 درصد می‌باشد. بر روی این سرور هم هیچ چیز دیگری نصب نیست و مطابق مقررات موجود، قرار هم نیست که برنامه‌ای نصب شود. اکنون سؤال این است که چطور تشخیص می‌دهید، کدام قسمت یکی از برنامه‌ها‌ی دات نتی در حال اجرا (در اینجا یکی از برنامه‌های ASP.NET هاست شده)، سبب بروز این مشکل شده است؟ کدام ترد بیشترین زمان CPU را به خود اختصاص داده است؟ چطور باید خطایابی کرد؟
اولین کاری که در این موارد توصیه می‌شود مراجعه به برنامه‌ی معروف process explorer و بررسی برگه‌ی threads آن است. در اینجا حتی می‌توان call stacks مرتبط با یک ترد را هم مشاهده کرد. اما ... این برگه در مورد پروسه‌ها و تردهای دات نتی، اطلاعات چندانی را در اختیار ما قرار نمی‌دهد.
خوشبختانه امکان دیباگ پروسه‌های دات نتی در حال اجرا توسط کتابخانه‌ی MdbgCore.dll پیش بینی شده است. این فایل را در یکی از مسیر‌های ذیل می‌توانید پیدا کنید:
C:\Program Files\Microsoft SDKs\Windows\vXYZ\bin\MdbgCore.dll
C:\Program Files\Microsoft SDKs\Windows\vXYZ\bin\NETFX 4.0 Tools\MdbgCore.dll

در ادامه می‌خواهیم توسط امکانات این کتابخانه، به stack trace تردهای در حال اجرای یک برنامه دات نتی دسترسی پیدا کرده و سپس نام متدهای مرتبط را نمایش دهیم:
using System;
using System.Collections;
using System.Diagnostics;
using Microsoft.Samples.Debugging.MdbgEngine;

namespace CpuAnalyzer
{
class Program
{
static void Main(string[] args)
{
var engine = new MDbgEngine();

var processesByName = Process.GetProcessesByName("MyApp");
if (processesByName.Length == 0)
throw new InvalidOperationException("specified process not found.");
var processId = processesByName[0].Id;

var process = engine.Attach(processId);
process.Go().WaitOne();

foreach (MDbgThread thread in (IEnumerable)process.Threads)
{
foreach (MDbgFrame frame in thread.Frames)
{
if (frame == null || frame.Function == null) continue;
Console.WriteLine(frame.Function.FullName);
}
}

process.Detach().WaitOne();
}
}
}
در اینجا در ابتدا نیاز است تا pid یا process-id مرتبط با برنامه در حال اجرا یافت شود. سپس از این pid جهت اتصال (engine.Attach) به پروسه مورد نظر استفاده خواهیم کرد. در ادامه کلیه تردهای این پروسه در حال دیباگ لیست شده و سپس MDbgFrameهای این ترد بررسی می‌شوند و نام متدهای مرتبط در کنسول نمایش داده خواهند شد.
خوب در مرحله بعد شاید بد نباشد که این متدها را بر اساس CPU usage آن‌ها مرتب کنیم. به این ترتیب بهتر می‌توان تشخیص داد که کدام متد مشکل ساز بوده است. برای این منظور باید به API ویندوز و تابع GetThreadTimes مراجعه کرد و اولین پارامتر ورودی آن، همان thread.CorThread.Handle اولین حلقه مثال فوق می‌باشد. هر کدام که جمع KernelTime + UserTime بیشتری داشت، همان است که مشکل درست کرده است.
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern bool GetThreadTimes(IntPtr handle, out long creation, out long exit, out long kernel, out long user);
این مورد را به عنوان تمرین بررسی کرده و ادامه دهید! همچنین بهتر است جهت دستیابی به اطلاعاتی معتبر، اولین حلقه برنامه فوق، حداقل 10 بار اجرا شود تا اطلاعات آماری بهتری را بتوان ارائه داد. البته در این حالت نکته‌ی زیر باید رعایت شود:
for (int i = 0; i < 10; i++)
{
foreach (MDbgThread thread in (IEnumerable)process.Threads)
{
//...
}
process.Go();
Thread.Sleep(1000);
process.AsyncStop().WaitOne();
}

در کل این مثال جای کار زیاد دارد. برای مثال طراحی یک رابط کاربری برای آن و نمایش جزئیات بیشتر. به همین منظور حداقل سه پروژه مشابه را می‌توان نام برد:

مطالب
ساخت یک بارکدخوان با استفاده از OpenCV و ZXing.Net
فرض کنید می‌خواهیم بارکد این قبض را یافته و سپس عدد متناظر با آن‌را در برنامه بخوانیم.


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


بارگذاری تصویر و چرخش آن در صورت نیاز

ابتدا تصویر بارکد دار را بارگذاری کرده و آن‌را تبدیل به یک تصویر سیاه و سفید می‌کنیم:
// load the image and convert it to grayscale
var image = new Mat(fileName);
 
if (rotation != 0)
{
    rotateImage(image, image, rotation, 1);
}
 
if (debug)
{
    Cv2.ImShow("Source", image);
    Cv2.WaitKey(1); // do events
}
 
var gray = new Mat();
var channels = image.Channels();
if (channels > 1)
{
    Cv2.CvtColor(image, gray, ColorConversion.BgrToGray);
}
else
{
    image.CopyTo(gray);
}
در این بین ممکن است بارکد موجود در تصویر، دقیقا در زاویه‌ای که در تصویر ابتدای بحث قرار گرفته‌است، وجود نداشته باشد؛ مثلا منهای 90 درجه، چرخیده باشد. به همین جهت می‌توان از متد چرخش تصویر مطلب «تغییر اندازه، و چرخش تصاویر» ارائه شده در قسمت نهم این سری استفاده کرد.


تشخیص گرادیان‌های افقی و عمودی

یکی از روش‌های تشخیص بارکد، استفاده از روشی است که در تشخیص خودرو قسمت 16 بیان شد. تعداد زیادی تصویر بارکد را تهیه و سپس آن‌ها را به الگوریتم‌های machine learning جهت تشخیص و یافتن محدوده‌ی بارکد موجود در یک تصویر، ارسال کنیم. هرچند این روش جواب خواهد داد، اما در این مورد خاص، قسمت بارکد، شبیه به گرادیانی از رنگ‌ها است. کتابخانه‌ی OpenCV برای یافتن این نوع گرادیان‌ها دارای متدی است به نام Sobel :
// compute the Scharr gradient magnitude representation of the images
// in both the x and y direction
var gradX = new Mat();
Cv2.Sobel(gray, gradX, MatType.CV_32F, xorder: 1, yorder: 0, ksize: -1);
//Cv2.Scharr(gray, gradX, MatType.CV_32F, xorder: 1, yorder: 0);
 
var gradY = new Mat();
Cv2.Sobel(gray, gradY, MatType.CV_32F, xorder: 0, yorder: 1, ksize: -1);
//Cv2.Scharr(gray, gradY, MatType.CV_32F, xorder: 0, yorder: 1);
 
// subtract the y-gradient from the x-gradient
var gradient = new Mat();
Cv2.Subtract(gradX, gradY, gradient);
Cv2.ConvertScaleAbs(gradient, gradient);
 
if (debug)
{
    Cv2.ImShow("Gradient", gradient);
    Cv2.WaitKey(1); // do events
}


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


کاهش نویز و یکی کردن نواحی تشخیص داده شده

در ادامه می‌خواهیم با استفاده از متدهای تشخیص کانتور (قسمت 12)، نواحی با بیشترین شدت گرادیان افقی را پیدا کنیم. اما تصویر حاصل از قسمت قبل برای اینکار مناسب نیست. به همین جهت با استفاده از متدهای کار با مورفولوژی تصاویر، این نواحی گرادیانی را یکی می‌کنیم (قسمت 8).
// blur and threshold the image
var blurred = new Mat();
Cv2.Blur(gradient, blurred, new Size(9, 9));
 
var threshImage = new Mat();
Cv2.Threshold(blurred, threshImage, thresh, 255, ThresholdType.Binary);
 
if (debug)
{
    Cv2.ImShow("Thresh", threshImage);
    Cv2.WaitKey(1); // do events
}
 
 
// construct a closing kernel and apply it to the thresholded image
var kernel = Cv2.GetStructuringElement(StructuringElementShape.Rect, new Size(21, 7));
var closed = new Mat();
Cv2.MorphologyEx(threshImage, closed, MorphologyOperation.Close, kernel);
 
if (debug)
{
    Cv2.ImShow("Closed", closed);
    Cv2.WaitKey(1); // do events
}
 
 
// perform a series of erosions and dilations
Cv2.Erode(closed, closed, null, iterations: 4);
Cv2.Dilate(closed, closed, null, iterations: 4);
 
if (debug)
{
    Cv2.ImShow("Erode & Dilate", closed);
    Cv2.WaitKey(1); // do events
}
این سه مرحله را در تصاویر ذیل مشاهده می‌کنید:


ابتدا با استفاده از متد Threshold، تصویر را به یک تصویر باینری تبدیل خواهیم کرد. در این تصویر تمام نقاط دارای شدت رنگ کمتر از مقدار thresh، به مقدار حداکثر 255 تنظیم می‌شوند.
سپس با استفاده از متدهای تغییر مورفولوژی تصویر، قسمت‌های مجاور به هم را می‌بندیم و یکی می‌کنیم. این مورد در یافتن اشیاء احتمالی که ممکن است بارکد باشند، بسیار مفید است.
متدهای Erode و Dilate در اینجا کار حذف نویزهای اضافی را انجام می‌دهند؛ تا بهتر بتوان بر روی نواحی بزرگتر یافت شده، تمرکز کرد.



یافتن بزرگترین ناحیه‌ی به هم پیوسته‌ی موجود در یک تصویر

تمام این مراحل را انجام دادیم تا بتوانیم بزرگترین ناحیه‌ی به هم پیوسته‌ای را که احتمال می‌رود بارکد باشد، در تصویر تشخیص دهیم. پس از این آماده سازی‌ها، اکنون با استفاده از متد یافتن کانتورها، تمام نواحی یکی شده را یافته و بزرگترین مساحت ممکن را به عنوان بارکد انتخاب می‌کنیم:
//find the contours in the thresholded image, then sort the contours
//by their area, keeping only the largest one
 
Point[][] contours;
HiearchyIndex[] hierarchyIndexes;
Cv2.FindContours(
    closed,
    out contours,
    out hierarchyIndexes,
    mode: ContourRetrieval.CComp,
    method: ContourChain.ApproxSimple);
 
if (contours.Length == 0)
{
    throw new NotSupportedException("Couldn't find any object in the image.");
}
 
var contourIndex = 0;
var previousArea = 0;
var biggestContourRect = Cv2.BoundingRect(contours[0]);
while ((contourIndex >= 0))
{
    var contour = contours[contourIndex];
 
    var boundingRect = Cv2.BoundingRect(contour); //Find bounding rect for each contour
    var boundingRectArea = boundingRect.Width * boundingRect.Height;
    if (boundingRectArea > previousArea)
    {
        biggestContourRect = boundingRect;
        previousArea = boundingRectArea;
    }
 
    contourIndex = hierarchyIndexes[contourIndex].Next;
}
 
 
var barcode = new Mat(image, biggestContourRect); //Crop the image
Cv2.CvtColor(barcode, barcode, ColorConversion.BgrToGray);
 
Cv2.ImShow("Barcode", barcode);
Cv2.WaitKey(1); // do events
حاصل این عملیات یافتن بزرگترین ناحیه‌ی گرادیانی به هم پیوسته‌ی موجود در تصویر است:


خواندن مقدار متناظر با بارکد یافت شده

خوب، تا اینجا موفق شدیم، محل قرارگیری بارکد را تصویر پیدا کنیم. مرحله‌ی بعد خواندن مقدار متناظر با این تصویر است. برای این منظور از کتابخانه‌ی سورس بازی به نام http://zxingnet.codeplex.com استفاده خواهیم کرد. این کتابخانه قادر است بارکد بسازد و همچنین تصاویر بارکدها را خوانده و مقادیر متناظر با آن‌ها را استخراج کند. برای نصب آن می‌توان از دستور ذیل استفاده کرد:
 PM> Install-Package ZXing.Net
پس از نصب این کتابخانه‌ی بارکدساز و بارکد خوان، اکنون تنها کاری که باید صورت گیرد، ارسال تصویر بارکد جدا شده‌ی توسط OpenCV به آن است:
private static string getBarcodeText(Mat barcode)
{
    // `ZXing.Net` needs a white space around the barcode
    var barcodeWithWhiteSpace = new Mat(new Size(barcode.Width + 30, barcode.Height + 30), MatType.CV_8U, Scalar.White);
    var drawingRect = new Rect(new Point(15, 15), new Size(barcode.Width, barcode.Height));
    var roi = barcodeWithWhiteSpace[drawingRect];
    barcode.CopyTo(roi);
 
    Cv2.ImShow("Enhanced Barcode", barcodeWithWhiteSpace);
    Cv2.WaitKey(1); // do events
 
    return decodeBarcodeText(barcodeWithWhiteSpace.ToBitmap());
}
 
private static string decodeBarcodeText(System.Drawing.Bitmap barcodeBitmap)
{
    var source = new BitmapLuminanceSource(barcodeBitmap);
 
    // using http://zxingnet.codeplex.com/
    // PM> Install-Package ZXing.Net
    var reader = new BarcodeReader(null, null, ls => new GlobalHistogramBinarizer(ls))
    {
        AutoRotate = true,
        TryInverted = true,
        Options = new DecodingOptions
        {
            TryHarder = true,
            //PureBarcode = true,
            /*PossibleFormats = new List<BarcodeFormat>
                    {
                        BarcodeFormat.CODE_128
                        //BarcodeFormat.EAN_8,
                        //BarcodeFormat.CODE_39,
                        //BarcodeFormat.UPC_A
                    }*/
        }
    };
 
    //var newhint = new KeyValuePair<DecodeHintType, object>(DecodeHintType.ALLOWED_EAN_EXTENSIONS, new Object());
    //reader.Options.Hints.Add(newhint);
 
    var result = reader.Decode(source);
    if (result == null)
    {
        Console.WriteLine("Decode failed.");
        return string.Empty;
    }
 
    Console.WriteLine("BarcodeFormat: {0}", result.BarcodeFormat);
    Console.WriteLine("Result: {0}", result.Text);
 
 
    var writer = new BarcodeWriter
    {
        Format = result.BarcodeFormat,
        Options = { Width = 200, Height = 50, Margin = 4},
        Renderer = new ZXing.Rendering.BitmapRenderer()
    };
    var barcodeImage = writer.Write(result.Text);
    Cv2.ImShow("BarcodeWriter", barcodeImage.ToMat());
 
    return result.Text;
}
چند نکته را باید در مورد کار با ZXing.Net بخاطر داشت؛ وگرنه جواب نمی‌گیرید:
الف) این کتابخانه حتما نیاز دارد تا تصویر بارکد، در یک حاشیه‌ی سفید در اختیار او قرار گیرد. به همین جهت در متد getBarcodeText، ابتدا تصویر بارکد یافت شده، به میانه‌ی یک مستطیل سفید رنگ بزرگ‌تر کپی می‌شود.
ب) برای تبدیل Mat به Bitmap مورد نیاز این کتابخانه می‌توان از متد الحاقی ToBitmap استفاده کرد (قسمت 7).
ج) پس از آن وهله‌ای از کلاس BarcodeReader آماده شده و در آن پارامترهایی مانند بیشتر سعی کن (TryHarder) و اصلاح درجه‌ی چرخش تصویر (AutoRotate) تنظیم شده‌اند.
د) بارکدهای موجود در قبض‌های ایران عموما بر اساس فرمت CODE_128 ساخته می‌شوند. بنابراین برای خواندن سریعتر آ‌نها می‌توان PossibleFormats را مقدار دهی کرد. اگر این مقدار دهی صورت نگیرد، تمام حالت‌های ممکن بررسی می‌شوند.

در آخر کار این متد، از متد Writer آن نیز برای تولید بارکد مشابهی استفاده شده‌است تا بتوان بررسی کرد این دو تا چه اندازه به هم شبیه هستند.


همانطور که مشاهده می‌کنید، عدد تشخیص داده شده، با عدد شناسه‌ی قبض و شناسه‌ی پرداخت تصویر ابتدای بحث یکی است.


بهبود تصویر، پیش از ارسال آن به متد Decode کتابخانه‌ی ZXing.Net

در تصویر قبلی، سطر decode failed را هم ملاحظه می‌کنید. علت اینجا است که اولین سعی انجام شده، موفق نبوده است؛ چون تصویر تشخیص داده شده، بیش از اندازه نویز و حاشیه‌ی خاکستری دارد. می‌توان این حاشیه‌ی خاکستری را با دوبار اعمال متد Threshold از بین برد:
var barcodeClone = barcode.Clone();
var barcodeText = getBarcodeText(barcodeClone);
 
if (string.IsNullOrWhiteSpace(barcodeText))
{
    Console.WriteLine("Enhancing the barcode...");
    //Cv2.AdaptiveThreshold(barcode, barcode, 255,
        //AdaptiveThresholdType.GaussianC, ThresholdType.Binary, 9, 1);
    //var th = 119;
    var th = 100;
    Cv2.Threshold(barcode, barcode, th, 255, ThresholdType.ToZero);
    Cv2.Threshold(barcode, barcode, th, 255, ThresholdType.Binary);
    barcodeText = getBarcodeText(barcode);
}
 
Cv2.Rectangle(image,
    new Point(biggestContourRect.X, biggestContourRect.Y),
    new Point(biggestContourRect.X + biggestContourRect.Width, biggestContourRect.Y + biggestContourRect.Height),
    new Scalar(0, 255, 0),
    2);
 
if (debug)
{
    Cv2.ImShow("Segmented Source", image);
    Cv2.WaitKey(1); // do events
}
 
Cv2.WaitKey(0);
Cv2.DestroyAllWindows();


اعداد یافت شده، دقیقا از روی تصویر بهبود یافته‌ی توسط متدهای Threshold خوانده شده‌اند و نه تصویر ابتدایی یافت شده. بنابراین به این موضوع نیز باید دقت داشت.


کدهای کامل این مثال را از اینجا می‌توانید دریافت کنید.
مطالب
تبدیل زیرنویس‌های یوتیوب به فایل srt

تعدادی از ویدیوهای یوتیوب، خصوصا مواردی که از Google videos به یوتیوب منتقل شده‌اند، دارای زیر نویس هستند. این زیر نویس هم فرمت جالبی دارد:
<?xml version="1.0" encoding="UTF-8"?>
<transcript>
<text start="23.49" dur="5.97">
&amp;gt;&amp;gt;Commentator: We have here today, Steve with
us whom I met not too long ago at something
</text>
...
</transcript>
و از هر لحاظ بهتر است از فرمت srt متداول (خصوصا از لحاظ قابلیت بهتر parse آن) ولی ... این فرمت xml ایی، متداول نیست و بهتر است جهت استفاده از آن در برنامه‌های پخش کننده فایل‌های ویدیویی، به srt تبدیل شود.
ابتدا شاید بپرسید که این‌ها را چگونه می‌توان دریافت کرد. روش کار به صورت زیر است:
http://video.google.com/timedtext?hl=en&v=Id&lang=en

در قسمت‌های hl و lang ، زبان مورد نظر قرار می‌گیرد و قسمت v همان id معروف ویدیوی یوتیوب مورد نظر است. برای مثال اگر لینک ویدیوی ما http://www.youtube.com/watch?v=-PA-buwI3q4 باشد، لینک زیرنویس انگلیسی هماهنگ با آن http://video.google.com/timedtext?hl=en&v=-PA-buwI3q4&lang=en خواهد بود.

البته زمان‌های این فایل xml یک نکته‌ی خاص گوگلی دارد!
اگر عدد 3.4 را مشاهده کردید یعنی سه ثانیه و 400 میلی ثانیه
اگر عدد 3.04 را مشاهده کردید یعنی سه ثانیه و 40 میلی ثانیه
به عبارتی با قسمت اعشاری آن باید به صورت رشته برخورد کنید. اگر طول آن یک بود، دو صفر و اگر طول آن 2 بود، 1 صفر در جلوی آن باید قرار گیرد تا قابل استفاده شود.

کلاس تهیه شده زیر کار تبدیل Xml گوگل را به Srt انجام می‌دهد:
using System;
using System.Linq;
using System.Text;
using System.Web;
using System.Xml.Linq;

namespace YtSubs
{
public class GoogleXmlToSrt
{
public string ConvertXmlToSrt(string url)
{
var transcript = loadTranscript(url);

var srtBuf = new StringBuilder();
var lineNumber = 0;

foreach (var item in transcript.Root.Elements("text"))
{
lineNumber++;
srtBuf.Append(lineNumber.ToString());

var timeLine = getTimeLine(item);
srtBuf.AppendFormat("{0}{1} --> {2}{3}", Environment.NewLine, timeLine.Item1, timeLine.Item2, Environment.NewLine);

var msg = decodeText(item);
srtBuf.AppendLine(msg + Environment.NewLine);
}
return srtBuf.ToString();
}

private static XDocument loadTranscript(string url)
{
var transcript = XDocument.Load(url);

if (transcript.Root == null)
throw new InvalidOperationException("couldn't load received data.");
return transcript;
}

private static string decodeText(XElement text)
{
var textValue = text.Value.Split('\n');
var msg = textValue.Aggregate(
string.Empty, (current, line) =>
current + (HttpUtility.HtmlDecode(line.Trim()) + Environment.NewLine)
);
return msg.Trim();
}

private static int fractionalPart(string data)
{
var idx = data.IndexOf(".");
if (idx == -1) return 0;
var fractionalPart = data.Substring(idx + 1);
if (fractionalPart.Length == 1) //3.4 --> 3.400
return int.Parse(fractionalPart + "00");

if (fractionalPart.Length == 2) //3.04 --> 3.040
return int.Parse(fractionalPart + "0");

return int.Parse(fractionalPart.Substring(0, 3));
}

private Tuple<string, string> getTimeLine(XElement text)
{
var startTs = getStartTs(text);
TimeSpan endTs = getEndTs(text, startTs);
return new Tuple<string, string>(timeSpanToString(startTs), timeSpanToString(endTs));
}

private static TimeSpan getStartTs(XElement text)
{
var startData = text.Attribute("start");
if (startData == null)
throw new InvalidOperationException("This is not a valid subtitle file.");

var start = startData.Value;
var startTs = new TimeSpan(0, 0, 0, (int)Math.Truncate(double.Parse(start)), fractionalPart(start));
return startTs;
}

private static TimeSpan getEndTs(XElement text, TimeSpan startTs)
{
TimeSpan endTs;
var durData = text.Attribute("dur");
if (durData == null)
{
endTs = startTs + new TimeSpan(0, 0, 0, 3, 0);
}
else
{
var dur = durData.Value;
var durTs = new TimeSpan(0, 0, 0, (int)Math.Truncate(double.Parse(dur)), fractionalPart(dur));
endTs = startTs + durTs;
}
return endTs;
}

private static string timeSpanToString(TimeSpan lineTs)
{
return string.Format("{0}:{1}:{2},{3}",
lineTs.Hours.ToString("D2"),
lineTs.Minutes.ToString("D2"),
lineTs.Seconds.ToString("D2"),
lineTs.Milliseconds.ToString("D3"));
}
}
}
و یک نمونه از استفاده آن می‌تواند به شکل زیر باشد:
using System.IO;

namespace YtSubs
{
class Program
{
static void Main(string[] args)
{
var url = "http://video.google.com/timedtext?hl=en&v=-PA-buwI3q4&lang=en";
//var url = "subtitle.xml";
var srtBuf = new GoogleXmlToSrt().ConvertXmlToSrt(url);
File.WriteAllText("-PA-buwI3q4.srt", srtBuf);
}
}
}


مطالب تکمیلی:
همه چیز درباره زیرنویس ویدئوهای گوگل

مطالب
الگوی سردر Facade Pattern
یکی از الگوهای ساختاری Gang Of Four، استفاده از الگوی Facade است که پیچیدگی‌های یک سیستم را مخفی می‌سازد و با ارائه یک پیاده سازی ساده‌تر، استفاده از آن و تست آن را راحت‌تر می‌سازد. این الگو یک کلاس یا یک سیستمی را با متدها و رویدادهایی ساده، در اختیار ما قرار می‌دهد و در یک لحظه، تنها با یک کلاس واحد سر و کله می‌زنیم. احتمالا بسیاری از شما از این الگو استفاده کرده‌اید، ولی شاید با اسم آن آشنا نبوده‌اید.

کار این کلاس در واقع ترکیب کلاس‌ها و کتابخانه‌های کاری مشخص است که نیاز به ارتباط با یکدیگر را دارند. به عنوان مثال یک برنامه کتابخانه، برای وظیفه‌ای چون امانت یک کتاب نیاز است تا چندین کلاس مختلف را با یکدیگر به کار بگیرد که این وظایف شامل موارد زیر می‌باشند:
  1. بررسی وجود کتاب
  2. بررسی تعداد موجود یک کتاب در کتابخانه
  3. بررسی وضعیت امانی کتاب (آیا کتاب در دست کسی از قبل امانت است؟ یا کتاب برای امانت آزاد است؟)
  4. در صورتی که کتابی بیش از زمان مورد نظر در دست کسی امانت است، با یک پیامک از او بخواهیم که کتاب را بازگرداند.
تمامی موارد بالا تنها قسمتی از انجام یک عمل ساده هستند که در یک گروه جای می‌گیرند؛ ولی در واقع از چندین کلاس جدا مثل کلاس کتاب، امانت، سیستم پیامکی و ... استفاده شده است . الگوی Facade به ما کمک می‌کند تا پیچیدگی و تعداد خطوط اجرا را در سطوح بالاتر مخفی سازیم و تنها با صدا زدن یک یا چند متد ساده، کار را به اتمام برسانیم. این کار باعث کاهش کد و خوانایی برنامه در سطوح بالاتر می‌شود.

در کد زیر ما قصد داریم نمونه‌ای از اجرای این الگو را ببینیم. ابتدا سه کلاس اطلاعاتی زیر را ایجاد می‌کنیم:
کلاس کتاب Book:
    class Book
    {
        public int Id { get; set; }
        public string Title { get; set; }
        public int Quantity { get; set; }
        public bool IsLoanable { get; set; }
    }

کلاس کاربر User
 class User
    {
        public int Id { get; set; }
        public string Title { get; set; }
        public  string CellPhoneNumber { get; set; }
    }

کلاس امانت Loan
  class Loan
    {
       public DateTime ExpiredDate { get; set; }
        public User User { get; set; }
    }

بعد از آن سه کلاس را برای مدیریت کتاب، مدیریت امانت و مدیریت پیامکی، ایجاد می‌کنیم:
کلاس کتاب BookManager:
 class BookManager
    {
        public Book GetBook(int id)
        {
                return new Book()
                {
                    Id=id,
                    Title = "a book",
                    Quantity = 3,
                    IsLoanable = true
                };
        }
    }
کلاس بالا یک کتاب را با موجودی سه عدد بازمی‌گرداند که خاصیت IsLoanable آن می‌گوید کتاب را برای بردن به خانه امانت می‌دهند؛ ولی اگر کتاب به فرض یک کتاب مرجع باشد باید False برگرداند تا از امانت آن خودداری شود.

کلاس LoanManager برای مدیریت امانت‌ها
 public int IsLoan(int bookId)
        {
            return 2;
        }

        public List<Loan> GetLoans(int bookId)
        {
            return new List<Loan>()
            {
                new Loan()
                {
                    ExpiredDate = DateTime.Now.AddDays(-15),
                    User = new User()
                {
                    Id = 2,
                    Title = "User1",
                    CellPhoneNumber = "342342424"
                }
                },
                new Loan()
                {
                    ExpiredDate = DateTime.Now.AddDays(5),
                    User = new User()
                        {
                            Id = 56,
                            Title = "User56",
                            CellPhoneNumber = "324324324324"
                        }
                }
            };
        }
این کلاس شامل دو متد است که اولین متد آن کد کتاب را دریافت می‌کند و تعداد افرادی را که در حال حاضر نسخه‌های مختلف آن را به امانت برده‌اند، برمی‌گرداند. در کد بالا عدد دو بازگردانده می‌شود که میگوید از نسخه‌های موجود این کتاب در کتابخانه، دوتای آن‌ها به امانت برده شده‌اند. در متد دوم، کد کتاب داده شده و امانت‌های فعلی آن کتاب که همان دو عدد بالا می‌باشد را برگشت می‌دهد.

در نهایت سومی کلاس مدیریتی برای پیامک هاست:
   class SmsManager
    {
        public void SendMessage(string number)
        {
            Console.WriteLine("please take back the book to the library : "+number);
        }
    }

و در کلاس Facade داریم
    class FacadeBookLoan
    {
        private readonly BookManager _bookManager;
        private readonly LoanManager _loanManager;
        private readonly SmsManager _smsManager;
        public FacadeBookLoan()
        {
            _bookManager = new BookManager();
            _loanManager=new LoanManager();
            _smsManager=new SmsManager();
        }

        public int IsLoanable(int bookId)
        {
            var book = _bookManager.GetBook(2);
            if (book == null)
                return -2;
            if (!book.IsLoanable)
                return -1;
            var howManyBookIsLoaned = _loanManager.IsLoan(bookId);

            if(howManyBookIsLoaned>0) ManageLoaners(bookId);

            return book.Quantity - howManyBookIsLoaned;
        }

        private void ManageLoaners(int bookId)
        {
            var loans = _loanManager.GetLoans(bookId);

            foreach (var loan in loans)
            {
                if (loan.ExpiredDate > DateTime.Now)
                {
                    _smsManager.SendMessage(loan.User.CellPhoneNumber);
                }
            }
        }
    }
در این کلاس متد IsLoanable چک می‌کند که آیا کتاب قابل امانت هست یا خیر. در اینجا مرحله به مرحله، وجود کتاب و قابلیت امانت کتاب بررسی شده و در صورت نتیجه، کد وضعیت -1 یا -2 بازگردانده می‌شوند و در مرحله بعد تعداد نسخه‌های آن کتاب که در حال حاضر در دست امانت هستند، بررسی می‌شوند. اگر کتابی در درست امانت باشد، متد خصوصی صدا زده شده و به کاربرانی که بیش از مدت معینی یک نسخه از کتاب را داشته‌اند، پیامک می‌زند که کتاب را بازگردانید؛ چرا که نسخه‌ها در حال کاهش هستند و در مرحله بعد تعداد نسخه‌های موجود از آن کتاب را در کتابخانه باز می‌گرداند که در این مثال تنها یک نسخه از آن کتاب در کتابخانه موجود است و دو تای آن‌ها در دست امانت هستند که یکی از امانت دارها 15 روز است کتاب را در تاریخ معینی تحویل نداده است.

کد بدنه اصلی برنامه:
var myfacade=new FacadeBookLoan();
var loansCount= myfacade.IsLoanable(2);
Console.WriteLine(loansCount > 0 ? "you can loan the book" : "you can't loan the book");
خروجی برنامه:
please take back the book to the library : 324324324324
you can loan the book
مطالب
بازنویسی سطح دوم کش برای Entity framework 6
چندی قبل مطلبی را در مورد پیاده سازی سطح دوم کش در EF در این سایت مطالعه کردید. اساس آن مقاله‌ای بود که نحوه‌ی کش کردن اطلاعات حاصل از LINQ to Objects را بیان کرده بود (^). این مقاله پایه‌ی بسیاری از سیستم‌های کش مشابه نیز شده‌است (^ و ^ و ...).
مشکل مهم این روش عدم سازگاری کامل آن با EF است. برای مثال در آن تفاوتی بین (Include(x=>x.Tags و (Include(x=>x.Users وجود ندارد. به همین جهت در این نوع موارد، قادر به تولید کلید منحصربفردی جهت کش کردن اطلاعات یک کوئری مشخص نیست. در اینجا یک کوئری LINQ، به معادل رشته‌ای آن تبدیل می‌شود و سپس Hash آن محاسبه می‌گردد. این هش، کلید ذخیره سازی اطلاعات حاصل از کوئری، در سیستم کش خواهد بود. زمانیکه دو کوئری Include دار متفاوت EF، هش‌های یکسانی را تولید کنند، عملا این سیستم کش، کارآیی خودش را از دست می‌دهد. برای رفع این مشکل پروژه‌ی دیگری به نام EF cache ارائه شده‌است. این پروژه بسیار عالی طراحی شده و می‌تواند جهت ایده دادن به تیم EF نیز بکار رود. اما در آن فرض بر این است که شما می‌خواهید کل سیستم را در یک کش قرار دهید. وارد مکانیزم DBCommand و DataReader می‌شود و در آن‌جا کار کش کردن تمام کوئری‌ها را انجام می‌دهد؛ مگر آنکه به آن اعلام کنید از کوئری‌های خاصی صرفنظر کند.
با توجه به این مشکلات، روش بهتری برای تولید هش یک کوئری LINQ to Entities بر اساس کوئری واقعی SQL تولید شده توسط EF، پیش از ارسال آن به بانک اطلاعاتی به صورت زیر وجود دارد:
        private static ObjectQuery TryGetObjectQuery<T>(IQueryable<T> source)
        {
            var dbQuery = source as DbQuery<T>;

            if (dbQuery != null)
            {
                const BindingFlags privateFieldFlags = 
                    BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public;

                var internalQuery =
                    source.GetType().GetProperty("InternalQuery", privateFieldFlags)
                        .GetValue(source);

                return
                    (ObjectQuery)internalQuery.GetType().GetProperty("ObjectQuery", privateFieldFlags)
                        .GetValue(internalQuery);
            }

            return null;
        }
این متد یک کوئری LINQ مخصوص EF را دریافت می‌کند و با کمک Reflection، اطلاعات درونی آن که شامل ObjectQuery اصلی است را استخراج می‌کند. سپس فراخوانی متد objectQuery.ToTraceString بر روی حاصل آن، سبب تولید SQL معادل کوئری LINQ اصلی می‌گردد. همچنین objectQuery امکان دسترسی به پارامترهای تنظیم شده‌ی کوئری را نیز میسر می‌کند. به این ترتیب می‌توان به معادل رشته‌ای منطقی‌تری از یک کوئری LINQ رسید که قابلیت تشخیص JOINها و متد Include نیز به صورت خودکار در آن لحاظ شده‌است.

این اطلاعات، پایه‌ی تهیه‌ی کتابخانه‌ی جدیدی به نام EFSecondLevelCache گردید. برای نصب آن کافی است دستور ذیل را در کنسول پاورشل نیوگت صادر کنید:
 PM> Install-Package EFSecondLevelCache
سپس برای کش کردن کوئری معمولی مانند:
 var products = context.Products.Include(x => x.Tags).FirstOrDefault();
می‌توان از متد جدید Cacheable آن به نحو ذیل استفاده کرد (این روش بسیار تمیزتر است از روش مقاله‌ی قبلی و امکان استفاده‌ی از انواع و اقسام متدهای EF را به صورت متداولی میسر می‌کند):
 var products = context.Products.Include(x => x.Tags).Cacheable().FirstOrDefault(); // Async methods are supported too.

پس از آن نیاز است کدهای کلاس Context خود را نیز به نحو ذیل ویرایش کنید (به روز رسانی شده‌ی آن در اینجا):
namespace EFSecondLevelCache.TestDataLayer.DataLayer
{
    public class SampleContext : DbContext
    {
        // public DbSet<Product> Products { get; set; }
 
        public SampleContext()
            : base("connectionString1")
        {
        }
 
        public override int SaveChanges()
        {
            return SaveAllChanges(invalidateCacheDependencies: true);
        }
 
        public int SaveAllChanges(bool invalidateCacheDependencies = true)
        {
            var changedEntityNames = getChangedEntityNames();
            var result = base.SaveChanges();
            if (invalidateCacheDependencies)
            {
               new EFCacheServiceProvider().InvalidateCacheDependencies(changedEntityNames);
            }
            return result;
        }
 
        private string[] getChangedEntityNames()
        {
            return this.ChangeTracker.Entries()
                .Where(x => x.State == EntityState.Added ||
                            x.State == EntityState.Modified ||
                            x.State == EntityState.Deleted)
                .Select(x => ObjectContext.GetObjectType(x.Entity.GetType()).FullName)
                .Distinct()
                .ToArray();
        }
    }
}
متد InvalidateCacheDependencies سبب می‌شود تا اگر تغییری در بانک اطلاعاتی رخ‌داد، به صورت خودکار کش‌های کوئری‌های مرتبط غیر معتبر شوند و برنامه اطلاعات قدیمی را از کش نخواند.


کدهای کامل این پروژه را از مخزن کد ذیل می‌توانید دریافت کنید:
EFSecondLevelCache



پ.ن.
این کتابخانه هم اکنون در سایت جاری در حال استفاده است.
مطالب دوره‌ها
بررسی سرعت و کارآیی AutoMapper
AutoMapper تنها کتابخانه‌ی نگاشت اشیاء مخصوص دات نت نیست. در این مطلب قصد داریم سرعت AutoMapper را با حالت نگاشت دستی، نگاشت توسط EmitMapper و نگاشت به کمک ValueInjecter، مقایسه کنیم.


مدل مورد استفاده

در اینجا قصد داریم، شیء User را یک میلیون بار توسط روش‌های مختلف، به خودش نگاشت کنیم و سرعت انجام این‌کار را در حالت‌های مختلف اندازه گیری نمائیم:
public class User
{
    public int Id { get; set; }
    public string UserName { get; set; }
    public string Password { get; set; }
    public DateTime LastLogin { get; set; }
}


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

برای کاهش کدهای تکراری، می‌توان قسمت تکرار شونده را به صورت یک Action، در بین سایر کدهایی که هر بار نیاز است به یک شکل فراخوانی شوند، قرار داد:
public static void RunActionMeasurePerformance(Action action)
{
    GC.Collect();
    var initMemUsage = Process.GetCurrentProcess().WorkingSet64;
    var stopwatch = new Stopwatch();
    stopwatch.Start();
    action();
    stopwatch.Stop();
    var currentMemUsage = Process.GetCurrentProcess().WorkingSet64;
    var memUsage = currentMemUsage - initMemUsage;
    if (memUsage < 0) memUsage = 0;
    Console.WriteLine("Elapsed time: {0}, Memory Usage: {1:N2} KB", stopwatch.Elapsed, memUsage / 1024);
}


انجام آزمایش

در مثال زیر، ابتدا یک میلیون شیء User ایجاد می‌شوند و سپس هربار توسط روش‌های مختلفی به شیء User دیگری نگاشت می‌شوند:
static void Main(string[] args)
{
    var length = 1000000;
    var users = new List<User>(length);
    for (var i = 0; i < length; i++)
    {
 
        var user = new User
        {
            Id = i,
            UserName = "User" + i,
            Password = "1" + i + "2" + i,
            LastLogin = DateTime.Now
        };
        users.Add(user);
    }
 
    Console.WriteLine("Custom mapping");
    RunActionMeasurePerformance(() =>
    {
        var userList =
            users.Select(
                o =>
                    new User
                    {
                        Id = o.Id,
                        UserName = o.UserName,
                        Password = o.Password,
                        LastLogin = o.LastLogin
                    }).ToList();
    });
 
    Console.WriteLine("EmitMapper mapping");
    RunActionMeasurePerformance(() =>
    {
        var map = EmitMapper.ObjectMapperManager.DefaultInstance.GetMapper<User, User>();
        var emitUsers = users.Select(o => map.Map(o)).ToList();
    });
 
    Console.WriteLine("ValueInjecter mapping");
    RunActionMeasurePerformance(() =>
    {
        var valueUsers = users.Select(o => (User)new User().InjectFrom(o)).ToList();
    });
 
    Console.WriteLine("AutoMapper mapping, DynamicMap using List");
    RunActionMeasurePerformance(() =>
    {
        var userMap = Mapper.DynamicMap<List<User>>(users).ToList();
    });
 
    Console.WriteLine("AutoMapper mapping, Map using List");
    RunActionMeasurePerformance(() =>
    {
        var userMap = Mapper.Map<List<User>>(users).ToList();
    });
 
    Console.WriteLine("AutoMapper mapping, Map using IEnumerable");
    RunActionMeasurePerformance(() =>
    {
        var userMap = Mapper.Map<IEnumerable<User>>(users).ToList();
    });
 
 
    Console.ReadKey();
}


خروجی آزمایش

در ادامه یک نمونه‌ی خروجی نهایی را مشاهده می‌کنید:
 Custom mapping
Elapsed time: 00:00:00.4869463, Memory Usage: 58,848.00 KB

EmitMapper mapping
Elapsed time: 00:00:00.6068193, Memory Usage: 62,784.00 KB

ValueInjecter mapping
Elapsed time: 00:00:15.6935578, Memory Usage: 21,140.00 KB

AutoMapper mapping, DynamicMap using List
Elapsed time: 00:00:00.6028971, Memory Usage: 7,164.00 KB

AutoMapper mapping, Map using List
Elapsed time: 00:00:00.0106244, Memory Usage: 680.00 KB

AutoMapper mapping, Map using IEnumerable
Elapsed time: 00:00:01.5954456, Memory Usage: 40,248.00 KB

ValueInjecter از همه کندتر است.
EmitMapper از AutoMapper سریعتر است (البته فقط در بعضی از حالت‌ها).
سرعت AutoMapper زمانیکه نوع آرگومان ورودی به آن به IEnumerable تنظیم شود، نسبت به حالت استفاده از List معمولی، به مقدار قابل توجهی کندتر است. زمانیکه از List استفاده شده، سرعت آن از سرعت حالت نگاشت دستی (مورد اول) هم بیشتر است.
متد DynamicMap اندکی کندتر است از متد Map.

در این بین اگر ValueInjecter را از لیست حذف کنیم، به نمودار ذیل خواهیم رسید (اعداد آن برحسب ثانیه هستند):



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


کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید:
AM_Sample06.zip
مطالب
نمایش حداکثر اندازه مجاز فایل قابل آپلود به کاربر، در ASP.Net

گاهی از اوقات قبل از درگیر شدن با کاربران (!)، بهتر است حداکثر اندازه مجاز فایل قابل ارسال به سرور را به آن‌ها نمایش داد. درغیراینصورت باید پاسخگوی این باشید که چرا فایل 100 مگابایتی که من ارسال کردم، ذخیره نشده و برنامه کار نمی‌کنه!
خطای دریافتی این خواهد بود: Maximum request length exceeded
در ASP.Net اگر هیچ تنظیم خاصی صورت نگرفته باشد، حداکثر اندازه فایل قابل ارسال به سرور، 4 مگابایت است. این مورد را در machine.config و یا در web.config می‌توان تغییر داد.
برای مثال، جهت بالا بردن اندازه فایل قابل ارسال به سرور در وب کانفیگ برنامه به 39 مگابایت، می‌توان سطر زیر را به قسمت system.web اضافه کرد.
<httpRuntime executionTimeout="1200" maxRequestLength="39936" />

البته در این حالت بهتر است executionTimeout را نیز تنظیم نمود (بر اساس ثانیه) تا یک فایل حجیم را بتوانند آپلود کنند و در این حین مشکل timeout رخ ندهد (در اینجا به 20 دقیقه تنظیم شده است).

اما یک نکته را هم باید درنظر داشت. اگر هاست مورد استفاده شما فایل machine.config را قفل کرده باشد (که از لحاظ امنیتی توصیه می‌شود)، سطر فوق در web.config هیچ تاثیری نخواهد داشت.

به همین منظور کلاس زیر را تهیه کرده‌ام که تمامی این موارد را لحاظ می‌کند.
ابتدا مقدار پیش فرض 4 مگابایت درنظر گرفته خواهد شد.
سپس سعی می‌شود که مقدار مجاز MaxRequestLength از فایل machine.config خوانده شود. همچنین وضعیت قفل بودن آن نیز دریافت می‌شود.
اگر این قسمت قابل خواندن بود و همچنین قفل نشده بود، مقدار تنظیم شده maxRequestLength در وب کانفیگ، دریافت و استفاده خواهد شد.
و در آخر، اندازه دریافتی، که بر اساس KB است به شکلی قابل خواندن بازگشت داده می‌شود.

using System;
using System.Configuration;
using System.Web.Configuration;

/// <summary>
/// کلاسی جهت نمایش اندازه مجاز فایل قابل ارسال به سرور
/// </summary>
public class CMaxLimit
{
/// <summary>
/// اندازه مجاز فایل قابل ارسال به سرور
/// </summary>
/// <returns></returns>
public static string MaxFileUploadSizeLimit()
{
//مقدار پیش فرض
int resultKB = 4096;

//machine.config
Configuration mConfig =
WebConfigurationManager.OpenMachineConfiguration();
bool mConfigIsLocked = false;
HttpRuntimeSection section =
mConfig.GetSection("system.web/httpRuntime") as HttpRuntimeSection;
if (section != null)
{
resultKB = section.MaxRequestLength;
mConfigIsLocked = section.ElementInformation.IsLocked;
}

//web.config
if (!mConfigIsLocked)
{
HttpRuntimeSection httpRuntimeSection =
WebConfigurationManager.GetSection("system.web/httpRuntime") as HttpRuntimeSection;
if (httpRuntimeSection != null)
{
resultKB = httpRuntimeSection.MaxRequestLength;
}
}

return
SizeToString(resultKB * 1024);
}

/// <summary>
/// نمایش اندازه یک فایل به صورتی قابل درک
/// </summary>
/// <param name="len">اندازه فایل</param>
/// <returns></returns>
public static string SizeToString(long len)
{
int order = 0;
string[] sizes = new[] { "B", "KB", "MB", "GB" };
while (len >= 1024 && order + 1 < sizes.Length)
{
order++;
len = len / 1024;
}
return String.Format("{0:0.##} {1}", len, sizes[order]);
}
}