همه اون مواردی هم که اشاره فرمودید انجام دادم، ولی بازم خطا میده و اطلاعات رو داخل گریدها نمایش نمیده:
با سلام. امکان دارد در مورد نحوه استخراج اطلاعات از این سرویس و نمایش در سایت کمی راهنمایی کنید؟ باتشکر.
در مطلب «تولید پویای ستونها در PdfReport» عنوان شد که ذکر قسمت MainTableColumns و تمام تعاریف مرتبط با آن در PdfReports اختیاری است. همچنین به کمک متد MainTableAdHocColumnsConventions میتوان بر اساس نوعهای دادهای، بر روی نحوه نمایش ستونها تاثیر گذاشت. برای مثال هرجایی DateTime مشاهده شد، به صورت خودکار تبدیل به تاریخ شمسی شود.
روش دیگری که این روزها در اکثر فریمهای دات نتی مرسوم شده است، استفاده از Data Annotations جهت انتساب یک سری متادیتا به خاصیتهای تعریف شده کلاسها است. برای مثال ASP.NET MVC از این قابلیت زیاد استفاده میکند (در تولید پویای کد، یا اعتبار سنجیهای سمت سرور و کاربر).
به همین جهت برای سازگاری بیشتر PdfReport با مدلهای اینگونه فریم ورکها، اکثر ویژگیها و Data Annotations متداول را نیز میتوان در PdfReport بکار برد. همچنین تعدادی ویژگی سفارشی نیز تعریف شده است، که در ادامه به بررسی آنها خواهیم پرداخت.
آشنایی با مدلهای بکار رفته در مثال جاری:
در اینجا یک enum، جهت تعیین سمت شغلی تعریف شده است. برای اینکه بتوان خروجی مطلوبی را در گزارشات شاهد بود، میتوان از ویژگی Description، جهت تعیین مقدار نمایشی آنها نیز استفاده کرد و این تعاریف در PdfReport خوانده و اعمال میشوند.
مدل فوق جهت مقدار دهی اطلاعات یک شخص تعریف شده است.
- اگر قصد ندارید خاصیتی در این بین، در گزارشات ظاهر شود، از ویژگی IsVisible با مقدار false استفاده کنید.
- از ویژگی DisplayName جهت تعیین برچسبهای سرستونها استفاده خواهد شد.
- ذکر ویژگی ColumnItemsTemplate اختیاری است و اگر عنوان نشود به صورت خودکار از TextBlockField استفاده خواهد شد. اما اگر نیاز به استفاده از قالبهای ستونهای سفارشی و یا حتی قالبهای پیش فرض دیگری که متنی نیستند، وجود دارد، میتوانید از ویژگی ColumnItemsTemplate به همراه نوع کلاس مورد نظر استفاده نمائید. کلاسهای پیش فرض قالبهای ستونها در PdfReport در پوشه Lib\ColumnsItemsTemplates سورس آن قرار دارند.
- برای تعیین نحوه فرمت اطلاعات در اینجا میتوان از ویژگی DisplayFormat استفاده کرد. این ویژگی در اسمبلی System.ComponentModel.DataAnnotations.dll دات نت تعریف شده است؛ که در اینجا نمونهای از استفاده از آنرا برای تعیین نحوه نمایش تاریخ، ملاحظه میکنید. توسط این ویژگی حتی میتوان مشخص ساخت (توسط پارامتر NullDisplayText) که اگر اطلاعاتی null بود، بجای آن چه عبارتی نمایش داده شود.
- اگر علاقمند به اعمال تابعی تجمعی به ستونی خاص هستید، از ویژگی CustomAggregateFunction استفاده کنید. پارامتر آن نوع کلاس تابع مورد نظر است. یک سری تابع تجمعی پیش فرض در فضای نام PdfRpt.Aggregates.Numbers قرار دارند. البته امکان تهیه انواع سفارشی آنها نیز پیش بینی شده است که در قسمتهای بعد به آن خواهیم پرداخت.
- امکان تعریف خواص محاسباتی نیز پیش بینی شده است. برای این منظور دو کار را باید انجام داد:
الف) ویژگی IsCalculatedField را با مقدار true بر روی خاصیت مورد نظر اعمال کنید.
ب) هم نام خاصیت محاسباتی افزوده شده به کلاس جاری، ویژگی CalculatedFieldFormula را بر روی یک فیلد استاتیک عمومی در آن کلاس به نحوی که ملاحظه میکنید (مطابق امضای فیلد CalculatedFieldFormula فوق)، تعریف نمائید. (علت این است که نمیتوان توسط ویژگیها از delegates استفاده کرد و این محدودیت ذاتی وجود دارد)
در ادامه کدهای منبع داده فرضی مثال جاری ذکر شده است:
در پایان، نحوه استفاده از منبع داده فوق جهت تامین یک گزارش، به نحو زیر میباشد:
همانطور که مشخص است، از ذکر متد MainTableColumns به علت استفاده از DataAnnotations صرفنظر شده و PdfReport این تعاریف را بر اساس ویژگیهای خواص کلاس شخص دریافت میکند. تنها از متد MainTableAdHocColumnsConventions جهت مشخص سازی اینکه نیاز به نمایش ستون ردیف میباشد، استفاده کردهایم.
روش دیگری که این روزها در اکثر فریمهای دات نتی مرسوم شده است، استفاده از Data Annotations جهت انتساب یک سری متادیتا به خاصیتهای تعریف شده کلاسها است. برای مثال ASP.NET MVC از این قابلیت زیاد استفاده میکند (در تولید پویای کد، یا اعتبار سنجیهای سمت سرور و کاربر).
به همین جهت برای سازگاری بیشتر PdfReport با مدلهای اینگونه فریم ورکها، اکثر ویژگیها و Data Annotations متداول را نیز میتوان در PdfReport بکار برد. همچنین تعدادی ویژگی سفارشی نیز تعریف شده است، که در ادامه به بررسی آنها خواهیم پرداخت.
آشنایی با مدلهای بکار رفته در مثال جاری:
using System.ComponentModel; namespace PdfReportSamples.Models { public enum JobTitle { [Description("Grunt")] Grunt, [Description("Programmer")] Programmer, [Description("Analyst Programmer")] AnalystProgrammer, [Description("Project Manager")] ProjectManager, [Description("Chief Information Officer")] ChiefInformationOfficer, } }
using System; using System.Collections.Generic; using System.ComponentModel; using System.ComponentModel.DataAnnotations; using PdfReportSamples.Models; using PdfRpt.Aggregates.Numbers; using PdfRpt.ColumnsItemsTemplates; using PdfRpt.Core.Contracts; using PdfRpt.Core.Helper; using PdfRpt.DataAnnotations; namespace PdfReportSamples.DataAnnotations { public class Person { [IsVisible(false)] public int Id { get; set; } [DisplayName("User name")] //Note: If you don't specify the ColumnItemsTemplate, a new TextBlockField() will be used automatically. [ColumnItemsTemplate(typeof(TextBlockField))] public string Name { get; set; } [DisplayName("Job title")] public JobTitle JobTitle { set; get; } [DisplayName("Date of birth")] [DisplayFormat(DataFormatString = "{0:MM/dd/yyyy}")] public DateTime DateOfBirth { get; set; } [DisplayName("Date of death")] [DisplayFormat(NullDisplayText = "-", DataFormatString = "{0:MM/dd/yyyy}")] public DateTime? DateOfDeath { get; set; } [DisplayFormat(DataFormatString = "{0:n0}")] [CustomAggregateFunction(typeof(Sum))] public int Salary { get; set; } [IsCalculatedField(true)] [DisplayName("Calculated Field")] [DisplayFormat(DataFormatString = "{0:n0}")] [AggregateFunction(AggregateFunction.Sum)] public string CalculatedField { get; set; } [CalculatedFieldFormula("CalculatedField")] public static Func<IList<CellData>, object> CalculatedFieldFormula = list => { if (list == null) return string.Empty; var salary = (int)list.GetValueOf<Person>(x => x.Salary); return salary * 0.8; };//Note: It's a static field, not a property. } }
- اگر قصد ندارید خاصیتی در این بین، در گزارشات ظاهر شود، از ویژگی IsVisible با مقدار false استفاده کنید.
- از ویژگی DisplayName جهت تعیین برچسبهای سرستونها استفاده خواهد شد.
- ذکر ویژگی ColumnItemsTemplate اختیاری است و اگر عنوان نشود به صورت خودکار از TextBlockField استفاده خواهد شد. اما اگر نیاز به استفاده از قالبهای ستونهای سفارشی و یا حتی قالبهای پیش فرض دیگری که متنی نیستند، وجود دارد، میتوانید از ویژگی ColumnItemsTemplate به همراه نوع کلاس مورد نظر استفاده نمائید. کلاسهای پیش فرض قالبهای ستونها در PdfReport در پوشه Lib\ColumnsItemsTemplates سورس آن قرار دارند.
- برای تعیین نحوه فرمت اطلاعات در اینجا میتوان از ویژگی DisplayFormat استفاده کرد. این ویژگی در اسمبلی System.ComponentModel.DataAnnotations.dll دات نت تعریف شده است؛ که در اینجا نمونهای از استفاده از آنرا برای تعیین نحوه نمایش تاریخ، ملاحظه میکنید. توسط این ویژگی حتی میتوان مشخص ساخت (توسط پارامتر NullDisplayText) که اگر اطلاعاتی null بود، بجای آن چه عبارتی نمایش داده شود.
- اگر علاقمند به اعمال تابعی تجمعی به ستونی خاص هستید، از ویژگی CustomAggregateFunction استفاده کنید. پارامتر آن نوع کلاس تابع مورد نظر است. یک سری تابع تجمعی پیش فرض در فضای نام PdfRpt.Aggregates.Numbers قرار دارند. البته امکان تهیه انواع سفارشی آنها نیز پیش بینی شده است که در قسمتهای بعد به آن خواهیم پرداخت.
- امکان تعریف خواص محاسباتی نیز پیش بینی شده است. برای این منظور دو کار را باید انجام داد:
الف) ویژگی IsCalculatedField را با مقدار true بر روی خاصیت مورد نظر اعمال کنید.
ب) هم نام خاصیت محاسباتی افزوده شده به کلاس جاری، ویژگی CalculatedFieldFormula را بر روی یک فیلد استاتیک عمومی در آن کلاس به نحوی که ملاحظه میکنید (مطابق امضای فیلد CalculatedFieldFormula فوق)، تعریف نمائید. (علت این است که نمیتوان توسط ویژگیها از delegates استفاده کرد و این محدودیت ذاتی وجود دارد)
در ادامه کدهای منبع داده فرضی مثال جاری ذکر شده است:
using System; using System.Collections.Generic; using PdfReportSamples.Models; namespace PdfReportSamples.DataAnnotations { public static class PersonnelDataSource { public static IList<Person> CreatePersonnelList() { return new List<Person> { new Person { Id = 1, Name = "Edward", DateOfBirth = new DateTime(1900, 1, 1), DateOfDeath = new DateTime(1990, 10, 15), JobTitle = JobTitle.ChiefInformationOfficer, Salary = 5000 }, new Person { Id = 2, Name = "Margaret", DateOfBirth = new DateTime(1950, 2, 9), DateOfDeath = null, JobTitle = JobTitle.AnalystProgrammer, Salary = 4000 }, new Person { Id = 3, Name = "Grant", DateOfBirth = new DateTime(1975, 6, 13), DateOfDeath = null, JobTitle = JobTitle.Programmer, Salary = 3500 } }; } } }
using System; using PdfRpt.Core.Contracts; using PdfRpt.FluentInterface; namespace PdfReportSamples.DataAnnotations { public class DataAnnotationsPdfReport { 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.DefaultFooter(printDate: DateTime.Now.ToString("MM/dd/yyyy")); }) .PagesHeader(header => { header.DefaultHeader(defaultHeader => { defaultHeader.ImagePath(AppPath.ApplicationPath + "\\Images\\01.png"); defaultHeader.Message("new rpt."); defaultHeader.RunDirection(PdfRunDirection.LeftToRight); }); }) .MainTableTemplate(template => { template.BasicTemplate(BasicTemplate.ClassicTemplate); }) .MainTablePreferences(table => { table.ColumnsWidthsType(TableColumnWidthType.FitToContent); }) .MainTableDataSource(dataSource => { dataSource.StronglyTypedList(PersonnelDataSource.CreatePersonnelList()); }) .MainTableEvents(events => { events.DataSourceIsEmpty(message: "There is no data available to display."); }) .MainTableSummarySettings(summary => { summary.OverallSummarySettings("Total"); summary.PageSummarySettings("Page Summary"); summary.PreviousPageSummarySettings("Pervious Page Summary"); }) .MainTableAdHocColumnsConventions(adHocColumns => { adHocColumns.ShowRowNumberColumn(true); adHocColumns.RowNumberColumnCaption("#"); }) .Export(export => { export.ToExcel(); export.ToXml(); }) .Generate(data => data.AsPdfFile(AppPath.ApplicationPath + "\\Pdf\\DataAnnotationsSampleRpt.pdf")); } } }
در قسمت اول این سری، با مدل برنامه نویسی Event based asynchronous pattern ارائه شده از دات نت 2 و همچنین APM یا Asynchronous programming model موجود از نگارش یک دات نت، آشنا شدیم (به آن الگوی IAsyncResult هم گفته میشود). نکتهی مهم این الگوها، استفادهی گسترده از آنها در کدهای کلاسهای مختلف دات نت فریم ورک است و برای بسیاری از آنها هنوز async API سازگار با نگارش مبتنی بر Taskهای سیشارپ 5 ارائه نشدهاست. هرچند دات نت 4.5 سعی کردهاست این خلاء را پوشش دهد، برای مثال متد الحاقی DownloadStringTaskAsync را به کلاس WebClient اضافه کردهاست و امثال آن، اما هنوز بسیاری از کلاسهای دیگر دات نتی هستند که معادل Task based API ایی برای آنها طراحی نشدهاست. در ادامه قصد داریم بررسی کنیم چگونه میتوان این الگوهای مختلف قدیمی برنامه نویسی غیرهمزمان را با استفاده از روشهای جدیدتر ارائه شده بکار برد.
نگاشت APM به یک Task
در قسمت اول، نمونه مثالی را از APM، که در آن کار با BeginGetResponse آغاز شده و سپس در callback نهایی توسط EndGetResponse، نتیجهی عملیات به دست میآید، مشاهده کردید. در ادامه میخواهیم یک محصور کنندهی جدید را برای این نوع API قدیمی تهیه کنیم، تا آنرا به صورت یک Task ارائه دهد.
همانطور که در این مثال مشاهده میکنید، یک چنین سناریوهایی در TPL یا کتابخانهی Task parallel library پیش بینی شدهاند. در اینجا یک محصور کننده برای متدهای BeginRead و EndRead کلاس Stream دات نت ارائه شدهاست. به عمد نیز به صورت یک متد الحاقی تهیه شدهاست تا در حین استفاده از آن اینطور به نظر برسد که واقعا کلاس Stream دارای یک چنین متد Async ایی است. مابقی کار توسط متد Task.Factory.FromAsync انجام میشود. متد FromAsync دارای امضاهای متعددی است تا اکثر حالات APM را پوشش دهد.
در مثال فوق BeginRead و EndRead استفاده شده از نوع delegate هستند. چون خروجی EndRead از نوع int است، خروجی متد نیز از نوع Task of int تعیین شدهاست. همچنین سه پارامتر ابتدایی BeginRead ، دقیقا data، offset و count هستند. دو پارامتر آخر آن callback و state نام دارند. پارامتر callback توسط متد FromAsync فراهم میشود و state نیز در اینجا null درنظر گرفته شدهاست.
یک مثال استفاده از آنرا در ادامه مشاهده میکنید:
File.OpenRead، خروجی از نوع استریم دارد. سپس متد الحاقی ReadAsync بر روی آن فراخوانی شدهاست و نهایتا تعداد بایت خوانده شده نمایش داده میشود.
البته همانطور که پیشتر نیز عنوان شد، استفاده از خاصیت Result، اجرای کد را بجای غیرهمزمان بودن، به حالت همزمان تبدیل میکند.
در اینجا چون خروجی متد ReadAsync یک Task است، میتوان از متد ContinueWith نیز بر روی آن جهت دریافت نتیجه استفاده کرد:
یک نکته
پروژهی سورس بازی به نام Async Generator در GitHub، سعی کردهاست برای ساده سازی نوشتن محصور کنندههای مبتنی بر Task روش APM، یک Code generator تولید کند. فایلهای آنرا از آدرس ذیل میتوانید دریافت کنید:
نگاشت EAP به یک Task
نمونهای از Event based asynchronous pattern یا EAP را در قسمت اول، زمانیکه روال رخدادگردان webClient.DownloadStringCompleted را بررسی کردیم، مشاهده نمودید. کار کردن با آن نسبت به APM بسیار سادهتر است و نتیجهی نهایی عملیات غیرهمزمان را در یک روال رخدادگران، در اختیار استفاده کننده قرار میدهد. همچنین در روش EAP، اطلاعات در همان Synchronization Context ایی که عملیات شروع شدهاست، بازگشت داده میشود. به این ترتیب اگر آغاز کار در ترد UI باشد، نتیجه نیز در همان ترد دریافت خواهد شد. به این ترتیب دیگر نگران دسترسی به مقدار آن در کارهای UI نخواهیم بود؛ اما در APM چنین ضمانتی وجود ندارد.
متاسفانه TPL همانند روش FromAsync معرفی شده در ابتدای بحث، راه حل توکاری را برای محصور سازی متدهای روش EAP ارائه ندادهاست. اما با استفاده از امکانات TaskCompletionSource آن میتوان چنین کاری را انجام داد. در ادامه سعی خواهیم کرد همان متد الحاقی توکار DownloadStringTaskAsync ارائه شده در دات نت 4.5 را از صفر بازنویسی کنیم.
روش انجام کار را در اینجا ملاحظه میکنید. ابتدا باید تعاریف delaget مرتبط با رخدادگردان Completed اضافه شوند. یکبار += را ملاحظه میکنید و بار دوم -= را. مورد دوم جهت آزاد سازی منابع و جلوگیری از نشتی حافظهی روال رخدادگردان هنوز متصل، ضروری است.
سپس از TaskCompletionSource برای تبدیل این عملیات به یک Task کمک میگیریم. اگر args.Cancelled مساوی true باشد، یعنی عملیات دریافت فایل لغو شدهاست. بنابراین متد SetCanceled منبع Task ایجاد شده را فراخوانی خواهیم کرد. این مورد استثنایی را در کدهای فراخوان سبب میشود. به همین دلیل بررسی خطا با یک if else پس از آن انجام شدهاست. برای بازگشت خطای دریافت شده از متد SetException و برای بازگشت نتیجهی واقعی دریافتی، از متد SetResult میتوان استفاده کرد.
به این ترتیب متد الحاقی غیرهمزمان جدیدی را به نام DownloadTextTaskAsync برای محصور سازی متد EAP ایی به نام DownloadStringAsync و همچنین رخدادگران آن تهیه کردیم.
نگاشت APM به یک Task
در قسمت اول، نمونه مثالی را از APM، که در آن کار با BeginGetResponse آغاز شده و سپس در callback نهایی توسط EndGetResponse، نتیجهی عملیات به دست میآید، مشاهده کردید. در ادامه میخواهیم یک محصور کنندهی جدید را برای این نوع API قدیمی تهیه کنیم، تا آنرا به صورت یک Task ارائه دهد.
public static class ApmWrapper { public static Task<int> ReadAsync(this Stream stream, byte[] data, int offset, int count) { return Task<int>.Factory.FromAsync(stream.BeginRead, stream.EndRead, data, offset, count, null); } }
در مثال فوق BeginRead و EndRead استفاده شده از نوع delegate هستند. چون خروجی EndRead از نوع int است، خروجی متد نیز از نوع Task of int تعیین شدهاست. همچنین سه پارامتر ابتدایی BeginRead ، دقیقا data، offset و count هستند. دو پارامتر آخر آن callback و state نام دارند. پارامتر callback توسط متد FromAsync فراهم میشود و state نیز در اینجا null درنظر گرفته شدهاست.
یک مثال استفاده از آنرا در ادامه مشاهده میکنید:
using System; using System.IO; using System.Threading.Tasks; namespace Async06 { public static class ApmWrapper { public static Task<int> ReadAsync(this Stream stream, byte[] data, int offset, int count) { return Task<int>.Factory.FromAsync(stream.BeginRead, stream.EndRead, data, offset, count, null); } } class Program { static void Main(string[] args) { using (var stream = File.OpenRead(@"..\..\program.cs")) { var data = new byte[10000]; var task = stream.ReadAsync(data, 0, data.Length); Console.WriteLine("Read bytes: {0}", task.Result); } } } }
البته همانطور که پیشتر نیز عنوان شد، استفاده از خاصیت Result، اجرای کد را بجای غیرهمزمان بودن، به حالت همزمان تبدیل میکند.
در اینجا چون خروجی متد ReadAsync یک Task است، میتوان از متد ContinueWith نیز بر روی آن جهت دریافت نتیجه استفاده کرد:
using (var stream = File.OpenRead(@"..\..\program.cs")) { var data = new byte[10000]; var task = stream.ReadAsync(data, 0, data.Length); task.ContinueWith(t => Console.WriteLine("Read bytes: {0}", t.Result)).Wait(); }
یک نکته
پروژهی سورس بازی به نام Async Generator در GitHub، سعی کردهاست برای ساده سازی نوشتن محصور کنندههای مبتنی بر Task روش APM، یک Code generator تولید کند. فایلهای آنرا از آدرس ذیل میتوانید دریافت کنید:
نگاشت EAP به یک Task
نمونهای از Event based asynchronous pattern یا EAP را در قسمت اول، زمانیکه روال رخدادگردان webClient.DownloadStringCompleted را بررسی کردیم، مشاهده نمودید. کار کردن با آن نسبت به APM بسیار سادهتر است و نتیجهی نهایی عملیات غیرهمزمان را در یک روال رخدادگران، در اختیار استفاده کننده قرار میدهد. همچنین در روش EAP، اطلاعات در همان Synchronization Context ایی که عملیات شروع شدهاست، بازگشت داده میشود. به این ترتیب اگر آغاز کار در ترد UI باشد، نتیجه نیز در همان ترد دریافت خواهد شد. به این ترتیب دیگر نگران دسترسی به مقدار آن در کارهای UI نخواهیم بود؛ اما در APM چنین ضمانتی وجود ندارد.
متاسفانه TPL همانند روش FromAsync معرفی شده در ابتدای بحث، راه حل توکاری را برای محصور سازی متدهای روش EAP ارائه ندادهاست. اما با استفاده از امکانات TaskCompletionSource آن میتوان چنین کاری را انجام داد. در ادامه سعی خواهیم کرد همان متد الحاقی توکار DownloadStringTaskAsync ارائه شده در دات نت 4.5 را از صفر بازنویسی کنیم.
public static class WebClientExtensions { public static Task<string> DownloadTextTaskAsync(this WebClient web, string url) { var tcs = new TaskCompletionSource<string>(); DownloadStringCompletedEventHandler handler = null; handler = (sender, args) => { web.DownloadStringCompleted -= handler; if (args.Cancelled) { tcs.SetCanceled(); } else if(args.Error!=null) { tcs.SetException(args.Error); } else { tcs.SetResult(args.Result); } }; web.DownloadStringCompleted += handler; web.DownloadStringAsync(new Uri(url)); return tcs.Task; } }
سپس از TaskCompletionSource برای تبدیل این عملیات به یک Task کمک میگیریم. اگر args.Cancelled مساوی true باشد، یعنی عملیات دریافت فایل لغو شدهاست. بنابراین متد SetCanceled منبع Task ایجاد شده را فراخوانی خواهیم کرد. این مورد استثنایی را در کدهای فراخوان سبب میشود. به همین دلیل بررسی خطا با یک if else پس از آن انجام شدهاست. برای بازگشت خطای دریافت شده از متد SetException و برای بازگشت نتیجهی واقعی دریافتی، از متد SetResult میتوان استفاده کرد.
به این ترتیب متد الحاقی غیرهمزمان جدیدی را به نام DownloadTextTaskAsync برای محصور سازی متد EAP ایی به نام DownloadStringAsync و همچنین رخدادگران آن تهیه کردیم.
ایجاد Strong Name به اسمبلی برای داشتن یک هویت منحصر به فرد برای آن اسمبلی کمک میکند و یکی از پارامترهای آن داشتن Public Key Token برای اسمبلی است (بیشتر ). در این پست قصد دارم یه کمک ابزارهای جانبی Visual Studio 2012 که البته در 2010 نیز امکان پذیر است روشی برای تهیه آسانتر این Key ارائه کنم .
برای آغاز نرم افزار VS2012 را باز میکنیم و به منوی Tools رفته و گزینه External Tools را انتخاب میکنیم :
در پنجرهی پیش رو روی دکمه Add کیلک کنید و نامی برای Tools انتخاب کنید :
سپس مسیر فایل sn.exe را کپی کرده و در فیلد Command قرار دهید .
برای پارامتر از عبارت زیراستفاده کرده تا Public Key اسمبلی جاری را به شما بدهد . برای اطلاعات بیشتر در مورد آرگومانها به اینجا مراجعه کنید
-Tp $(TargetPath)
همچنین گزینههای Prompt for Arguments را برای دریافت آرگومان دلخواه شما (مثلا مواردی که مایلید برای یک اسمبلی دیگر key استخراج کنید) و Use Output window برای نمایش خروجی را علامت بزنید
روی OK کلیک کنید و به منوی Tools بارگردید :
حال روی نام پروژه خود در Solution Explorer کلیک کنید و روی Tools ساخته شده کلیک کنید :
و خروجی:
یکی دیگر از ویجتهای Kendo UI، ویجت نمایش ساختارهای درختی است به نام TreeView. در ادامه قصد داریم با نحوهی نمایش آن، به کمک اطلاعات JSON دریافتی از سرور آشنا شویم.
ساختار مورد نیاز یک Kendo UI Tree View
فرض کنید قصد دارید نظرات تو در توی مطلبی را توسط Kendo UI Tree View نمایش دهید. مدل خود ارجاع دهندهی آن میتواند چنین شکلی را داشته باشد:
سه خاصیت اول این کلاس همواره در تمام کلاسهای خود ارجاع دهنده حضور دارند؛ شماره ردیف، متن و شماره Id والد احتمالی.
چند خاصیت بعدی مانند HasChildren و imageUrl مخصوص Kendo UI هستند. از imageUrl اختیاری میتوان جهت نمایش آیکنی در کنار یک آیتم استفاده کرد و HasChildren به این معنا است که آیا گره جاری دارای عناصر فرزندی میباشد یا خیر.
تهیه یک منبع داده نمونه
شکل ابتدای مطلب، از طریق منبع داده ذیل تهیه شدهاست:
در اینجا نحوهی مقدار دهی ParentId و HasChildren را جهت تو در تو سازی اطلاعات، مشاهده میکنید.
در این لیست دو رکورد، دارای ParentId مساوی null هستند. از این null بودنها جهت کوئری گرفتن و نمایش ریشههای TreeView در ادامه استفاده خواهیم کرد.
بازگشت نظرات با فرمت JSON به سمت کلاینت
در ادامه یک کنترلر ASP.NET MVC را مشاهده میکنید که توسط اکشن متد GetBlogComments، رکوردهای مورد نظر را با فرمت JSON به سمت کلاینت ارسال میکند:
اگر از سمت Kendo UI، مقدار id تنظیم نشود، به معنای درخواست نمایش ریشهها است. در این حالت رکوردها را بر اساس مواردی که دارای ParentId مساوی null هستند، فیلتر خواهیم کرد.
اگر مقدار id به سمت سرور ارسال شود، یعنی کاربر گره و نودی را گشودهاست. بر این اساس، تمامی فرزندان این گره را یافته و بازگشت میدهیم.
کدهای سمت کاربر نمایش Kendo UI Tree View
برای کار با Kendo UI TreeView نیاز است از منبع داده خاصی به نام HierarchicalDataSource به نحو ذیل استفاده کنیم. در قسمت transport آن مشخص میکنیم که اطلاعات باید از چه آدرسی خوانده شوند که در اینجا به آدرس اکشن متد GetBlogComments اشاره میکند.
همچنین نیاز است مشخص کنیم کدامیک از خواص مدل بازگردانده شده، همان hasChildren است که در مثال فوق دقیقا به همین نام نیز تنظیم شدهاست.
پس از تنظیم remote data source، اکنون نوبت به تعریف و تنظیم kendoTreeView است.
- در ابتدا به ازای هر ردیف این TreeView، از یک قالب استفاده شدهاست. تعریف این مورد اختیاری است. اگر نیاز به سفارشی سازی نحوهی نمایش هر آیتم را داشتید، میتوان از قالبها استفاده کرد.
- قسمت checkboxes مشخص میکند که آیا نیاز است در کنار هر آیتم یک checkbox نیز نمایش داده شود یا خیر.
- dataSource را به HierarchicalDataSource تنظیم کردهایم.
- dataTextField مشخص میکند که کدام فیلد دربرگیرندهی متن هر آیتم TreeView است.
- تعدادی رخداد منتسب به TreeView نیز تنظیم شدهاند که خروجی آنها را در console تصویر ابتدای بحث مشاهده میکنید.
کدهای کامل این مثال را از اینجا میتوانید دریافت کنید.
ساختار مورد نیاز یک Kendo UI Tree View
فرض کنید قصد دارید نظرات تو در توی مطلبی را توسط Kendo UI Tree View نمایش دهید. مدل خود ارجاع دهندهی آن میتواند چنین شکلی را داشته باشد:
namespace KendoUI11.Models { public class BlogComment { public int Id { set; get; } public string Body { set; get; } public int? ParentId { get; set; } // مخصوص کندو یو آی هستند public bool HasChildren { get; set; } public string imageUrl { get; set; } } }
چند خاصیت بعدی مانند HasChildren و imageUrl مخصوص Kendo UI هستند. از imageUrl اختیاری میتوان جهت نمایش آیکنی در کنار یک آیتم استفاده کرد و HasChildren به این معنا است که آیا گره جاری دارای عناصر فرزندی میباشد یا خیر.
تهیه یک منبع داده نمونه
شکل ابتدای مطلب، از طریق منبع داده ذیل تهیه شدهاست:
using System.Collections.Generic; namespace KendoUI11.Models { /// <summary> /// منبع داده فرضی جهت سهولت دموی برنامه /// </summary> public static class BlogCommentsDataSource { private static readonly IList<BlogComment> _cachedItems; static BlogCommentsDataSource() { _cachedItems = createBlogCommentsDataSource(); } public static IList<BlogComment> LatestComments { get { return _cachedItems; } } /// <summary> /// هدف صرفا تهیه یک منبع داده آزمایشی ساده تشکیل شده در حافظه است /// </summary> private static IList<BlogComment> createBlogCommentsDataSource() { var list = new List<BlogComment>(); var comment1 = new BlogComment { Id = 1, Body = "نظر من این است که", HasChildren = true, ParentId = null }; list.Add(comment1); var comment12 = new BlogComment { Id = 2, Body = "پاسخی به نظر اول", HasChildren = true, ParentId = 1 }; list.Add(comment12); var comment12A = new BlogComment { Id = 3, Body = "پاسخی دیگری به نظر اول", HasChildren = false, ParentId = 1 }; list.Add(comment12A); var comment121 = new BlogComment { Id = 4, Body = "پاسخی به پاسخ به نظر اول", HasChildren = false, ParentId = 2 }; list.Add(comment121); var comment2 = new BlogComment { Id = 5, Body = "نظر 2", HasChildren = true, ParentId = null, imageUrl= "images/search.png" }; list.Add(comment2); var comment21 = new BlogComment { Id = 6, Body = "پاسخ به نظر 2", HasChildren = false, ParentId = 5 }; list.Add(comment21); return list; } } }
در این لیست دو رکورد، دارای ParentId مساوی null هستند. از این null بودنها جهت کوئری گرفتن و نمایش ریشههای TreeView در ادامه استفاده خواهیم کرد.
بازگشت نظرات با فرمت JSON به سمت کلاینت
در ادامه یک کنترلر ASP.NET MVC را مشاهده میکنید که توسط اکشن متد GetBlogComments، رکوردهای مورد نظر را با فرمت JSON به سمت کلاینت ارسال میکند:
using System.Linq; using System.Web.Mvc; using KendoUI11.Models; namespace KendoUI11.Controllers { public class HomeController : Controller { public ActionResult Index() { return View(); // shows the page. } [HttpGet] public ActionResult GetBlogComments(int? id) { if (id == null) { //دریافت ریشهها return Json( BlogCommentsDataSource.LatestComments .Where(x => x.ParentId == null) // ریشهها .ToList(), JsonRequestBehavior.AllowGet); } else { //دریافت فرزندهای یک ریشه return Json( BlogCommentsDataSource.LatestComments .Where(x => x.ParentId == id) .ToList(), JsonRequestBehavior.AllowGet); } } } }
اگر مقدار id به سمت سرور ارسال شود، یعنی کاربر گره و نودی را گشودهاست. بر این اساس، تمامی فرزندان این گره را یافته و بازگشت میدهیم.
کدهای سمت کاربر نمایش Kendo UI Tree View
برای کار با Kendo UI TreeView نیاز است از منبع داده خاصی به نام HierarchicalDataSource به نحو ذیل استفاده کنیم. در قسمت transport آن مشخص میکنیم که اطلاعات باید از چه آدرسی خوانده شوند که در اینجا به آدرس اکشن متد GetBlogComments اشاره میکند.
همچنین نیاز است مشخص کنیم کدامیک از خواص مدل بازگردانده شده، همان hasChildren است که در مثال فوق دقیقا به همین نام نیز تنظیم شدهاست.
<!--نحوهی راست به چپ سازی --> <div class="k-rtl k-header demo-section"> <div id="my-treeview"></div> </div> @section JavaScript { <script type="text/javascript"> $(function () { var dataSource = new kendo.data.HierarchicalDataSource({ transport: { read: { url: "@Url.Action("GetBlogComments", "Home")", dataType: "json", contentType: 'application/json; charset=utf-8', type: 'GET' } }, schema: { model: { id: "Id", hasChildren: "HasChildren" } } }); $("#my-treeview").kendoTreeView({ //استفاده از قالب در صورت نیاز template: kendo.template($("#treeview-template").html()), checkboxes: { checkChildren: false }, dataSource: dataSource, dataTextField: "Body", //رخدادها select: function (e) { console.log("Selecting: " + this.text(e.node)); }, check: function (e) { console.log("Checkbox changed :: " + this.text(e.node)); }, change: function (e) { console.log("Selection changed"); }, collapse: function (e) { console.log("Collapsing " + this.text(e.node)); }, expand: function (e) { console.log("Expanding " + this.text(e.node)); } }); }); </script> <script id="treeview-template" type="text/kendo-ui-template"> <strong> #: item.Body # </strong> </script> <style scoped> .demo-section { width: 100%; height: 300px; } </style> }
- در ابتدا به ازای هر ردیف این TreeView، از یک قالب استفاده شدهاست. تعریف این مورد اختیاری است. اگر نیاز به سفارشی سازی نحوهی نمایش هر آیتم را داشتید، میتوان از قالبها استفاده کرد.
- قسمت checkboxes مشخص میکند که آیا نیاز است در کنار هر آیتم یک checkbox نیز نمایش داده شود یا خیر.
- dataSource را به HierarchicalDataSource تنظیم کردهایم.
- dataTextField مشخص میکند که کدام فیلد دربرگیرندهی متن هر آیتم TreeView است.
- تعدادی رخداد منتسب به TreeView نیز تنظیم شدهاند که خروجی آنها را در console تصویر ابتدای بحث مشاهده میکنید.
کدهای کامل این مثال را از اینجا میتوانید دریافت کنید.
در ASP.NET MVC به کمک یک سری فیلتر میتوان مشخص کرد که یک اکشن متد تنها به درخواستهایی از نوع Get پاسخ دهد، دیگری به درخواستهایی از نوع Post و الی آخر. عادت متداول من هم برای نمایش معمولی صفحات، استفاده از حالت HttpGet است که در شبکههای داخلی بدون مشکل کار میکند چون Bot ایی در این شبکهها وجود ندارد و اگر باشد احتمالا یک ویروس است!
اما روی اینترنت وضع فرق میکند. برای مثال یک کنترلر Feed ممکن است درخواستهایی از نوع Head نیز داشته باشد و یا بعضی از Botها بجای Get جهت دریافت اطلاعات فید، از Post هم استفاده میکنند! (مانند فیدبرنر گوگل مطابق لاگهای برنامه!) و در این حالت اگر متد رندر کردن یک فید را به HttpGet تنظیم کرده باشیم، مدام با خطای A public action method xyz was not found on controller abc در لاگهای خطای برنامه مواجه خواهیم شد.
بنابراین بهتر است که در سایتهای عمومی، جهت ارائه اطلاعات و صفحات عمومی، از هیچ نوع فیلتری برای محدود کردن یک اکشن متد که کارش صرفا رندر کردن اطلاعات است، استفاده نکنیم. به این ترتیب اگر کسی توسط درخواستی از نوع Head فقط قصد بررسی تغییرات صورت گرفته را داشت و نه دریافت کل فید، بیجهت آنرا محدود نکرده باشیم.
[HttpGet] public ActionResult Index() { return View(); }
اما روی اینترنت وضع فرق میکند. برای مثال یک کنترلر Feed ممکن است درخواستهایی از نوع Head نیز داشته باشد و یا بعضی از Botها بجای Get جهت دریافت اطلاعات فید، از Post هم استفاده میکنند! (مانند فیدبرنر گوگل مطابق لاگهای برنامه!) و در این حالت اگر متد رندر کردن یک فید را به HttpGet تنظیم کرده باشیم، مدام با خطای A public action method xyz was not found on controller abc در لاگهای خطای برنامه مواجه خواهیم شد.
بنابراین بهتر است که در سایتهای عمومی، جهت ارائه اطلاعات و صفحات عمومی، از هیچ نوع فیلتری برای محدود کردن یک اکشن متد که کارش صرفا رندر کردن اطلاعات است، استفاده نکنیم. به این ترتیب اگر کسی توسط درخواستی از نوع Head فقط قصد بررسی تغییرات صورت گرفته را داشت و نه دریافت کل فید، بیجهت آنرا محدود نکرده باشیم.