مطالب
نحوه استخراج آیکون‌های یک قلم در WPF
مطلب «نحوه نمایش تمام آیکون‌های تعریف شده در یک قلم در WPF» را در نظر بگیرید. سؤال: اگر در یک برنامه تنها به تعدادی از این آیکون‌ها یا گلیف‌ها نیاز بود آیا می‌توان این‌ها را به صورت مجزا استخراج و استفاده کرد؟
پاسخ: بلی. همان کلاس  FontFamily موجود در اسمبلی PresentationCore.dll، امکان تبدیل یک گلیف را به معادل هندسی آن نیز دارد. در ادامه کدهای آن‌را مرور خواهیم کرد:
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Windows;
using System.Windows.Media;
using CrMap.Models;

namespace CrMap.ViewModels
{
    public class CrMapViewModel
    {
        public IList<Symbol> Symbols { set; get; }
        public int GridRows { set; get; }
        public int GridCols { set; get; }

        public CrMapViewModel()
        {
            fillDataSource();
        }

        private void fillDataSource()
        {
            Symbols = new List<Symbol>();
            GridCols = 15;

            var fontFamily = new FontFamily(new Uri("pack://application:,,,/"), "/Fonts/#whhglyphs");

            GlyphTypeface glyph = null;
            Typeface glyphTypeface = null;
            foreach (var typeface in fontFamily.GetTypefaces())
            {
                if (typeface.TryGetGlyphTypeface(out glyph) && (glyph != null))
                {
                    glyphTypeface = typeface;
                    break;
                }
            }

            if (glyph == null)
                throw new InvalidOperationException("Couldn't find a GlyphTypeface.");

            GridRows = (glyph.CharacterToGlyphMap.Count / GridCols) + 1;

            foreach (var item in glyph.CharacterToGlyphMap)
            {
                var index = item.Key;
                Symbols.Add(new Symbol
                {
                    Character = Convert.ToChar(index),
                    CharacterCode = string.Format("&#x{0:X}", index)
                });

                saveToFile(glyphTypeface, index);
            }
        }

        private static void saveToFile(Typeface glyphTypeface, int index)
        {
            var formattedText = new FormattedText(
                                        textToFormat: Convert.ToChar(index).ToString(),
                                        culture: new CultureInfo("en-us"),
                                        flowDirection: FlowDirection.LeftToRight,
                                        typeface: glyphTypeface,
                                        emSize: 20,
                                        foreground: Brushes.Black);
            var geometry = formattedText.BuildGeometry(new Point(0, 0));
            var path = geometry.GetFlattenedPathGeometry();
            File.WriteAllText(index + ".path", path.ToString());
        }
    }
}
در اینجا تنها متد saveToFile در مقایسه با قسمت قبل افزوده شده است.
شیء FormattedText دارای متدی است به نام BuildGeometry که اطلاعات یک گلیف را تبدیل به معادل هندسی آن می‌کند. سپس توسط GetFlattenedPathGeometry معادل Path آن‌را می‌توان بدست آورد. برای مثال اگر پس از اجرای این مثال، به فایل 48.path تولیدی آن مراجعه کنیم، چنین خروجی را می‌توان مشاهده کرد:
 F1M5,7.47150993347168L5,17.2566661834717 5.732421875,19.0242443084717 7.5,19.7566661834717
12.5,19.7566661834717 13.69140625,19.4441661834717 5,7.47150993347168z
M7.5,4.75666618347168L6.30859375,5.06916618347168 15,17.0418224334717
15,7.25666618347168 14.267578125,5.48908805847168 12.5,4.75666618347168
7.5,4.75666618347168z M7.5,2.25666618347168L12.5,2.25666618347168
14.4189453125,2.62287712097168 16.03515625,3.72150993347168 17.1337890625,5.33772134780884
17.5,7.25666618347168 17.5,17.2566661834717 17.1337890625,19.1756114959717
16.03515625,20.7918224334717 14.4189453125,21.8904552459717 12.5,22.2566661834717
7.5,22.2566661834717 5.5810546875,21.8904552459717 3.96484375,20.7918224334717
2.8662109375,19.1756114959717 2.5,17.2566661834717 2.5,7.25666618347168 2.8662109375,5.33772134780884
3.96484375,3.72150993347168 5.5810546875,2.62287712097168 7.5,2.25666618347168z
که برای استفاده از اطلاعات آن در WPF می‌توان نوشت:
<Path Stroke="DarkRed" Fill="Black" Data="F1M5,7.47150993347168L5,17.2566661834717
              5.732421875,19.0242443084717 7.5,19.7566661834717 12.5,19.7566661834717 13.69140625,19.4441661834717 
              5,7.47150993347168z M7.5,4.75666618347168L6.30859375,5.06916618347168 15,17.0418224334717 
              15,7.25666618347168 14.267578125,5.48908805847168 12.5,4.75666618347168 7.5,4.75666618347168z 
              M7.5,2.25666618347168L12.5,2.25666618347168 14.4189453125,2.62287712097168 
              16.03515625,3.72150993347168 17.1337890625,5.33772134780884 17.5,7.25666618347168 
              17.5,17.2566661834717 17.1337890625,19.1756114959717 16.03515625,20.7918224334717 
              14.4189453125,21.8904552459717 12.5,22.2566661834717 7.5,22.2566661834717 
              5.5810546875,21.8904552459717 3.96484375,20.7918224334717 2.8662109375,19.1756114959717 
              2.5,17.2566661834717 2.5,7.25666618347168 2.8662109375,5.33772134780884 
              3.96484375,3.72150993347168 
              5.5810546875,2.62287712097168 7.5,2.25666618347168z" />
مطالب دوره‌ها
گزارش درصد پیشرفت عملیات در اعمال غیرهمزمان
گزارش درصد پیشرفت عملیات در اعمال طولانی، امکان لغو هوشمندانه‌تری را برای کاربر فراهم می‌کند. در دات نت 4.5 دو روش برای گزارش درصد پیشرفت عملیات اعمال غیرهمزمان تدارک دیده شده‌اند:
- اینترفیس جنریک IProgress واقع در فضای نام System
- کلاس جنریک Progress واقع در فضای نام System

در اینجا وهله‌ی از پیاده سازی اینترفیس IProgress به Task ارسال می‌شود. در این بین، عملیات در حال انجام با فراخوانی متد Report آن می‌تواند در هر زمانیکه نیاز باشد، درصد پیشرفت کار را گزارش کند.
namespace System
{
  public interface IProgress<in T>
  {
      void Report( T value );
  }
}
البته برای اینکه کار تعریف و پیاده سازی اینترفیس IProgress اندکی کاهش یابد، کلاس توکار Progress برای اینکار تدارک دیده شده‌است. نکته‌ی مهم آن استفاده از Synchronization Context برای ارائه گزارش پیشرفت در ترد UI است تا به سادگی بتوان از نتایج دریافتی، در رابط کاربری استفاده کرد.
namespace System
{
  public class Progress<T> : IProgress<T>
  {
    public Progress();
    public Progress( Action<T> handler );
    protected virtual void OnReport( T value );
  }
}


یک مثال از گزارش درصد پیشرفت عملیات به همراه پشتیبانی از لغو آن

using System;
using System.Threading;
using System.Threading.Tasks;

namespace Async09
{
    public class TestProgress
    {
        public async Task DoProcessingReportProgress()
        {
            var progress = new Progress<int>(percent =>
            {
                Console.WriteLine(percent + "%");
            });

            var cts = new CancellationTokenSource();

            // call some where cts.Cancel();

            try
            {
                await doProcessing(progress, cts.Token);
            }
            catch (OperationCanceledException ex)
            {
                //todo: handle cancellations
                Console.WriteLine(ex);
            }

            Console.WriteLine("Done!");
        }

        private static async Task doProcessing(IProgress<int> progress, CancellationToken ct)
        {
            await Task.Run(async () =>
            {
                for (var i = 0; i != 100; ++i)
                {
                    await Task.Delay(100, ct);
                    if (progress != null)
                        progress.Report(i);

                    ct.ThrowIfCancellationRequested();
                }
            }, ct);
        }
    }
}
متد private static async Task doProcessing طوری طراحی شده‌است که از مفاهیم لغو یک عملیات غیرهمزمان و همچنین گزارش درصد پیشرفت آن توسط اینترفیس IProgress پشتیبانی می‌کند. در اینجا هر زمانیکه نیاز به گزارش درصد پیشرفت باشد، متد Report وهله‌ی ارسالی به آرگومان progress فراخوانی خواهد شد.
برای تدارک این وهله، از کلاس توکار Progress دات نت در متد public async Task DoProcessingReportProgress استفاده شده‌است.
این متد جنریک بوده و برای مثال نوع آن در اینجا int تعریف شده‌است. سازنده‌ی آن می‌تواند یک callback را قبول کند. هر زمانیکه متد Report در متد doProcessing فراخوانی گردد، این callback در سمت کدهای استفاده کننده، فراخوانی خواهد شد. مثلا توسط مقدار آن می‌توان یک Progress bar را نمایش داد.
به علاوه روش دیگری را در مورد لغو یک عملیات در اینجا ملاحظه می‌کنید. متد ThrowIfCancellationRequested نیز سبب خاتمه‌ی عملیات می‌گردد؛ البته اگر در کدهای برنامه در جایی متد Cancel توکن، فراخوانی گردد. برای مثال یک دکمه‌ی لغو عملیات در صفحه قرارگیرد و کار آن صرفا فراخوانی cts.Cancel باشد.
مطالب
سفارشی سازی Header و Footer در PdfReport
صورت مساله:
- می‌خواهیم footer پیش فرض PdfReport را که تاریخ را در یک سمت، و شماره صفحه را در سمتی دیگر نمایش می‌دهد، به عبارت «صفحه x از n» تغییر دهیم.
- می‌خواهیم در Header گزارش بجای Header پیش فرض PdfReport یکی از قالب‌های PDF تهیه شده توسط Open Office را نمایش دهیم (و یا هر ساختار دیگری را).

تمام اجزای PdfReport جهت امکان اعمال تغییرات کلی و توسعه آن‌ها طراحی شده‌اند؛ قالب‌ها، هدر، فوتر، منابع داده، قالب‌های نمایش سلول‌ها، تعریف توابع تجمعی سفارشی و غیره. جهت سهولت کار، به ازای هر یک از این موارد، پیاده سازی‌های پیش فرضی در PdfReport قرار دارند، امکان اگر مورد رضایت شما نیستند ... از بنیان تغییرشان دهید! (و همچنین اگر مورد جالبی را پیاده سازی کردید، می‌توانید به عنوان یک وصله جدید ارائه دهید تا به پروژه اضافه شود)
ضمنا این مطالب سفارشی سازی نیاز به آشنایی با ساختار iTextSharp را نیز دارند؛ در حد ایجاد یک جدول ساده باید با iTextSharp آشنا باشید.

مدل‌های مورد استفاده:
namespace PdfReportSamples.Models
{
    public class Task
    {
        public int Id { set; get; }
        public string Name { set; get; }
        public int PercentCompleted { set; get; }
        public bool IsActive { set; get; }
        public User Assignee { set; get; }
    }
}

using System;

namespace PdfReportSamples.Models
{
    public class User
    {
        public int Id { set; get; }
        public string Name { set; get; }
        public string LastName { set; get; }
        public long Balance { set; get; }
        public DateTime RegisterDate { set; get; }
    }
}
توسط این مدل‌ها قصد داریم تعدادی فعالیت (Task) را که به تعدادی کاربر انتساب یافته است، نمایش دهیم. همچنین نمایش مقادیر خواص تو در تو  نیز در اینجا مد نظر است؛ برای مثال ستونی مانند این:
 column.PropertyName<Task>(x => x.Assignee.Name) 
کدهای کامل مثال را در ادامه ملاحظه خواهید نمود:
using System;
using System.Collections.Generic;
using System.Drawing;
using PdfReportSamples.Models;
using PdfRpt.Core.Contracts;
using PdfRpt.FluentInterface;

namespace PdfReportSamples.CustomHeaderFooter
{
    public class CustomHeaderFooterPdfReport
    {
        readonly CustomHeader _customHeader = new CustomHeader();
        public IPdfReportData CreatePdfReport()
        {
            return new PdfReport().DocumentPreferences(doc =>
            {
                doc.RunDirection(PdfRunDirection.LeftToRight);
                doc.Orientation(PageOrientation.Portrait);
                doc.PageSize(PdfPageSize.A4);
                doc.DocumentMetadata(new DocumentMetadata { Author = "Vahid", Application = "PdfRpt", Keywords = "Test", Subject = "Test Rpt", Title = "Test" });
            })
            .DefaultFonts(fonts =>
            {
                fonts.Path(Environment.GetEnvironmentVariable("SystemRoot") + "\\fonts\\tahoma.ttf",
                                  Environment.GetEnvironmentVariable("SystemRoot") + "\\fonts\\verdana.ttf");
            })
            .PagesFooter(footer =>
            {
                footer.CustomFooter(new CustomFooter(footer.PdfFont, PdfRunDirection.LeftToRight));
            })
            .PagesHeader(header =>
            {
                header.CustomHeader(_customHeader);
            })
            .MainTableTemplate(template =>
            {
                template.BasicTemplate(BasicTemplate.SilverTemplate);
            })
            .MainTablePreferences(table =>
            {
                table.ColumnsWidthsType(TableColumnWidthType.Relative);
                table.MultipleColumnsPerPage(new MultipleColumnsPerPage
                {
                    ColumnsGap = 22,
                    ColumnsPerPage = 2,
                    ColumnsWidth = 250,
                    IsRightToLeft = false,
                    TopMargin = 7
                });
            })
            .MainTableDataSource(dataSource =>
            {
                var rows = new List<Task>();
                var rnd = new Random();
                for (int i = 1; i < 210; i++)
                {
                    rows.Add(new Task
                    {
                        Assignee = new User
                        {
                            Id = i,
                            Name = "user-" + i
                        },
                        IsActive = rnd.Next(0, 2) == 1 ? true : false,
                        Name = "task-" + i
                    });
                }
                dataSource.StronglyTypedList(rows);
            })
            .MainTableColumns(columns =>
            {
                columns.AddColumn(column =>
                {
                    column.PropertyName("rowNo");
                    column.IsRowNumber(true);
                    column.CellsHorizontalAlignment(HorizontalAlignment.Center);
                    column.IsVisible(true);
                    column.Order(0);
                    column.Width(1);
                    column.HeaderCell("#");
                });

                columns.AddColumn(column =>
                {
                    column.PropertyName<Task>(x => x.Name);
                    column.CellsHorizontalAlignment(HorizontalAlignment.Center);
                    column.IsVisible(true);
                    column.Order(1);
                    column.Width(3);
                    column.HeaderCell("Task Name");
                });

                columns.AddColumn(column =>
                {
                    column.PropertyName<Task>(x => x.Assignee.Name); // nested property support
                    column.CellsHorizontalAlignment(HorizontalAlignment.Center);
                    column.IsVisible(true);
                    column.Order(2);
                    column.Width(3);
                    column.HeaderCell("Assignee");
                });

                columns.AddColumn(column =>
                {
                    column.PropertyName<Task>(x => x.IsActive);
                    column.CellsHorizontalAlignment(HorizontalAlignment.Center);
                    column.IsVisible(true);
                    column.Order(3);
                    column.Width(2);
                    column.HeaderCell("Active");
                    column.ColumnItemsTemplate(template =>
                    {
                        template.Checkmark(checkmarkFillColor: Color.Green, crossSignFillColor: Color.DarkRed);
                    });
                });
            })
            .MainTableEvents(events =>
            {
                events.DataSourceIsEmpty(message: "There is no data available to display.");
            })
            .Export(export =>
            {
                export.ToExcel();
            })
            .Generate(data => data.AsPdfFile(AppPath.ApplicationPath + "\\Pdf\\CustomHeaderFooterPdfReportSample.pdf"));
        }
    }
}

به همراه Header سفارشی:
using System.Collections.Generic;
using iTextSharp.text;
using iTextSharp.text.pdf;
using PdfRpt.Core.Contracts;
using PdfRpt.Core.Helper;

namespace PdfReportSamples.CustomHeaderFooter
{
    public class CustomHeader : IPageHeader
    {
        public PdfPTable RenderingGroupHeader(Document pdfDoc, PdfWriter pdfWriter, IList<CellData> rowdata, IList<SummaryCellData> summaryData)
        {
            return null;
        }

        Image _image;
        public PdfPTable RenderingReportHeader(Document pdfDoc, PdfWriter pdfWriter, IList<SummaryCellData> summaryData)
        {
            if (_image == null) //cache is empty
            {
                var templatePath = AppPath.ApplicationPath + "\\data\\PdfHeaderTemplate.pdf";
                _image = PdfImageHelper.GetITextSharpImageFromPdfTemplate(pdfWriter, templatePath);
            }

            var table = new PdfPTable(1);
            var cell = new PdfPCell(_image, true) { Border = 0 };
            table.AddCell(cell);
            return table;
        }
    }
}

و Footer سفارشی استفاده شده:
using System.Collections.Generic;
using iTextSharp.text;
using iTextSharp.text.pdf;
using PdfRpt.Core.Contracts;

namespace PdfReportSamples.CustomHeaderFooter
{
    public class CustomFooter : IPageFooter
    {
        PdfContentByte _pdfContentByte;
        readonly IPdfFont _pdfRptFont;
        readonly Font _font;
        readonly PdfRunDirection _direction;
        PdfTemplate _template;

        public CustomFooter(IPdfFont pdfRptFont, PdfRunDirection direction)
        {
            _direction = direction;
            _pdfRptFont = pdfRptFont;
            _font = _pdfRptFont.Fonts[0];
        }

        public void ClosingDocument(PdfWriter writer, Document document, IList<SummaryCellData> columnCellsSummaryData)
        {
            _template.BeginText();
            _template.SetFontAndSize(_pdfRptFont.Fonts[0].BaseFont, 8);
            _template.SetTextMatrix(0, 0);
            _template.ShowText((writer.PageNumber - 1).ToString());
            _template.EndText();
        }

        public void PageFinished(PdfWriter writer, Document document, IList<SummaryCellData> columnCellsSummaryData)
        {
            var pageSize = document.PageSize;
            var text = "Page " + writer.PageNumber + " / ";
            var textLen = _font.BaseFont.GetWidthPoint(text, _font.Size);
            var center = (pageSize.Left + pageSize.Right) / 2;
            var align = _direction == PdfRunDirection.RightToLeft ? Element.ALIGN_RIGHT : Element.ALIGN_LEFT;

            ColumnText.ShowTextAligned(
                        canvas: _pdfContentByte,
                        alignment: align,
                        phrase: new Phrase(text, _font),
                        x: center,
                        y: pageSize.GetBottom(25),
                        rotation: 0,
                        runDirection: (int)_direction,
                        arabicOptions: 0);

            var x = _direction == PdfRunDirection.RightToLeft ? center - textLen : center + textLen;
            _pdfContentByte.AddTemplate(_template, x, pageSize.GetBottom(25));
        }

        public void DocumentOpened(PdfWriter writer, IList<SummaryCellData> columnCellsSummaryData)
        {
            _pdfContentByte = writer.DirectContent;
            _template = _pdfContentByte.CreateTemplate(50, 50);
        }
    }
}

البته لازم به ذکر است که تمام این کدها به پوشه Samples سورس پروژه نیز جهت سهولت دسترسی، اضافه شده‌اند .

توضیحات:

برای پیاده سازی Header و Footer سفارشی در PdfReport نیاز خواهید داشت تا دو اینترفیس IPageHeader و IPageFooter را پیاده سازی کنید.
ساختار IPageHeader را در ذیل ملاحظه می‌کنید:
using System.Collections.Generic;
using iTextSharp.text;
using iTextSharp.text.pdf;

namespace PdfRpt.Core.Contracts
{
    public interface IPageHeader
    {
        PdfPTable RenderingGroupHeader(Document pdfDoc, PdfWriter pdfWriter, IList<CellData> newGroupInfo, IList<SummaryCellData> summaryData);

        PdfPTable RenderingReportHeader(Document pdfDoc, PdfWriter pdfWriter, IList<SummaryCellData> summaryData);
    }
}

RenderingGroupHeader مرتبط است به مباحث گروه بندی اطلاعات و گزارشات master-detail که در قسمت‌های بعد به آن‌ها اشاره خواهد شد. چون در اینجا به آن نیازی نداشتیم، تنها کافی است متد متناظر با آن، null بر گرداند که در کلاس CustomHeader فوق قابل مشاهده است.
متد RenderingReportHeader به ازای تولید هر صفحه جدید، فراخوانی خواهد شد. به عبارتی می‌توانید در صفحات مختلف، هدرهای مختلفی را نمایش دهید.
خروجی هر دو متد در اینجا یک جدول از نوع PdfPTable است. بنابراین هر نوع ساختار دلخواهی را که علاقمند هستید به شکل یک PdfPTable ایجاد کرده و بازگشت دهید. این جدول در هدر صفحات ظاهر خواهد شد.
برای نمونه در کلاس CustomHeader، یک قالب تهیه شده توسط Open Office توسط متد توکار PdfImageHelper.GetITextSharpImageFromPdfTemplate دریافت و تبدیل به تصویر می‌شود. این تصویر از نوع تصاویر قابل درک توسط iTextSharp است و نه اینکه واقعا تبدیل به یک تصویر معمولی مثلا از نوع bmp شود. سپس این تصویر، در یک ردیف از جدولی قرار داده شده و این جدول بازگشت داده می‌شود.
در کل یا توسط کار با PdfPTable می‌توانید یک هدر غیرپیش فرض را طراحی کنید و یا می‌توانید توسط ابزارهای بصری مانند Open Office یک قالب خاص را برای آن تهیه کرده و به روشی که ذکر شد و کدهای آن‌را ملاحظه می‌کنید، بارگذاری و استفاده کنید. این قالب‌ها در مسیر Bin\Data سورس‌های پروژه قرار داده شده‌اند.

ساختار IPageFooter به صورت زیر است:
using iTextSharp.text;
using iTextSharp.text.pdf;
using System.Collections.Generic;

namespace PdfRpt.Core.Contracts
{
    public interface IPageFooter
    {
        void DocumentOpened(PdfWriter writer, IList<SummaryCellData> columnCellsSummaryData);

        void PageFinished(PdfWriter writer, Document document, IList<SummaryCellData> columnCellsSummaryData);

        void ClosingDocument(PdfWriter writer, Document document, IList<SummaryCellData> columnCellsSummaryData);
    }
}

برای طراحی یک Footer سفارشی کافی است اینترفیس فوق را پیاده سازی کنید که نمونه‌ای از آن‌را در کدهای کلاس CustomFooter ملاحظه می‌نمائید.
متد DocumentOpened، با وهله سازی شیء Document فراخوانی می‌شود.
متد PageFinished هر بار پیش از اتمام کار صفحه جاری و افزوده شدن آن به Document فراخوانی می‌گردد.
متد ClosingDocument، در زمان بسته شدن شیء Document فراخوانی خواهد شد.

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

استفاده از این کلاس‌های سفارشی نیز همواره به شکل زیر خواهد بود:
readonly CustomHeader _customHeader = new CustomHeader();
//...
.PagesFooter(footer =>
{
   footer.CustomFooter(new CustomFooter(footer.PdfFont, PdfRunDirection.LeftToRight));
})
.PagesHeader(header =>
{
  header.CustomHeader(_customHeader);
})
کلا در PdfReport هر جایی متدی به نام CustomXYZ را مشاهده کردید، این متد یک اینترفیس را دریافت می‌کند. به عبارتی این امکان را خواهید داشت تا از متدهای پیش فرض مهیا صرفنظر کرده و مطابق نیاز، نسبت به پیاده سازی و استفاده از وهله جدیدی از این اینترفیس تعریف شده، اقدام کنید.
نظرات مطالب
فعال سازی قسمت آپلود تصویر و فایل Kendo UI Editor
اگر thumb‌ها به درستی  نمایش داده نمی‌شود و فقط قسمتی از عکس رو مشاهده می‌کنید ، با استفاده از قطعه کد زیر این مشکل رفع خواهد شد :
1- تعریف کلاس به صورت زیر
public class ImageSize
    {
        public int Height
        {
            get;
            set;
        }

        public int Width
        {
            get;
            set;
        }
    }
2- تعریف کلاس ImageResizer همانند زیر :
public class ImageResizer
    {
        public ImageSize Resize(ImageSize originalSize, ImageSize targetSize)
        {
            var aspectRatio = (float)originalSize.Width / (float)originalSize.Height;
            var width = targetSize.Width;
            var height = targetSize.Height;

            if (originalSize.Width > targetSize.Width || originalSize.Height > targetSize.Height)
            {
                if (aspectRatio > 1)
                {
                    height = (int)(targetSize.Height / aspectRatio);
                }
                else
                {
                    width = (int)(targetSize.Width * aspectRatio);
                }
            }
            else
            {
                width = originalSize.Width;
                height = originalSize.Height;
            }

            return new ImageSize
            {
                Width = Math.Max(width, 1),
                Height = Math.Max(height, 1)
            };
        }
    }

3- تعریف کلاس ThumbnailCreator  همانند نمونه زیر :
 public class ThumbnailCreator
    {
        private static readonly IDictionary<string, ImageFormat> ImageFormats = new Dictionary<string, ImageFormat>{
            {"image/png", ImageFormat.Png},
            {"image/gif", ImageFormat.Gif},
            {"image/jpeg", ImageFormat.Jpeg}
        };

        private readonly ImageResizer resizer;

        public ThumbnailCreator()
        {
            this.resizer = new ImageResizer();
        }

        public byte[] Create(Stream source, ImageSize desiredSize, string contentType)
        {
            using (var image = Image.FromStream(source))
            {
                var originalSize = new ImageSize
                {
                    Height = image.Height,
                    Width = image.Width
                };

                var size = resizer.Resize(originalSize, desiredSize);

                using (var thumbnail = new Bitmap(size.Width, size.Height))
                {
                    ScaleImage(image, thumbnail);

                    using (var memoryStream = new MemoryStream())
                    {
                        thumbnail.Save(memoryStream, ImageFormats[contentType]);

                        return memoryStream.ToArray();
                    }
                }
            }
        }

        private void ScaleImage(Image source, Image destination)
        {
            using (var graphics = Graphics.FromImage(destination))
            {
                graphics.CompositingMode = CompositingMode.SourceCopy;
                graphics.CompositingQuality = CompositingQuality.HighQuality;
                graphics.SmoothingMode = SmoothingMode.AntiAlias;
                graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
                graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;

                graphics.DrawImage(source, 0, 0, destination.Width, destination.Height);
            }
        }
    }

4- تعریف اکشن Thumbnail همانند زیر :
private FileContentResult CreateThumbnail(string physicalPath)
        {
            using (var fileStream = System.IO.File.OpenRead(physicalPath))
            {
                var desiredSize = new ImageSize
                {
                    Width = ThumbnailWidth,
                    Height = ThumbnailHeight
                };


                string contentType = MimeMapping.GetMimeMapping(physicalPath);
                return File(thumbnailCreator.Create(fileStream, desiredSize, contentType), contentType);
            }
        }

و در پایان اکشن GetThumbnail  را همانند زیر تغییر خواهیم داد :
 public virtual ActionResult GetThumbnail(string path)
        {
            path = GetSafeFileAndDirPath(path);
          //  return File(path, contentType); 
            return CreateThumbnail(path);
        }



مطالب
تبدیل زیرنویس‌های خاص پلورال‌سایت به فرمت SRT
یک سری از دوره‌های پلورال‌سایت دارای زیرنویس هستند که تحت عنوان Transcript در کنار آن‌ها قرار گرفته‌اند:


این زیرنویس‌ها فرمت ویژه‌ای دارند:
                <li class="transcript-module">
                    Introduction to ASP.NET MVC 4
                    <ul>
                            <li class="transcript-clip" data-p="author=scott-allen&amp;name=mvc4-building-m1-intro&amp;mode=live&amp;clip=0&amp;course=mvc4-building"><a href="javascript:void(0)" onclick="LaunchPlayerWindow('http://pluralsight.com/training', 'author=scott-allen&amp;name=mvc4-building-m1-intro&amp;mode=live&amp;clip=0&amp;course=mvc4-building');">Introduction</a><br />
                                <div>
                                        <a href="javascript:void(0)" onclick="p(this);" data-s="1.636">Hi, this is Scott Allen and this is the first module in the course design</a>
                                </div>
                            </li>
                            <li class="transcript-clip" data-p="author=scott-allen&amp;name=mvc4-building-m1-intro&amp;mode=live&amp;clip=1&amp;course=mvc4-building"><a href="javascript:void(0)" onclick="LaunchPlayerWindow('http://pluralsight.com/training', 'author=scott-allen&amp;name=mvc4-building-m1-intro&amp;mode=live&amp;clip=1&amp;course=mvc4-building');">Web Platform Installer</a><br />
                                <div>
                                ...
در آن، هر li که دارای کلاسی به نام transcript-clip است، حاوی یک div می‌باشد و این div دارای تعدادی لینک است. این لینک‌ها توسط ویژگی datas آن‌ها که بیانگر زمان شروع گفتگو است، مشخص می‌شوند و همینطور الی آخر. بنابراین اگر بخواهیم برای آن‌ها ساختاری را تهیه کنیم، به کلاس‌های ذیل خواهیم رسید:
    public class TranscriptClip
    {
        public string Title { set; get; }
        public IList<TranscriptItem> TranscriptItems { set; get; }
    }

    public class TranscriptItem
    {
        public double StartTime { set; get; }
        public string Text { set; get; }
    }
هر li دارای کلاس transcript-clip، یک شیء TranscriptClip را تشکیل می‌دهد. هر شیء TranscriptClip می‌تواند داری چندین TranscriptItem باشد.
برای استخراج این اطلاعات، یکی از بهترین ابزارها، کتابخانه HTML Agility pack است که توسط آن می‌توان به liهای یاد شده دسترسی یافت:
 var nodes = doc.DocumentNode.SelectNodes("//li[@class='transcript-clip']/div");
و سپس اطلاعات آن‌ها را استخراج نمود.
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Web;
using HtmlAgilityPack;

namespace PluralsightTranscripts
{
    public class TranscriptClip
    {
        public string Title { set; get; }
        public IList<TranscriptItem> TranscriptItems { set; get; }
    }

    public class TranscriptItem
    {
        public double StartTime { set; get; }
        public string Text { set; get; }
    }

    public class ExtractSubtitle
    {
        public static void ConvertToSrt(string fileName)
        {
            var transcriptClips = extractItems(fileName);
            var itemNumber = 1;
            foreach (var item in transcriptClips)
            {
                transcriptClipToSrt(item, itemNumber);
                itemNumber++;
            }
        }

        private static void transcriptClipToSrt(TranscriptClip item, int itemNumber)
        {                        
            var count = item.TranscriptItems.Count;
            var srtFileContent = transcriptItemsToSrt(item.TranscriptItems, count);
            var fileName = removeIllegalCharacters(string.Format("{0}-{1}.srt", itemNumber.ToString("00"), item.Title));            
            File.WriteAllText(fileName, srtFileContent);
        }

        private static string transcriptItemsToSrt(IList<TranscriptItem> items, int count)
        {
            var lineNumber = 1;
            var sb = new StringBuilder();
            for (int row = 0; row < count; row++)
            {
                sb.AppendLine(lineNumber.ToString(CultureInfo.InvariantCulture));
                sb.AppendLine(getTimeLine(items, count, row));
                sb.AppendLine(items[row].Text);
                sb.AppendLine(string.Empty);
                lineNumber++;
            }
            return sb.ToString();            
        }

        private static string getTimeLine(IList<TranscriptItem> items, int count, int row)
        {
            var startTs = TimeSpan.FromSeconds(items[row].StartTime);
            var endTs = row + 1 < count ? TimeSpan.FromSeconds(items[row + 1].StartTime) : TimeSpan.FromSeconds(items[row].StartTime + 5);
            return string.Format("{0} --> {1}", timeSpanToString(startTs), timeSpanToString(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"));
        }

        private static string removeIllegalCharacters(string fileName)
        {
            string regexSearch = string.Format("{0}{1}",
                                               new string(Path.GetInvalidFileNameChars()),
                                               new string(Path.GetInvalidPathChars()));
            var r = new Regex(string.Format("[{0}]", Regex.Escape(regexSearch)));
            return r.Replace(fileName, ".");
        }

        private static IList<TranscriptClip> extractItems(string fileName)
        {
            var htmlContent = File.ReadAllText(fileName);
            var results = new List<TranscriptClip>();

            var doc = new HtmlDocument
            {
                OptionCheckSyntax = true,
                OptionFixNestedTags = true,
                OptionAutoCloseOnEnd = true,
                OptionDefaultStreamEncoding = Encoding.UTF8
            };
            doc.LoadHtml(htmlContent);

            var nodes = doc.DocumentNode.SelectNodes("//li[@class='transcript-clip']/div");

            foreach (var node in nodes)
            {
                var itemsList = new List<TranscriptItem>();
                var title = node.ParentNode.ChildNodes.First(x => x.Name == "a").InnerText;

                foreach (var childNode in node.ChildNodes)
                {
                    if (childNode.Name != "a") continue;

                    var dataS = childNode.Attributes.First(x => x.Name == "data-s");
                    itemsList.Add(new TranscriptItem
                    {
                        StartTime = double.Parse(dataS.Value),
                        Text = HttpUtility.HtmlDecode(childNode.InnerText.Trim())
                    });
                }

                results.Add(new TranscriptClip { TranscriptItems = itemsList, Title = title });
            }

            return results;
        }
    }
}
اگر این اطلاعات را کنار هم قرار دهیم، به کلاس کمکی فوق خواهیم رسید. کار با گره‌های li شروع می‌شود. سپس در این گره‌ها، کلیه گره‌های a یا لینک‌ها، یافت شده و سپس dataS و متن آن‌ها استخراج می‌شوند. اگر این‌ها را نهایتا کنار هم قرار دهیم، می‌توان به فرمت SRT متداول که اکثر پخش کننده‌های فایل‌های تصویری قادر به پردازش آن‌ها هستند، رسید.
فرمت SRT ساختار ساده‌ای دارد. هر گفتگوی آن حداقل از سه سطر تشکیل می‌شود. سطر اول یک شماره خود افزاینده است. سطر دوم زمان شروع و پایان گفتگو را مشخص می‌کند و سطر سوم بیانگر متن گفتگو است. برای مثال:
 1
00:00:01,636 --> 00:00:05,616
Hi, this is Scott Allen and this is the first module in the course design

دریافت پروژه کامل این مطلب
PluralsightTranscripts.zip
مطالب
روش استفاده‌ی صحیح از HttpClient در برنامه‌های دات نت
اگر در کدهای خود قطعه کد ذیل را دارید:
using(var client = new HttpClient())
{
   // do something with http client
}
استفاده‌ی از using در اینجا، نه‌تنها غیرضروری و اشتباه است، بلکه سبب از کار افتادن زود هنگام برنامه‌ی شما با صدور استثنای ذیل خواهد شد:
 Unable to connect to the remote server
System.Net.Sockets.SocketException: Only one usage of each socket address (protocol/network address/port) is normally permitted.


HttpClient خود را Dispose نکنید

کلاس HttpClient اینترفیس IDisposable را پیاده سازی می‌کند. بنابراین روش استفاده‌ی اصولی آن باید به صورت ذیل و با پیاده سازی خودکار رهاسازی منابع مرتبط با آن باشد:
using (var client = new HttpClient())
{
       var result = await client.GetAsync("http://example.com/");
}
اما در این حال فرض کنید به همین روش تعدادی درخواست را ارسال کرده‌اید:
for (int i = 0; i < 10; i++)
{
      using (var client = new HttpClient())
      {
            var result = await client.GetAsync("http://example.com/");
            Console.WriteLine(result.StatusCode);
      }
}
مشکل این روش، در ایجاد سوکت‌های متعددی است که حتی پس از بسته شدن برنامه نیز باز، باقی خواهند ماند:
  TCP    192.168.1.6:13996      93.184.216.34:http     TIME_WAIT
  TCP    192.168.1.6:13997      93.184.216.34:http     TIME_WAIT
  TCP    192.168.1.6:13998      93.184.216.34:http     TIME_WAIT
  TCP    192.168.1.6:13999      93.184.216.34:http     TIME_WAIT
  TCP    192.168.1.6:14000      93.184.216.34:http     TIME_WAIT
  TCP    192.168.1.6:14001      93.184.216.34:http     TIME_WAIT
  TCP    192.168.1.6:14002      93.184.216.34:http     TIME_WAIT
  TCP    192.168.1.6:14003      93.184.216.34:http     TIME_WAIT
  TCP    192.168.1.6:14004      93.184.216.34:http     TIME_WAIT
  TCP    192.168.1.6:14005      93.184.216.34:http     TIME_WAIT
این یک نمونه‌ی خروجی برنامه‌ی فوق، توسط دستور netstat «پس از بسته شدن کامل برنامه» است.

بنابراین اگر برنامه‌ی شما تعداد زیادی کاربر دارد و یا تعداد زیادی درخواست را به روش فوق ارسال می‌کند، سیستم عامل به حد اشباع ایجاد سوکت‌های جدید خواهد رسید.
این مشکل نیز ارتباطی به طراحی این کلاس و یا زبان #C و حتی استفاده‌ی از using نیز ندارد. این رفتار، رفتار معمول سیستم عامل، با سوکت‌های ایجاد شده‌است. TIME_WAIT ایی را که در اینجا ملاحظه می‌کنید، به معنای بسته شدن اتصال از طرف برنامه‌ی ما است؛ اما سیستم عامل هنوز منتظر نتیجه‌ی نهایی، از طرف دیگر اتصال است که آیا قرار است بسته‌ی TCP ایی را دریافت کند یا خیر و یا شاید در بین راه تاخیری وجود داشته‌است. برای نمونه ویندوز به مدت 240 ثانیه یک اتصال را در این حالت حفظ خواهد کرد، که مقدار آن نیز در اینجا تنظیم می‌شود:
 [HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\TcpTimedWaitDelay]

بنابراین روش توصیه شده‌ی کار با HttpClient، داشتن یک وهله‌ی سراسری از آن در برنامه و عدم Dispose آن است. HttpClient نیز thread-safe طراحی شده‌است و دسترسی به یک شیء سراسری آن در برنامه‌های چند ریسمانی مشکلی را ایجاد نمی‌کند. همچنین Dispose آن نیز غیرضروری است و پس از پایان برنامه به صورت خودکار توسط سیستم عامل انجام خواهد شد.


تمام اجزای HttpClient به صورت Thread-safe طراحی نشده‌اند

تا اینجا به این نتیجه رسیدیم که روش صحیح کار کردن با HttpClient، نیاز به داشتن یک وهله‌ی Singleton از آن‌را در سراسر برنامه دارد و Dispose صریح آن، بجز اشباع سوکت‌های سیستم عامل و ناپایدار کردن تمام برنامه‌هایی که از آن سرویس می‌گیرند، حاصلی را به همراه نخواهد داشت. در این بین مطابق مستندات HttpClient، استفاده‌ی از متدهای ذیل این کلاس thread-safe هستند:
CancelPendingRequests
DeleteAsync
GetAsync
GetByteArrayAsync
GetStreamAsync
GetStringAsync
PostAsync
PutAsync
SendAsync
اما تغییر این خواص در کلاس HttpClient به هیچ عنوان thread-safe نبوده و در برنامه‌های چند ریسمانی و چند کاربری، مشکل ساز می‌شوند:
BaseAddress
DefaultRequestHeaders
MaxResponseContentBufferSize
Timeout
بنابراین در طراحی کلاس مدیریت کننده‌ی HttpClient برنامه‌ی خود نیاز است به ازای هر BaseAddress‌، یک HttpClient خاص آن‌را ایجاد کرد و HttpClientهای سراسری نمی‌توانند BaseAddress‌های خود را نیز به اشتراک گذاشته و تغییری را در آن ایجاد کنند.


استفاده‌ی سراسری و مجدد از HttpClient، تغییرات DNS را متوجه نمی‌شود

با طراحی یک کلاس مدیریت کننده‌ی سراسری HttpClient با طول عمر Singelton، به یک مشکل دیگر نیز برخواهیم خورد: چون در اینجا از اتصالات، استفاده‌ی مجدد می‌شوند، دیگر تغییرات DNS را لحاظ نخواهند کرد.
برای حل این مشکل، در زمان ایجاد یک HttpClient سراسری، به ازای یک BaseAddress مشخص، باید از ServicePointManager کوئری گرفته و زمان اجاره‌ی اتصال آن‌را دقیقا مشخص کنیم:
var sp = ServicePointManager.FindServicePoint(new Uri("http://thisisasample.com"));
sp.ConnectionLeaseTimeout = 60*1000; //In milliseconds
با این‌کار هرچند هنوز هم از اتصالات استفاده‌ی مجدد می‌شود، اما این استفاده‌ی مجدد، نامحدود نبوده و مدت معینی را پیدا می‌کند.


طراحی یک کلاس، برای مدیریت سراسری وهله‌های HttpClient‌

تا اینجا به صورت خلاصه به نکات ذیل رسیدیم:
- HttpClient باید به صورت یک وهله‌ی سراسری Singleton مورد استفاده قرار گیرد. هر وهله سازی مجدد آن 35ms زمان می‌برد.
- Dispose یک HttpClient غیرضروری است.
- HttpClient تقریبا thread safe طراحی شده‌است؛ اما تعدادی از خواص آن مانند BaseAddress‌  اینگونه نیستند.
- برای رفع مشکل اتصالات چسبنده (اتصالاتی که هیچگاه پایان نمی‌یابند)، نیاز است timeout آن‌را تنظیم کرد.

بنابراین بهتر است این نکات را در یک کلاس به صورت ذیل کپسوله کنیم:
using System;
using System.Collections.Generic;
using System.Net.Http;

namespace HttpClientTips
{
    public interface IHttpClientFactory : IDisposable
    {
        HttpClient GetOrCreate(
            Uri baseAddress,
            IDictionary<string, string> defaultRequestHeaders = null,
            TimeSpan? timeout = null,
            long? maxResponseContentBufferSize = null,
            HttpMessageHandler handler = null);
    }
}

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Threading;

namespace HttpClientTips
{
    /// <summary>
    /// Lifetime of this class should be set to `Singleton`.
    /// </summary>
    public class HttpClientFactory : IHttpClientFactory
    {
        // 'GetOrAdd' call on the dictionary is not thread safe and we might end up creating the HttpClient more than
        // once. To prevent this Lazy<> is used. In the worst case multiple Lazy<> objects are created for multiple
        // threads but only one of the objects succeeds in creating the HttpClient.
        private readonly ConcurrentDictionary<Uri, Lazy<HttpClient>> _httpClients =
                         new ConcurrentDictionary<Uri, Lazy<HttpClient>>();
        private const int ConnectionLeaseTimeout = 60 * 1000; // 1 minute

        public HttpClientFactory()
        {
            // Default is 2 minutes: https://msdn.microsoft.com/en-us/library/system.net.servicepointmanager.dnsrefreshtimeout(v=vs.110).aspx
            ServicePointManager.DnsRefreshTimeout = (int)TimeSpan.FromMinutes(1).TotalMilliseconds;
            // Increases the concurrent outbound connections
            ServicePointManager.DefaultConnectionLimit = 1024;
        }

        public HttpClient GetOrCreate(
           Uri baseAddress,
           IDictionary<string, string> defaultRequestHeaders = null,
           TimeSpan? timeout = null,
           long? maxResponseContentBufferSize = null,
           HttpMessageHandler handler = null)
        {
            return _httpClients.GetOrAdd(baseAddress,
                             uri => new Lazy<HttpClient>(() =>
                             {
                                 // Reusing a single HttpClient instance across a multi-threaded application means
                                 // you can't change the values of the stateful properties (which are not thread safe),
                                 // like BaseAddress, DefaultRequestHeaders, MaxResponseContentBufferSize and Timeout.
                                 // So you can only use them if they are constant across your application and need their own instance if being varied.
                                 var client = handler == null ? new HttpClient { BaseAddress = baseAddress } :
                                               new HttpClient(handler, disposeHandler: false) { BaseAddress = baseAddress };
                                 setRequestTimeout(timeout, client);
                                 setMaxResponseBufferSize(maxResponseContentBufferSize, client);
                                 setDefaultHeaders(defaultRequestHeaders, client);
                                 setConnectionLeaseTimeout(baseAddress, client);
                                 return client;
                             },
                             LazyThreadSafetyMode.ExecutionAndPublication)).Value;
        }

        public void Dispose()
        {
            foreach (var httpClient in _httpClients.Values)
            {
                httpClient.Value.Dispose();
            }
        }

        private static void setConnectionLeaseTimeout(Uri baseAddress, HttpClient client)
        {
            // This ensures connections are used efficiently but not indefinitely.
            client.DefaultRequestHeaders.ConnectionClose = false; // keeps the connection open -> more efficient use of the client
            ServicePointManager.FindServicePoint(baseAddress).ConnectionLeaseTimeout = ConnectionLeaseTimeout; // ensures connections are not used indefinitely.
        }

        private static void setDefaultHeaders(IDictionary<string, string> defaultRequestHeaders, HttpClient client)
        {
            if (defaultRequestHeaders == null)
            {
                return;
            }
            foreach (var item in defaultRequestHeaders)
            {
                client.DefaultRequestHeaders.Add(item.Key, item.Value);
            }
        }

        private static void setMaxResponseBufferSize(long? maxResponseContentBufferSize, HttpClient client)
        {
            if (maxResponseContentBufferSize.HasValue)
            {
                client.MaxResponseContentBufferSize = maxResponseContentBufferSize.Value;
            }
        }

        private static void setRequestTimeout(TimeSpan? timeout, HttpClient client)
        {
            if (timeout.HasValue)
            {
                client.Timeout = timeout.Value;
            }
        }
    }
}
در اینجا به ازای هر baseAddress جدید، یک HttpClient خاص آن ایجاد می‌شود تا در کل برنامه مورد استفاده‌ی مجدد قرار گیرد. برای مدیریت thread-safe ایجاد HttpClientها نیز از نکته‌ی مطلب «الگویی برای مدیریت دسترسی همزمان به ConcurrentDictionary» استفاده شده‌است. همچنین نکات تنظیم ConnectionLeaseTimeout و سایر خواص غیر thread-safe کلاس HttpClient نیز در اینجا لحاظ شده‌اند.

پس از تدارک این کلاس، نحوه‌ی معرفی آن به سیستم باید به صورت Singleton باشد. برای مثال اگر از ASP.NET Core استفاده می‌کنید، آن‌را به صورت ذیل ثبت کنید:
namespace HttpClientTips.Web
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddSingleton<IHttpClientFactory, HttpClientFactory>();
            services.AddMvc();
        }

اکنون، یک نمونه، نحوه‌ی استفاده‌ی از اینترفیس IHttpClientFactory تزریقی به صورت ذیل می‌باشد:
namespace HttpClientTips.Web.Controllers
{
    public class HomeController : Controller
    {
        private readonly IHttpClientFactory _httpClientFactory;
        public HomeController(IHttpClientFactory httpClientFactory)
        {
            _httpClientFactory = httpClientFactory;
        }

        public async Task<IActionResult> Index()
        {
            var host = new Uri("http://localhost:5000");
            var httpClient = _httpClientFactory.GetOrCreate(host);
            var responseMessage = await httpClient.GetAsync("home/about").ConfigureAwait(false);
            var responseContent = await responseMessage.Content.ReadAsStringAsync().ConfigureAwait(false);
            return Content(responseContent);
        }
سرویس IHttpClientFactory یک HttpClient را به ازای host درخواستی ایجاد کرده و در طول عمر برنامه از آن استفاده‌ی مجدد می‌کند. به همین جهت دیگر مشکل اشباع سوکت‌ها در این سیستم رخ نخواهند داد.


برای مطالعه‌ی بیشتر

You're using HttpClient wrong and it is destabilizing your software
Disposable, Finalizers, and HttpClient
Using HttpClient as it was intended (because you’re not)
Singleton HttpClient? Beware of this serious behaviour and how to fix it
Beware of the .NET HttpClient
Effectively Using HttpClient
مطالب
Func یا Expression Func در EF
با بررسی کدهای مختلف Entity framework گاهی از اوقات در امضای توابع کمکی نوشته شده، <>Func مشاهده می‌شود و در بعضی از موارد <<>Expression<Func و ... به نظر استفاده کنندگان دقیقا نمی‌دانند که تفاوت این دو در چیست و کدامیک را باید/یا بهتر است بکار برد.

ابتدا مثال کامل ذیل را در نظر بگیرید:
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Data.Entity.Migrations;
using System.Linq;
using System.Linq.Expressions;

namespace Sample
{
    public abstract class BaseEntity
    {
        public int Id { set; get; }
    }

    public class Receipt : BaseEntity
    {
        public int TotalPrice { set; get; }
    }

    public class MyContext : DbContext
    {
        public DbSet<Receipt> Receipts { get; set; }
    }

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

        protected override void Seed(MyContext context)
        {
            if (!context.Receipts.Any())
            {
                for (int i = 0; i < 20; i++)
                {
                    context.Receipts.Add(new Receipt { TotalPrice = i });
                }
            }
            base.Seed(context);
        }
    }

    public static class EFUtils
    {
        public static IList<T> LoadEntities<T>(this DbContext ctx, Expression<Func<T, bool>> predicate) where T : class
        {
            return ctx.Set<T>().Where(predicate).ToList();
        }

        public static IList<T> LoadData<T>(this DbContext ctx, Func<T, bool> predicate) where T : class
        {
            return ctx.Set<T>().Where(predicate).ToList();
        }
    }

    public static class Test
    {
        public static void RunTests()
        {
            startDB();

            using (var context = new MyContext())
            {
                var list1 = context.LoadEntities<Receipt>(x => x.TotalPrice == 10);
                var list2 = context.LoadData<Receipt>(x => x.TotalPrice == 20);
            }
        }

        private static void startDB()
        {
            Database.SetInitializer(new MigrateDatabaseToLatestVersion<MyContext, Configuration>());
            // Forces initialization of database on model changes.
            using (var context = new MyContext())
            {
                context.Database.Initialize(force: true);
            }
        }
    }
}
در این مثال ابتدا کلاس Receipt تعریف شده و سپس توسط کلاس MyContext در معرض دید EF قرار گرفته است. در ادامه توسط کلاس Configuration نحوه آغاز بانک اطلاعاتی مشخص گردیده است؛ به همراه ثبت تعدادی رکورد نمونه.
نکته اصلی مورد بحث، کلاس کمکی EFUtils است که در آن دو متد الحاقی LoadEntities و LoadData تعریف شده‌اند. در متد LoadEntities، امضای متد شامل Expression Func است و در متد LoadData فقط Func ذکر شده است.
در ادامه اگر برنامه را توسط فراخوانی متد RunTests اجرا کنیم، به نظر شما خروجی SQL حاصل از list1 و list2 چیست؟
احتمالا شاید عنوان کنید که هر دو یک خروجی SQL دارند (با توجه به اینکه بدنه متدهای LoadEntities و LoadData دقیقا/یا به نظر یکی هستند) اما یکی از پارامتر 10 استفاده می‌کند و دیگری از پارامتر 20. تفاوت دیگری ندارند.
اما ... اینطور نیست!
خروجی SQL متد LoadEntities در متد RunTests به صورت زیر است:
 SELECT
[Extent1].[Id] AS [Id],
[Extent1].[TotalPrice] AS [TotalPrice]
FROM [dbo].[Receipts] AS [Extent1]
WHERE 10 = [Extent1].[TotalPrice]
و ... خروجی متد LoadData به نحو زیر:
 SELECT
[Extent1].[Id] AS [Id],
[Extent1].[TotalPrice] AS [TotalPrice]
FROM [dbo].[Receipts] AS [Extent1]
بله. در لیست دوم هیچ فیلتری انجام نشده (در حالت استفاده از Func خالی) و کل اطلاعات موجود در جدول Receipts، بازگشت داده شده است.
چرا؟
Func اشاره‌گری است به یک متد و Expression Func بیانگر ساختار درختی عبارت lambda نوشته شده است. این ساختار درختی صرفا بیان می‌کند که عبارت lambda منتسب، چه کاری را قرار است یا می‌تواند انجام دهد؛ بجای انجام واقعی آن.
 public static IQueryable<TSource> Where<TSource>(this IQueryable<TSource> source, Expression<Func<TSource, bool>> predicate);
public static IEnumerable<TSource> Where<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate);
اگر از Expression Func استفاده شود، از متد Where ایی استفاده خواهد شد که خروجی IQueryable دارد. اگر از Func استفاده شود، از overload دیگری که خروجی و ورودی  IEnumerable دارد به صورت خودکار استفاده می‌گردد.
بنابراین هرچند بدنه دو متد LoadEntities و LoadData به ظاهر یکی هستند، اما بر اساس نوع ورودی Where ایی که دریافت می‌کنند، اگر Expression Func باشد، EF فرصت آنالیز و ترجمه عبارت ورودی را خواهد یافت اما اگر Func باشد، ابتدا باید کل اطلاعات را به صورت یک لیست IEnumerable دریافت و سپس سمت کلاینت، خروجی نهایی را فیلتر کند.
اگر برنامه را اجرا کنید نهایتا هر دو لیست یک و دو، بر اساس شرط عنوان شده عمل خواهند کرد و فیلتر خواهند شد. اما در حالت اول این فیلتر شدن سمت بانک اطلاعاتی است و در حالت دوم کل اطلاعات بارگذاری شده و سپس سمت کاربر فیلتر می‌شود (که کارآیی پایینی دارد).


نتیجه گیری
به امضای متد Where ایی که در حال استفاده است دقت کنید. همینطور در مورد Sum ، Count و یا موارد مشابه دیگری که predicate قبول می‌کنند.
مطالب
مروری بر کاربردهای Action و Func - قسمت پنجم
یکی دیگر از کاربردهای Action و Func، امکان حذف و بازنویسی switch statements بسیار حجیم و طولانی به نحوی شکیل است؛ و در ادامه این نوع Refactoring را بررسی خواهیم کرد.
در ابتدا مثال زیر را که از یک سوئیچ، برای انتخاب نوع حرکت و اعمال آن استفاده می‌کند، در نظر بگیرید:
using System;

namespace ActionFuncSamples
{
    public enum Direction
    {
        Up,
        Down,
        Left,
        Right
    }

    public class Sample5
    {
        public void Move(Direction direction)
        {
            switch (direction)
            {
                case Direction.Up: 
                    MoveUp();
                    break;

                case Direction.Down: 
                    MoveDown();
                    break;

                case Direction.Left: 
                    MoveLeft();
                    break;

                case Direction.Right:
                    MoveRight();
                    break;
            }
        }

        private void MoveRight()
        {
            Console.WriteLine("MoveRight");
        }

        private void MoveLeft()
        {
            Console.WriteLine("MoveLeft");
        }

        private void MoveDown()
        {
            Console.WriteLine("MoveDown");
        }

        private void MoveUp()
        {
            Console.WriteLine("MoveUp");
        }
    }
}
یک کلاس ساده که بر اساس مقدار enum دریافتی، حرکت به چهار جهت را سبب خواهد شد. اکنون می‌خواهیم با استفاده از Actionها نحوه تعریف متدهای حرکت را به فراخوان واگذار (کلاسی بسته برای تغییر اما باز برای توسعه) و به علاوه switch را نیز کلا حذف کنیم:
    public interface IMove
    {
        Dictionary<Direction, Action> MoveMap { get; }
    }

    public class Move : IMove
    {
        public Dictionary<Direction, Action> MoveMap
        {
            get
            {
                return new Dictionary<Direction, Action>
               {
                    { Direction.Up, MoveUp},
                    { Direction.Down, MoveDown},
                    { Direction.Left, MoveLeft},
                    { Direction.Right, MoveRight}
               };
            }
        }

        private void MoveRight()
        {
            Console.WriteLine("MoveRight");
        }

        private void MoveLeft()
        {
            Console.WriteLine("MoveLeft");
        }

        private void MoveDown()
        {
            Console.WriteLine("MoveDown");
        }

        private void MoveUp()
        {
            Console.WriteLine("MoveUp");
        }
    }

    public class Sample5
    {
        public void NewMove(IMove move, Direction dirction)
        {
            foreach (var item in move.MoveMap)
            {
                if (item.Key == dirction)
                {
                    item.Value(); //run action
                    return;
                }
            }
        }

توضیحات:

استفاده از Dictionary برای حذف سوئیچ بسیار مرسوم است. خصوصا زمانیکه توسط switch صرفا قرار است یک مقدار ساده بازگشت داده شود. در این حالت می‌توان کل سوئیچ را حذف کرد. case‌های آن تبدیل به keyهای یک Dictionary و مقادیری که باید بازگشت دهد، تبدیل به valueهای متناظر خواهند شد.
اما در اینجا مساله اندکی متفاوت است و خروجی خاصی مد نظر نیست؛ بلکه در هر قسمت قرار است کار مفروضی انجام شود. بنابراین اینبار کلیدهای Dictionary بازهم بر اساس مقادیر caseهای تعریف شده (در اینجا enum ایی به نام Direction) و مقادیر آن نیز Action تعریف خواهند شد. همچنین برای اینکه بتوان امکان تعریف قالبی (کلاسی) را برای تعاریف متدهای متناظر با اعضای enum نیز میسر کرد، این Dictionary را در یک interface قرار داده‌ایم تا کلاس‌های پیاده سازی کننده آن، تعریف متدها را نیز دربر داشته باشند.
همانطور که ملاحظه می‌کنید، اینبار تعاریف متدهای Move از کلاس Sample5 خارج شده‌اند، به علاوه برای دسترسی به آن‌ها نیز switch ایی تعریف نشده و از Action استفاده شده است.
نهایتا اینبار متد جدید Move تعریف شده، با اینترفیس IMove کار می‌کند که یک Dictionary حاوی متدهای متناظر با اعضای enum جهت را ارائه می‌دهد.


یک نکته تکمیلی
:
متد NewMove را به شکل‌های زیر نیز می‌توان پیاده سازی کرد:
// or ...
move.MoveMap[dirction]();

// or ...
Action action;
if(move.MoveMap.TryGetValue(dirction, out action))
   action();


مطالب دوره‌ها
تولید پویای کد در زمان اجرا توسط Reflection.Emit
در ادامه قصد داریم توسط امکانات Reflection به همراه کدهای IL، اشیایی را در زمان اجرا ایجاد کنیم.


Reflection چیست؟

Reflection چیزهایی هستند که با نگاه در یک آینه قابل مشاهده‌اند. در این حالت شخص می‌تواند قسمت‌های مختلف ظاهر خود را برانداز کرده یا قسمتی را تغییر دهید. اما این مساله چه ربطی به دنیای دات نت دارد؟ در دات نت با استفاده از Reflection می‌توان به اطلاعات اشیاء یک برنامه‌ی در حال اجرا دسترسی یافت. برای مثال نام کلاس‌های مختلف آن چیست یا درون کلاسی خاص، چه متدهایی قرار دارند. همچنین با استفاده از Reflection می‌توان رفتارهای جدیدی را نیز به کلاس‌ها و اشیاء افزود یا آن‌ها را تغییر داد.
همواره عنوان می‌شود که از Reflection به دلیل سربار بالای آن پرهیز کنید و تنها از آن به عنوان آخرین راه حل موجود استفاده نمائید و این دقیقا موردی است که در مباحث جاری بیشتر از آن استفاده خواهد شد: ساخت اشیاء جدید در زمان اجرا به کمک کدهای IL و امکانات Reflection


نگاهی به امکانات متداول Reflection

در مثال بعد، نگاهی خواهیم داشت به امکانات متداول Reflection، مانند دسترسی به متدها و خواص یک کلاس و تعویض مقدار یا فراخوانی آن‌ها:
using System;

namespace FastReflectionTests
{
    class Person
    {
        public string Name { set; get; }

        public string Speak()
        {
            return string.Format("Hello, my name is {0}.", this.Name);
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            //روش متداول
            var vahid = new Person { Name = "Vahid" };
            Console.WriteLine(vahid.Speak());

            var type = vahid.GetType();

            //نمایش متدهای یک کلاس
            var methods = type.GetMethods();
            foreach (var method in methods)
            {
                Console.WriteLine(method.Name);
            }

            //تغییر مقدار یک خاصیت
            var setNameMethod = type.GetMethod("set_Name");
            setNameMethod.Invoke(obj: vahid, parameters: new[] { "Ali" });

            //فراخوانی یک متد
            var speakMethod = type.GetMethod("Speak");
            var result = speakMethod.Invoke(obj: vahid, parameters: null);
            Console.WriteLine(result);
        }
    }
}
با خروجی ذیل
 Hello, my name is Vahid.
set_Name
get_Name
Speak
ToString
Equals
GetHashCode
GetType
Hello, my name is Ali.
توضیحات:
در اینجا یک کلاس شخص با خاصیت نام او تعریف شده است؛ به همراه متدی که رشته‌ای را نمایش خواهد داد.
در متد Main برنامه، ابتدا یک وهله جدید از این شخص ایجاد شده و سپس به روش متداول، متد Speak آن فراخوانی گردیده است. در ادامه کار از امکانات Reflection برای انجام همین امور کمک گرفته شده است.
کار با دریافت نوع یک وهله شروع می‌شود. برای نمونه در اینجا توسط vahid.GetType به نوع وهله ساخته شده دسترسی یافته‌ایم. سپس با داشتن این type، می‌توان به کلیه امکانات Reflection دسترسی یافت. برای مثال توسط GetMethods، لیست کلیه متدهای موجود در کلاس شخص بازگشت داده می‌شود.
اگر به خروجی فوق دقت کنید، پس از سطر اول، 7 سطر بعدی نمایانگر متدهای موجود در کلاس شخص هستند. شاید عنوان کنید که این کلاس به نظر یک متد بیشتر ندارد. اما در دات نت اشیاء از شیء Object مشتق می‌شوند و چهار متد ToString، Equals، GetHashCode و GetType متعلق به آن هستند. همچنین خواص تعریف شده نیز در اصل به دو متد set و get به صورت خودکار در کدهای IL برنامه ترجمه خواهند شد. از همین متد set_Name در ادامه برای مقدار دهی خاصیت نام وهله ایجاد شده استفاده شده است.
همانطور که ملاحظه می‌کنید برای فراخوانی یک وهله از طریق Reflection، ابتدا توسط متد type.GetMethod می‌توان به آن دسترسی یافت و سپس با فراخوانی متد Invoke، می‌توان متد مدنظر را بر روی یک شیء مهیا با پارامترهایی که ذکر می‌کنیم، فراخوانی کرد. اگر این متد پارامتری ندارد، آن‌را نال قرار خواهیم داد.

تا اینجا مقدمه‌ای را ملاحظه نمودید که بیشتر جهت تکمیل بحث، حفظ روابط منطقی قسمت‌های مختلف آن و یادآوری مباحث مرتبط با Reflection ذکر شدند.


ایجاد اشیاء در زمان اجرای برنامه

یکی از کلاس‌های مهم Reflection که در منابع مختلف کمتر به آن پرداخته شده است، کلاس DynamicMethod آن است که از آن می‌توان برای ایجاد اشیاء و یا متدهایی پویا در زمان اجرا استفاده کرد. این کلاس قرار گرفته در فضای نام System.Reflection.Emit، دارای یک ILGenerator است که می‌توان به آن OpCodeهایی را اضافه کرد. زمانیکه کار ایجاد این متدپویا به پایان رسید، با استفاده از Delegates امکان دسترسی و اجرای این متد پویا وجود خواهد داشت.
یک مثال کامل را در این زمینه در ادامه ملاحظه می‌نمائید:
using System;
using System.Reflection.Emit;

namespace FastReflectionTests
{
    class Program
    {
        static double Divider(int a, int b)
        {
            return a / b;
        }

        delegate double DividerDelegate(int a, int b);
        static void Main(string[] args)
        {
            //روش متداول
            Console.WriteLine(Divider(10, 2));

            //تعریف امضای متد
            var myMethod = new DynamicMethod(
                                        name: "DividerMethod",
                                        returnType: typeof(double),
                                        parameterTypes: new[] { typeof(int), typeof(int) },
                                        m: typeof(Program).Module);
            //تعریف بدنه متد
            var il = myMethod.GetILGenerator();
            il.Emit(opcode: OpCodes.Ldarg_0); //بارگذاری پارامتر اول بر روی پشته ارزیابی
            il.Emit(opcode: OpCodes.Ldarg_1); //بارگذاری پارامتر دوم بر روی پشته ارزیابی
            il.Emit(opcode: OpCodes.Div); // دو پارامتر از پشته ارزیابی دریافت و تقسیم خواهند شد
            il.Emit(opcode: OpCodes.Ret); // دریافت نتیجه نهایی از پشته ارزیابی و بازگشت آن

            //فراخوانی متد پویا
            //روش اول
            var result = myMethod.Invoke(obj: null, parameters: new object[] { 10, 2 });
            Console.WriteLine(result);

            //روش دوم
            var method = (DividerDelegate)myMethod.CreateDelegate(delegateType: typeof(DividerDelegate));
            Console.WriteLine(method(10, 2));
        }
    }
}
توضیحات
در ابتدای این مثال جدید یک متد متداول تقسیم کننده دو عدد را ملاحظه می‌کنید. در ادامه قصد داریم overload دیگری از این متد را توسط کدهای MSIL در زمان اجرا ایجاد کنیم که دو پارامتر int را قبول می‌کند.
کار با وهله سازی کلاس DynamicMethod موجود در فضای نام System.Reflection.Emit شروع می‌شود. در اینجا کار تعریف امضای متد جدید باید صورت گیرد. برای مثال نام آن چیست، نوع خروجی آن کدام است. نوع پارامترهای آن چیست و نهایتا این متدی که قرار است به صورت پویا به برنامه اضافه شود، باید در کجا قرار گیرد. برای اینکار از Module خود کلاس Program برنامه استفاده شده است.
پس از تعریف امضای متد پویا، نوبت به تعریف بدنه‌ی آن می‌رسد. کار با دریافت یک ILGenerator که می‌توان در آن کدهای IL را وارد کرد شروع می‌شود. مابقی آن تعریف کدهای IL توسط متد Emit است و پیشتر با مقدمات اسمبلی دات نت در قسمت‌های قبلی مبحث جاری آشنا شده‌ایم. ابتدا دو Ldarg فراخوانی شده‌اند تا دو پارامتر ورودی متد را دریافت کنند. سپس Div بر روی آن‌ها صورت گرفته و نهایتا نتیجه بازگشت داده شده است.
خوب؛ تا اینجا موفق شدیم اولین متد پویای خود را ایجاد نمائیم. برای اجرا آن حداقل دو روش وجود دارد:
الف) فراخوانی متد Invoke بر روی آن. با توجه به اینکه قرار نیست این متد بر روی وهله‌ی خاصی اجرا شود، اولین پارامتر آن null وارد شده است و سپس پارامترهای این متد پویا توسط آرگومان دوم متد Invoke وارد شده‌اند.
ب) می‌توان این عملیات را اندکی شکیل‌تر کرد. برای اینکار پیش از متد Main برنامه یک delegate به نام DividerDelegate تعریف شده است. سپس با استفاده از متد CreateDelegate، خروجی این متد پویا را تبدیل به یک delegate کرده‌ایم. اینبار فراخوانی متد پویا بسیار شبیه به متدهای معمولی می‌شود.
مطالب
مخفی کردن کوئری استرینگ‌ها در ASP.NET MVC توسط امکانات Routing
ابتدا مدل و منبع داده نمونه زیر را در نظر بگیرید:
using System.Collections.Generic;

namespace TestRouting.Models
{
    public class Issue
    {
        public int IssueId { set; get; }
        public int ProjectId { set; get; }
        public string Title { set; get; }
        public string Body { set; get; }
    }

    public static class IssuesDataSource
    {
        public static IList<Issue> CreateDataSource()
        {
            var results = new List<Issue>();
            for (int i = 0; i < 100; i++)
            {
                results.Add(new Issue { IssueId = i, ProjectId = i, Body = "Test...", Title = "Title " + i });
            }
            return results;
        }
    }
}
به همراه کنترلر زیر برای نمایش لیست اطلاعات و همچنین نمایش جزئیات یک issue انتخابی:
using System.Linq;
using System.Web.Mvc;
using TestRouting.Models;

namespace TestRouting.Controllers
{
    public class HomeController : Controller
    {
        public ActionResult Index()
        {
            var issuesList = IssuesDataSource.CreateDataSource();
            return View(issuesList); //show the list
        }

        public ActionResult Details(int issueId, int projectId)
        {
            var issue = IssuesDataSource.CreateDataSource()
                                        .Where(x => x.IssueId == issueId && x.ProjectId == projectId)
                                        .FirstOrDefault();
            return View(issue);
        }
    }
}
و View زیر کار نمایش لیست بازخوردهای یک پروژه را انجام می‌دهد:
@model IEnumerable<TestRouting.Models.Issue>
@{
    ViewBag.Title = "Index";
}
<h2>
    Issues</h2>
<ul>
    @foreach (var item in Model)
    {
   
        <li>
            @Html.ActionLink(linkText: item.Title,
                     actionName: "Details",
                     controllerName: "Home",
                     routeValues: new { issueId = item.IssueId, projectId = item.ProjectId },
                     htmlAttributes: null)
        </li>
    }
</ul>
در این حالت اگر پروژه را اجرا کنیم، هر لینک نمایش داده شده، چنین فرمی را خواهد داشت:
 http://localhost:1036/Home/Details?issueId=0&projectId=0
سؤال: آیا می‌شود این لینک‌ها را کمی زیباتر و SEO Friendly‌تر کرد؟
برای مثال آن‌را به نحو زیر نمایش داد:
 http://localhost:1036/Home/Details/0/0
پاسخ: بلی. برای اینکار تنها کافی است از امکانات مسیریابی استفاده کنیم:
using System.Web.Mvc;
using System.Web.Routing;

namespace TestRouting
{
    public class RouteConfig
    {
        public static void RegisterRoutes(RouteCollection routes)
        {
            routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

            routes.MapRoute(
                name: "IssueDetails",
                url: "Details/{issueId}/{projectId}", //تطابق با یک چنین مسیرهایی
                defaults: new 
                          { 
                                controller = "Home", //کنترلری که این نوع مسیرها را پردازش خواهد کرد
                                action = "Details", // اکشن متدی که نهایتا پارامترها را دریافت می‌کند
                                issueId = UrlParameter.Optional, //این خواص نیاز است هم نام پارامترهای اکشن متد تعریف شوند
                                projectId = UrlParameter.Optional
                          }
            );

            routes.MapRoute(
                name: "Default",
                url: "{controller}/{action}/{id}",
                defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
            );
        }
    }
}
در اینجا یک route جدید به نام دلخواه IssueDetails پیش از route پیش فرض، تعریف شده است.
این route جدید با مسیرهایی مطابق پارامتر url آن تطابق خواهد یافت. پس از آن کوئری استرینگ متناظر با issueId را به پارامتر issueId اکشن متدی به نام Details و کنترلر Home ارسال خواهد کرد؛ به همین ترتیب در مورد projectId عمل خواهد شد.
ضمنا در url نهایی نمایش داده شده، دیگر اثری از کوئری استرینگ‌ها نبوده و برای نمونه در این حالت، اولین لینک نمایش داده شده شکل زیر را خواهد داشت:
 http://localhost:1036/Home/Details/0/0
 البته باید دقت داشت، یک چنین اصلاح خودکاری تنها در حالت استفاده از متد Html.ActionLink رخ می‌دهد و اگر Urlها را دستی ایجاد کنید، تغییری را مشاهده نخواهید کرد.