مطالب
Vue.js - نحوه‌ی ایجاد یک Vue جدید و استفاده از معماری MVVM - قسمت دوم
در قسمت قبلی توضیحاتی جهت معرفی و نصب فریم‌ورک داده شد. در این قسمت قصد داریم کمی بیشتر با ساختار آن آشنا شویم و یک vue جدید را ایجاد کنیم. پس لازم است تا ابتدا درون تگ اسکریپتی که فراخوانی کردیم و آدرس فریم‌ورک در آن قرار داده شده‌است، به صورت زیر یک ویو جدید را ایجاد کنیم:
<html>
    <body>
       <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/1.0.27/vue.min.js">  
 new Vue({ 
               
            }); 
       </script>
    </body>
</html>
ساختار vue.js بدین صورت است که از شما می‌خواهد تعیین کنید این کدها محدود و مربوط به کدام صفحه می‌شوند.
اگر قصد داشته باشید تا درون تگی با یک مشخصه‌ی خاص، دستوری را اجرا کنید، باید تعیین کنید که آن تگ چه خواصی دارد. فرض می‌کنیم که تگ body درون پروژه لازم است مقداری را فراخوانی و اجرا کند. پس لازم است تا یک id، به تگ بادی اختصاص یابد. 
<html>
    <body id="dotnettips">
       <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/1.0.27/vue.min.js">  
 new Vue({ 
               
            }); 
       </script>
    </body>
</html>
در کد بالا یک آیدی با نام dotnettips، به تگ بادی داده شد. حال قصد داریم تا با استفاده از دستور new Vue، رشته یا مقداری مشخص را در خروجی قرار دهیم. پس باید یک مشخصه را برای آن تعریف کرد که ما طبق مستندات فریم‌ورک، مشخصه‌ای را با نام el فراخوانی می‌کنیم:
<html>
    <body id="dotnettips">
       <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/1.0.27/vue.min.js">  
       new Vue({ 
           el: '#dotnettips',
           data: {
                name: 'dotnettips'
               }
            });
       </script>
    </body>
</html>
با استفاده از مشخصه el تعیین کردیم که کدهای برنامه برای کدام id در نظر گرفته شده‌اند. حال با استفاده از مقدار خاصیت name، مقادیر ما در خروجی چاپ خواهند شد.


استفاده از MVVM درون پروژه
به زبان ساده در معماری MVVM، یک مدل، یک view و یک viewmodel داریم که در مدل، کدهای JS و اطلاعاتی که قصد نمایش آن را داریم، قرار می‌دهیم. در واقع viewmodel نقش data building را خواهد داشت.

برای شروع، یک مدل جدید را نیاز داریم که بدین شکل باید فراخوانی کنیم.
 //new model 
var SampleData =( 
        name: 'dotnettips' 
)
در کد بالا ما یک مدل جدید ساخته‌ایم و یک ویژگی را به آن نسبت دادیم. حال لازم است تا ویوی جدیدی را برای نمایش آن ایجاد کنیم.
  • برای نمایش اطلاعات درون ویو جی اس باید از {{ }} استفاده کنید، تا ویژگی ساخته و فراخوانی شده را نمایش دهد.
<div id="dotnettips"> 
 Hello  {{ name }} 
</div>
مثال بالا، ویوی ما خواهد بود که ویژگی مورد نظر را در خروجی نمایش می‌دهد. حال، باید هردو را با viewmodel بهم متصل کنیم.
یک ویوی جدید را ایجاد می‌کنیم و با مشخصه el، ویژگی تعیین شده را به مدل خود متصل می‌کنیم.
new Vue({
  el:'#dotnettips',
  data: SampleData
})
 .یک نمونه از این مثال را از اینجا می‌توانید مشاهده کنید  
.به نمونه کد کامل زیر دقت بفرمائید
<html>  
    <body id="dotnettips">  
          
        <h3 id="dotnettips"> 
            Hello  {{ name }} 
        </h3>  
      
        <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/1.0.27/vue.min.js">
            
        </script>  
        <script type="text/javascript"> 
            new Vue({  
            el: '#dotnettips',  
                data:{  
                    name: 'dotnettips'  
                }  
            });  
        </script>  
        </body>  
</html>

مطالب
آشنایی با الگوی طراحی Template Method
سناریویی وجود دارد که در آن شما می‌خواهید تنها یک کار را انجام دهید، ولی برای انجام آن n روش وجود دارد. برای مثال قصد مرتب سازی دارید و برای اینکار روش‌های مختلفی وجود دارند. برای حل این مساله پیشتر از الگوی طراحی استراتژی استفاده نمودیم.(مطالعه بیشتر در مورد الگوی طراحی استراتژی)
حال به سناریویی برخورد کردیم که بصورت زیر است:
می‌خواهیم یک کار را انجام دهیم ولی برای انجام این کار تنها برخی بخش‌های کار با هم متفاوت هستند. برای مثال قصد تولید گزارش و چاپ آن را داریم. در این سناریو خواندن اطلاعات و پردازش آن‌ها رخدادهایی ثابت هستند. ولی اگر بخواهیم گزارش را چاپ کنیم به مشکل می‌خوریم؛ چرا که چاپ گزارش به فرمت اکسل، فرمت و روش خود را دارد و چاپ به فرمت PDF شرایط خود را دارد.
در این سناریو دیگر الگوی طراحی استراتژی جواب نخواهد داد و نیاز داریم با یک الگوی طراحی جدید آشنا بشویم. این الگوی طراحی Template Method نام دارد.
در این الگو یک کلاس انتزاعی داریم به صورت زیر:
    public abstract class DataExporter
    {
        public void ReadData()
        {
            Console.WriteLine("Data is reading from SQL Server Database");
        }

        public void ProcessData()
        {
            Console.WriteLine("Data is processing...!");
        }

        public abstract void PrintData();

        public void GetReport()
        {
            ReadData();
            ProcessData();
            PrintData();
        }
    }
این کلاس abstract، یک متد بنام GetReport دارد که نحوه‌ی انجام کار را مشخص می‌کند. متدهای ReadData و ProcessData نشان می‌دهند که انجام این دو عمل همیشه ثابت هستند (منظور در این سناریو همیشه ثابت هستند). متد PrintData همانطور که مشاهده می‌شود بصورت انتزاعی تعریف شده است، چرا که چاپ عملی است که در هر فرمت دارای خروجی متفاوتی می‌باشد.
لذا در ادامه داریم:
    public class ExcelExporter : DataExporter
    {
        public override void PrintData()
        {
            Console.WriteLine("Data exported to Microsoft Excel!");
        }
    }

    public class PDFExporter : DataExporter
    {
        public override void PrintData()
        {
            Console.WriteLine("Data exported to PDF!");
        }
    }
کلاس ExcelExporter برای چاپ به فرمت اکسل می‌باشد. همانطور که مشاهده می‌شود این کلاس از کلاس انتزاعی DataExporter ارث بری کرده است. این بدین معنا است که کلاس ExcelExporter کارهای ReadData و ProcessData را از کلاس پدر خود می‌گیرد و در ادامه نحوه‌ی چاپ مختص به خود را پیاده می‌کند. همین توضیحات در مورد PDFExporter نیز صادق است.
حال برای استفاده‌ی از این کدها داریم:
            DataExporter dataExporter = new ExcelExporter();
            dataExporter.GetReport();
            Console.WriteLine("****************************");
            dataExporter = new PDFExporter();
            dataExporter.GetReport();
شما شاید بخواهید متدهای ReadData و ExportData و ProcessData را با سطح دسترسی متفاوتی از public تعریف نمایید که در این مقاله به این دلیل که خارج از بحث بود به آنها اشاره نشد و بصورت پیش فرض public در نظر گرفته شد.
مطالب
تهیه‌ی گزارش از آخرین ارائه‌های یک پروژه در GitHub
چندی قبل مطلب «اطلاع از بروز رسانی نرم افزار ساخته شده» را در سایت جاری مطالعه کردید. در این روش بسیار متداول، شماره نگارش‌های جدید برنامه در یک فایل XML و مانند آن قرار می‌گیرند و برنامه هربار این فایل را جهت یافتن شماره‌های مندرج در آن اسکن می‌کند. اگر پروژه‌ی شما سورس باز است و در GitHub هاست شده، روش دیگری نیز برای یافتن این اطلاعات وجود دارد. در GitHub می‌توان از طریق آدرسی به شکل https://api.github.com/repos/user_name/project_name/releases به اطلاعات آخرین ارائه‌های یک پروژه (قرار گرفته در برگه‌ی releases آن) با فرمت JSON دسترسی یافت (یک مثال). در ادامه قصد داریم روش استفاده‌ی از آن‌را بررسی کنیم.


ساختار JSON ارائه‌های یک پروژه در GitHub

ساختار کلی اطلاعات ارائه‌های یک پروژه در GitHub چنین شکلی را دارد:
[
  {
    release_info,
    "assets": [
      {
      }
    ]
  }
]
در اینجا آرایه‌ای از اطلاعات ارائه‌ی یک پروژه ارسال می‌شود. هر ارائه نیز دارای دو قسمت است: لینکی به صفحه‌ی اصلی release در GitHub و سپس آرایه‌ای به نام assets که در آن اطلاعات فایل‌های پیوستی مانند نام فایل، آدرس، اندازه و امثال آن قرار گرفته‌اند.



تهیه‌ی کلاس‌های معادل فرمت JSON ارائه‌های برنامه در GitHub

اگر بخواهیم قسمت‌های مهم خروجی JSON فوق را تبدیل به کلاس‌های معادل دات نتی کنیم، به دو کلاس ذیل خواهیم رسید:
using Newtonsoft.Json;
using System;

namespace ApplicationAnnouncements
{
    public class GitHubProjectRelease
    {
        [JsonProperty(PropertyName = "url")]
        public string Url { get; set; }

        [JsonProperty(PropertyName = "assets_url")]
        public string AssetsUrl { get; set; }

        [JsonProperty(PropertyName = "upload_url")]
        public string UploadUrl { get; set; }

        [JsonProperty(PropertyName = "html_url")]
        public string HtmlUrl { get; set; }

        [JsonProperty(PropertyName = "id")]
        public int Id { get; set; }

        [JsonProperty(PropertyName = "tag_name")]
        public string TagName { get; set; }

        [JsonProperty(PropertyName = "target_commitish")]
        public string TargetCommitish { get; set; }

        [JsonProperty(PropertyName = "name")]
        public string Name { get; set; }

        [JsonProperty(PropertyName = "body")]
        public string Body { get; set; }

        [JsonProperty(PropertyName = "draft")]
        public bool Draft { get; set; }

        [JsonProperty(PropertyName = "prerelease")]
        public bool PreRelease { get; set; }

        [JsonProperty(PropertyName = "created_at")]
        public DateTime CreatedAt { get; set; }

        [JsonProperty(PropertyName = "published_at")]
        public DateTime PublishedAt { get; set; }

        [JsonProperty(PropertyName = "assets")]
        public Asset[] Assets { get; set; }
    }

    public class Asset
    {
        [JsonProperty(PropertyName = "url")]
        public string Url { get; set; }

        [JsonProperty(PropertyName = "id")]
        public int Id { get; set; }

        [JsonProperty(PropertyName = "name")]
        public string Name { get; set; }

        [JsonProperty(PropertyName = "label")]
        public string Label { get; set; }

        [JsonProperty(PropertyName = "content_type")]
        public string ContentType { get; set; }

        [JsonProperty(PropertyName = "state")]
        public string State { get; set; }

        [JsonProperty(PropertyName = "size")]
        public int Size { get; set; }

        [JsonProperty(PropertyName = "download_count")]
        public int DownloadCount { get; set; }

        [JsonProperty(PropertyName = "created_at")]
        public DateTime CreatedAt { get; set; }

        [JsonProperty(PropertyName = "updated_at")]
        public DateTime UpdatedAt { get; set; }
    }
}
در اینجا از ویژگی JsonProperty جهت معرفی نام‌های واقعی خواص ارائه شده‌ی توسط GitHub استفاده کرده‌ایم.
پس از تشکیل این کلاس‌ها، مرحله‌ی بعد، دریافت اطلاعات JSON از آدرس API ارائه‌های پروژه در GitHub و سپس نگاشت آن‌ها می‌باشد:
using (var webClient = new WebClient())
{
    webClient.Headers.Add("user-agent", "DNTProfiler");
    var jsonData = webClient.DownloadString(url);
    var gitHubProjectReleases = JsonConvert.DeserializeObject<GitHubProjectRelease[]>(jsonData);

    foreach (var release in gitHubProjectReleases)
    {
       foreach (var asset in release.Assets)
       {
           // …
       }
    }
}
حین کار با WebClient یا هر روش دیگری که برای دریافت اطلاعات از وب استفاده می‌کنید، حتما نیاز است user-agent را ذکر کرد. در غیر اینصورت GitHub درخواست شما را برگشت خواهد زد. پس از دریافت اطلاعات JSON، با استفاده از متد JsonConvert.DeserializeObject کتابخانه‌ی JSON.NET، می‌توان آن‌ها را تبدیل به آرایه‌ای از GitHubProjectRelease کرد.

یک نکته: اگر به صفحه‌ی اصلی ارائه‌های یک پروژه در GitHub دقت کنید، شماره‌ی تعداد بار دریافت یک ارائه مشخص نشده‌است. در این API، عدد DownloadCount، بیانگر تعداد بار دریافت پروژه‌ی شما است.
نظرات مطالب
Owin چیست ؟ قسمت اول
روش برنامه نویسی مایکروسافت بیش از دو سالی می‌شود که به این شکل شده است که هر امکان و قابلیت جدیدی بر روی آخرین نسخه NET Framework. ارائه می‌شود و البته سپس به نسخ قبلی نیز تعمیم می‌یابد، در همین جا است که می‌بینید اکثر امکانات Entity Framework 5 & 6 ابتدا بر روی NET Framework 4.5. ارائه شدند، و سپس بر روی 4
اگر ما بخواهیم به NET Framework. به عنوان یک پیش نیاز دردسر زا نگاه کنیم، در اولین قدم خودمان را به دردسر انداخته ایم، چون نه برای Helios، بلکه برای صدها امکان دیگر مانند Data Flow‌های جدید و ... نیز باید صبر کنیم، که عملا هزینه به فایده آن نمی‌صرفد. پس همیشه با فراغ بال از آخرین نسخه NET Framework. استقبال کنیم
نکته‌ی دیگر را که باید مد نظر داشته باشیم، این است که مطابق با سیاست هایی که مایکروسافت جدیدا اتخاذ کرده است، دیگر نباید خیلی نگران نسخه‌های جدید NET Framework. باشیم، چون دیگر از آن نسخه دهی‌های پشت سر هم و با حجم تغییرات بالا خبری نیست، بلکه اکثر فریم ورک‌های مهم جدا از NET. ارائه و به روز رسانی می‌شوند.
علاوه بر این، ارتقا به آخرین نسخه سیستم عامل ویندوز نیز به هیچ وجه مانند قبل ( IIS 6 به IIS 7 ) دردسر زا نیست، و خوشبختانه این ارتقا ( و یا تغییر هاست ) بدون دردسر است.
به نظر من این ارتقاء را انجام دهید، چون نه فقط Helios که خیلی چیزهای دیگری را دارید از دست می‌دهید، مانند سرعت بالاتر توسعه برنامه بر روی Visual Studio 2013 و Windows 8.1 برای توسعه برنامه‌های وبی، سرعت و کارآمدی بسیار بالاتر NET Framework 4.5.1. با IIS 8.5 برای مشتری‌های برنامه و ...
به نظر من آنقدر این ارتقاء ارزشمند است، که ارزش Helios این میان کمتر ارزشش به چشم می‌آید.
یکی از دلایلی که برنامه‌های سمت وب به سرعت بر برنامه‌های دسکتاپی قدیمی چیره شدند، همین است: امکان ارتقای سرورها در مدت زمان کم و به شکل مدیریت شده و با کمترین تاثیر روی مشتری‌های نهایی، بارها این تصمیم را که در ابتدایش کمی سخت به نظر می‌آید را گرفته ام و در نهایت از مشتری تا برنامه نویس همه را راضی دیده ام، چون هیچ کسی از امکانات جدید که بدون دردسر حاصل شود بدش نمی‌آید، و خوشبختانه کیفیت محصولات مایکروسافت واقعا بهبود یافته و دیگر آن زمانی که از NET 2. به 3.5 می‌رفتیم و گرفتار چندین مشکل می‌شدیم گذشته است.
از این نگذرید که بالاخره روزی باید این مهاجرت‌ها را انجام دهید، پس چه بهتر که از سود آن زودتر بهره مند شوید، البته بی مهابا عمل کردن توصیه نمی‌شود، بد نیست زمانی شروع به ارتقاء کنید که صفحه Release Notes و سوالات موجود در سایت Stack over flow در رابطه با اشکالات رخ داده در زمان ارتقاء و Breaking Changes را از بر باشید، به این صورت عمل کنید تماما برد کرده اید.
اشتراک‌ها
ویژگی های جدید در SQL Server 2019

Features and performance

Sql server 2019 will help us by making data integration, Management and intelligence easier and more intuitive than ever earlier.

Major Key points

  • Single virtual data layer

  • Data virtualization and Integrating Data

  • No data replication and Managing all data

  • Spark Built-In

  • Unified platform for big data analytics

  • Spark jobs

  • Train machine learning models

     
ویژگی های جدید در SQL Server 2019
بازخوردهای دوره
آشنایی با مدل برنامه نویسی TAP
استفاده از async به معنای خالی کردن ترد جاری کدهای مدیریت شده‌ی دات نت است و انجام سایر کارهای برنامه و صبر کردن برای دریافت پاسخی است که در سمت کدهای مدیریت شده نیازی به پردازش و محاسبه ندارد.
برای مثال در حالت کار با یک دیتابیس، این موتور بانک اطلاعاتی است که کوئری رسیده را پردازش می‌کند و برنامه‌ی ما صرفا درخواستی را به آن ارائه داده است. به این ترتیب در اینجا استفاده از async برای خالی کردن ترد جاری و صبر کردن جهت دریافت نتیجه‌ی اطلاعات از سرور مفید است و میزان پاسخ‌دهی برنامه را بالا می‌برد.
بنابراین استفاده از async در سمت کدهای دات نتی، تاثیری بر روی عملکرد یک بانک اطلاعاتی ندارد. فقط در سمت کدهای ما است که برنامه تا رسیدن و محاسبه‌ی درخواست توسط بانک اطلاعاتی، هنگ نمی‌کند.
در این حالت اگر برنامه‌ی شما ویندوزی است، ترد UI آن آزاد شده و برنامه مدام در حال هنگ به نظر نمی‌رسد. اگر برنامه‌ی وب است، ترد جاری آن آزاد شده و thread pool برنامه می‌تواند از این ترد آزاد شده، برای پردازش سایر درخواست‌های رسیده توسط کاربران استفاده کند. به این ترتیب بازدهی و اصطلاحا throughput سرور افزایش پیدا می‌کند.
در حال حاضر تمام APIهای جدید مایکروسافت نسخه‌ی async را هم اضافه کرده‌اند. برای مثال اگر از EF استفاده می‌کنید، از نسخه‌ی 6 آن به بعد، متدهایی مانند ToListAsync برای کوئری گرفتن معمولی غیرهمزمان و SaveChangesAsync برای ذخیره سازی اطلاعات به صورت غیرهمزمان، اضافه شده‌اند. یک مثال کامل در این مورد در اینجا
البته بدیهی است تمام ORMهای دات نتی در سطح پایین خودشان از ADO.NET استفاده می‌کنند. ADO.NET نیز Async API سازگار با دات نت 4.5 به بعد را مدتی است که اضافه کرده‌است. برای مثال متدهایی مانند ExecuteReaderAsync ، GetFieldValueAsync و ExecuteNonQueryAsync و امثال آن به زیر ساخت ADO.NET اضافه شده‌اند. اطلاعات بیشتر
نظرات مطالب
فعال سازی عملیات CRUD در Kendo UI Grid
- این چند سطری را هم که نوشتید، به علت استفاده‌ی از الگوی مخزن، کارآیی بسیار پائینی دارد:
 var tags = _blogRepository.GetAllTags(blogID).OrderBy(s => s.Id);
    var currenttags = _mapper.Map<IList<TagsViewModel>>(tags);
            if (currenttags == null)
            {
                return NotFound();

            }
            return Json(currenttags.AsQueryable()
                       .ToDataSourceResult(request.Take, request.Skip, request.Sort,
                                                       request.Filter));
ابتدا تمام اطلاعات یک جدول را دریافت می‌کنید. بعد این لیست درون حافظه‌ای را نگاشت می‌کنید (که اگر از الگوی مخزن استفاده نمی‌کردید، با یک کوئری قابل انجام بود). بعد آن‌ها را تبدیل می‌کنید به Queryable و در آخر متد ToDataSourceResult را بر روی آن فراخوانی می‌کنید. هدف از متد ToDataSourceResult این بوده که مستقیما روی کوئری LINQ to Entities شما فراخوانی شود (پیاده سازی dynamic linq است) و نه به این شکل. کل این عملیات باید یک کوئری شود؛ نه سه کوئری که یک مورد آن بدترین حالت واکشی کل اطلاعات از بانک اطلاعاتی است و سپس فیلتر کردن آن در طی دو کوئری سمت کلاینت. استفاده از dynamic linq (یا ToDataSourceResult در اینجا)، یعنی اضافه کردن where، take و skip به صورت خودکار به کوئری نهایی شما؛ یعنی عدم نیاز به تنظیم دستی هرباره‌ی این‌ها و ساده شدن کوئری نویسی. هدف آن هم اعمال به کوئری اصلی LINQ to Entities است تا در SQL نهایی ظاهر شود. اینجا است که الگوی مخزن شما نه تنها کمکی نکرده، بلکه با مخفی کردن کوئری اصلی LINQ to Entites و بازگشت کل اطلاعات به صورت درون حافظه‌ای، کارآیی برنامه را هم به شدت کاهش داده.
- مثالی که اینجا ارائه شده جهت سهولت کار و توضیح و همچنین کاهش بار ذهنی خواننده، از یک منبع داده‌ی درون حافظه‌ای ساده‌ی بدون نیاز به بانک اطلاعاتی استفاده می‌کند. نه اینکه کدهای نهایی شما باید دقیقا به این شکل باشد.
نظرات مطالب
NoSQL ؟

با سلام

من به عنوان کسی که در پروژه‌های خود از انوع ذخیره سازی‌ها بر اساس نیاز استفاده کردم(سرعت! راحتی! پلتفرم ها! و...) هم نظر میدم و هم پاسخ شما دوست عزیز را می‌دم.

قطعا انتخاب اینکه از چه روشی برای ذخیره سازی داده‌ها استفاده شود بسته به تیم پیاده سازکننده پروژه و نیز طراحان و... دارد. من با یک مثال توضیحی را خدمت شما می‌دهم.

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

عذر می‌خوام اگر بجای نویسنده پاسخ دادم البته این پاسخ من خیلی سربسته بود و انشا.. مفید بوده.

از نویسنده مطلب بابت مطلب خوبشون که کم دیدم در تارنماهای فارسی به اون بپردازن(متاسفانه بسیاری از اساتید دانشگاهی با این مفهوم حتی اشنایی ندارند با اینکه دانستن کلیت ان یک تعریف ساده است!) موفق باشید.

مطالب
دریافت و نمایش فایل‌های PDF در برنامه‌های Blazor WASM
زمانیکه قرار است با فایل‌های باینری واقع در سمت سرور کار کنیم، اگر اکشن متدهای ارائه دهنده‌ی آن‌ها محافظت شده نباشند، برای نمایش و یا دریافت آن‌ها تنها کافی است از آدرس مستقیم این منابع استفاده کرد و در این حالت نیازی به رعایت هیچ نکته‌ی خاصی نیست. اما اگر اکشن متدی در سمت سرور توسط فیلتر Authorize محافظت شده باشد و روش محافظت نیز مبتنی بر کوکی‌ها نباشد، یعنی این کوکی‌ها در طی درخواست‌های مختلف، به صورت خودکار توسط مرورگر به سمت سرور ارسال نشوند، آنگاه نیاز است با استفاده از HttpClient برنامه‌های Blazor WASM، درخواست دسترسی به منبعی را به همراه برای مثال JSON Web Tokens کاربر به سمت سرور ارسال کرد و سپس فایل باینری نهایی را به صورت آرایه‌ای از بایت‌ها دریافت نمود. در این حالت با توجه به ماهیت Ajax ای این این عملیات، برای نمایش و یا دریافت این فایل‌های محافظت شده در مرورگر، نیاز به دانستن نکات ویژه‌ای است که در این مطلب به آن‌ها خواهیم پرداخت.



کدهای سمت سرور دریافت فایل PDF

در اینجا کدهای سمت سرور برنامه، نکته‌ی خاصی را به همراه نداشته و صرفا یک فایل PDF ساده (محتوای باینری) را بازگشت می‌دهد:
using Microsoft.AspNetCore.Mvc;

namespace BlazorWasmShowBinaryFiles.Server.Controllers
{
    [ApiController]
    [Route("api/[controller]")]
    public class ReportsController : ControllerBase
    {
        [HttpGet("[action]")]
        public IActionResult GetPdfReport()
        {
            //TODO: create the `sample.pdf` report file on the server

            return File(virtualPath: "~/app_data/sample.pdf",
                        contentType: "application/pdf",
                        fileDownloadName: "sample.pdf");
        }
    }
}
که در نهایت با آدرس api/Reports/GetPdfReport در سمت کلاینت قابل دسترسی خواهد بود.

یک نکته: استفاده مستقیم از کتابخانه‌های تولید PDF در برنامه‌های سمت کاربر Blazor منطقی نیست؛ چون به ازای هر کاربر، گاهی از اوقات مجبور به ارسال بیش از 8 مگابایت اضافی مختص به فایل‌های dll. آن کتابخانه‌ی تولید PDF خواهیم شد. بنابراین بهتر است تولید PDF را در سمت سرور و در اکشن متدهای Web API انجام داد و سپس فایل نهایی تولیدی را در برنامه‌ی سمت کلاینت، دریافت و یا نمایش داد. به همین جهت در این مثال خروجی نهایی یک چنین عملیات فرضی را توسط یک اکشن متد Web API ارائه داده‌ایم که در بسیاری از موارد حتی می‌تواند توسط فیلتر Authorize نیز محافظت شده باشد.


ساخت URL برای دسترسی به اطلاعات باینری

تمام مرورگرهای جدید از ایجاد URL برای اشیاء Blob دریافتی از سمت سرور، توسط متد توکار URL.createObjectURL پشتیبانی می‌کنند. این متد، شیء URL را از شیء window جاری دریافت می‌کند و سپس اطلاعات باینری را دریافت کرده و آدرسی را جهت دسترسی موقت به آن تولید می‌کند. حاصل آن، یک URL ویژه‌است مانند blob:https://localhost:5001/03edcadf-89fd-48b9-8a4a-e9acf09afd67 که گشودن آن در مرورگر، یا سبب نمایش آن تصویر و یا دریافت مستقیم فایل خواهد شد.
در برنامه‌های Blazor نیاز است اینکار را توسط JS Interop آن انجام داد؛ از این جهت که API تولید یک Blob URL، صرفا توسط کدهای جاوا اسکریپتی قابل دسترسی است. به همین جهت فایل جدید Client\wwwroot\site.js را با محتوای زیر ایجاد کرده و همچنین مدخل آن‌را در به انتهای فایل Client\wwwroot\index.html، پیش از بسته شدن تگ body، اضافه می‌کنیم:
window.JsBinaryFilesUtils = {
  createBlobUrl: function (byteArray, contentType) {
    // The byte array in .NET is encoded to base64 string when it passes to JavaScript.
    const numArray = atob(byteArray)
      .split("")
      .map((c) => c.charCodeAt(0));
    const uint8Array = new Uint8Array(numArray);
    const blob = new Blob([uint8Array], { type: contentType });
    return URL.createObjectURL(blob);
  },
  downloadFromUrl: function (fileName, url) {
    const anchor = document.createElement("a");
    anchor.style.display = "none";
    anchor.href = url;
    anchor.download = fileName;
    document.body.appendChild(anchor);
    anchor.click();
    document.body.removeChild(anchor);
  },
  downloadBlazorByteArray: function (fileName, byteArray, contentType) {
    const blobUrl = this.createBlobUrl(byteArray, contentType);
    this.downloadFromUrl(fileName, blobUrl);
    URL.revokeObjectURL(blobUrl);
  },
  printFromUrl: function (url) {
    const iframe = document.createElement("iframe");
    iframe.style.display = "none";
    iframe.src = url;
    document.body.appendChild(iframe);
    if (iframe.contentWindow) {
      iframe.contentWindow.print();
    }
  },
  printBlazorByteArray: function (byteArray, contentType) {
    const blobUrl = this.createBlobUrl(byteArray, contentType);
    this.printFromUrl(blobUrl);
    URL.revokeObjectURL(blobUrl);
  },
  showUrlInNewTab: function (url) {
    window.open(url);
  },
  showBlazorByteArrayInNewTab: function (byteArray, contentType) {
    const blobUrl = this.createBlobUrl(byteArray, contentType);
    this.showUrlInNewTab(blobUrl);
    URL.revokeObjectURL(blobUrl);
  },
};
توضیحات:
- زمانیکه در برنامه‌های Blazor با استفاده از متد ()HttpClient.GetByteArrayAsync آرایه‌ای از بایت‌های یک فایل باینری را دریافت می‌کنیم، ارسال آن به کدهای جاوااسکریپتی به صورت یک رشته‌ی base64 شده صورت می‌گیرد (JS Interop اینکار را به صورت خودکار انجام می‌دهد). به همین جهت در متد createBlobUrl روش تبدیل این رشته‌ی base64 دریافتی را به آرایه‌ای از بایت‌ها، سپس به یک Blob و در آخر به یک Blob URL، مشاهده می‌کنید.  این Blob Url اکنون آدرس موقتی دسترسی به آرایه‌ای از بایت‌های دریافتی توسط مرورگر است. به همین جهت می‌توان از آن به عنوان src بسیاری از اشیاء HTML استفاده کرد.
- متد downloadFromUrl، کار دریافت یک Url و سپس دانلود خودکار آن‌را انجام می‌دهد. اگر به یک anchor استاندارد HTML، ویژگی download را نیز اضافه کنیم، با کلیک بر روی آن، بجای گشوده شدن این Url، مرورگر آن‌را دریافت خواهد کرد. متد downloadFromUrl کار ساخت لینک و تنظیم ویژگی‌های آن و سپس کلیک بر روی آن‌را به صورت خودکار انجام می‌دهد. از متد downloadFromUrl زمانی استفاده کنید که منبع مدنظر، محافظت شده نباشد و Url آن به سادگی در مرورگر قابل گشودن باشد.
- متد downloadBlazorByteArray همان کار متد downloadFromUrl را انجام می‌دهد؛ با این تفاوت که Url مورد نیاز توسط متد downloadFromUrl را از طریق یک Blob Url تامین می‌کند.
- متد printFromUrl که جهت دسترسی به منابع محافظت نشده طراحی شده‌است، Url یک منبع را دریافت کرده، آن‌را به یک iframe اضافه می‌کند و سپس متد print را بر روی این iframe به صورت خودکار فراخوانی خواهد کرد تا سبب ظاهر شدن صفحه‌ی پیش‌نمایش چاپ شود.
- printBlazorByteArray همان کار متد printFromUrl را انجام می‌دهد؛  با این تفاوت که Url مورد نیاز توسط متد printFromUrl را از طریق یک Blob Url تامین می‌کند.


تهیه‌ی متدهایی الحاقی جهت کار ساده‌تر با JsBinaryFilesUtils

پس از تهیه‌ی JsBinaryFilesUtils فوق، می‌توان با استفاده از کلاس زیر که به همراه متدهایی الحاقی جهت دسترسی به امکانات آن است، کار با متدهای دریافت، نمایش و چاپ فایل‌های باینری را ساده‌تر کرد و از تکرار کدها جلوگیری نمود:
using System.Threading.Tasks;
using Microsoft.JSInterop;

namespace BlazorWasmShowBinaryFiles.Client.Utils
{
    public static class JsBinaryFilesUtils
    {
        public static ValueTask<string> CreateBlobUrlAsync(
            this IJSRuntime JSRuntime,
            byte[] byteArray, string contentType)
        {
            return JSRuntime.InvokeAsync<string>("JsBinaryFilesUtils.createBlobUrl", byteArray, contentType);
        }

        public static ValueTask DownloadFromUrlAsync(this IJSRuntime JSRuntime, string fileName, string url)
        {
            return JSRuntime.InvokeVoidAsync("JsBinaryFilesUtils.downloadFromUrl", fileName, url);
        }

        public static ValueTask DownloadBlazorByteArrayAsync(
            this IJSRuntime JSRuntime,
            string fileName, byte[] byteArray, string contentType)
        {
            return JSRuntime.InvokeVoidAsync("JsBinaryFilesUtils.downloadBlazorByteArray",
                    fileName, byteArray, contentType);
        }

        public static ValueTask PrintFromUrlAsync(this IJSRuntime JSRuntime, string url)
        {
            return JSRuntime.InvokeVoidAsync("JsBinaryFilesUtils.printFromUrl", url);
        }

        public static ValueTask PrintBlazorByteArrayAsync(
            this IJSRuntime JSRuntime,
            byte[] byteArray, string contentType)
        {
            return JSRuntime.InvokeVoidAsync("JsBinaryFilesUtils.printBlazorByteArray", byteArray, contentType);
        }

        public static ValueTask ShowUrlInNewTabAsync(this IJSRuntime JSRuntime, string url)
        {
            return JSRuntime.InvokeVoidAsync("JsBinaryFilesUtils.showUrlInNewTab", url);
        }

        public static ValueTask ShowBlazorByteArrayInNewTabAsync(
            this IJSRuntime JSRuntime,
            byte[] byteArray, string contentType)
        {
            return JSRuntime.InvokeVoidAsync("JsBinaryFilesUtils.showBlazorByteArrayInNewTab", byteArray, contentType);
        }
    }
}


اصلاح Content Security Policy سمت سرور جهت ارائه‌ی محتوای blob

پس از دریافت فایل PDF به صورت یک blob، با استفاده از متد URL.createObjectURL می‌توان آدرس موقت محلی را برای دسترسی به آن تولید کرد و یک چنین آدرس‌هایی به صورت blob:http تولید می‌شوند. در این حالت در Content Security Policy سمت سرور، نیاز است امکان دسترسی به تصاویر و همچنین اشیاء از نوع blob را نیز آزاد معرفی کنید:
img-src 'self' data: blob:
default-src 'self' blob:
object-src 'self' blob:
در غیراینصورت مرورگر، نمایش یک چنین تصاویر و یا اشیایی را سد خواهد کرد.


نمایش فایل PDF دریافتی از سرور، به همراه دکمه‌های دریافت، چاپ و نمایش آن در صفحه‌ی جاری

در ادامه کدهای کامل مرتبط با تصویری را که در ابتدای بحث مشاهده کردید، ملاحظه می‌کنید:
@page "/"

@using BlazorWasmShowBinaryFiles.Client.Utils

@inject IJSRuntime JSRuntime
@inject HttpClient HttpClient

<h1>Display PDF Files</h1>
<button class="btn btn-info" @onclick="handlePrintPdf">Print PDF</button>
<button class="btn btn-primary ml-2" @onclick="handleShowPdf">Show PDF</button>
<button class="btn btn-success ml-2" @onclick="handleDownloadPdf">Download PDF</button>

@if(!string.IsNullOrWhiteSpace(PdfBlobUrl))
{
    <section class="card mb-5 mt-3">
        <div class="card-header">
            <h4>using iframe</h4>
        </div>
        <div class="card-body">
            <iframe title="PDF Report" width="100%" height="600" src="@PdfBlobUrl" type="@PdfContentType"></iframe>
        </div>
    </section>

    <section class="card mb-5">
        <div class="card-header">
            <h4>using object</h4>
        </div>
        <div class="card-body">
            <object data="@PdfBlobUrl" aria-label="PDF Report" type="@PdfContentType" width="100%" height="100%"></object>
        </div>
    </section>

    <section class="card mb-5">
        <div class="card-header">
            <h4>using embed</h4>
        </div>
        <div class="card-body">
            <embed aria-label="PDF Report" src="@PdfBlobUrl" type="@PdfContentType" width="100%" height="100%">
        </div>
    </section>
}

@code
{
    private const string ReportUrl = "/api/Reports/GetPdfReport";
    private const string PdfContentType = "application/pdf";

    private string PdfBlobUrl;

    private async Task handlePrintPdf()
    {
        // Note: Using the `HttpClient` is useful for accessing the protected API's by JWT's (non cookie-based authorization).
        // Otherwise just use the `PrintFromUrlAsync` method.
        var byteArray = await HttpClient.GetByteArrayAsync(ReportUrl);
        await JSRuntime.PrintBlazorByteArrayAsync(byteArray, PdfContentType);
    }

    private async Task handleDownloadPdf()
    {
        // Note: Using the `HttpClient` is useful for accessing the protected API's by JWT's (non cookie-based authorization).
        // Otherwise just use the `DownloadFromUrlAsync` method.
        var byteArray = await HttpClient.GetByteArrayAsync(ReportUrl);
        await JSRuntime.DownloadBlazorByteArrayAsync("report.pdf", byteArray, PdfContentType);
    }

    private async Task handleShowPdf()
    {
        // Note: Using the `HttpClient` is useful for accessing the protected API's by JWT's (non cookie-based authorization).
        // Otherwise just use the `ReportUrl` as the `src` of the `iframe` directly.
        var byteArray = await HttpClient.GetByteArrayAsync(ReportUrl);
        PdfBlobUrl = await JSRuntime.CreateBlobUrlAsync(byteArray, PdfContentType);
    }

    // Tips:
    // 1- How do I enable/disable the built-in pdf viewer of FireFox
    // https://support.mozilla.org/en-US/kb/disable-built-pdf-viewer-and-use-another-viewer
    // 2- How to configure browsers to use the Adobe PDF plug-in to open PDF files
    // https://helpx.adobe.com/acrobat/kb/pdf-browser-plugin-configuration.html
    // https://helpx.adobe.com/acrobat/using/display-pdf-in-browser.html
    // 3- Microsoft Edge is gaining new PDF reader features within the Windows 10 Fall Creator’s Update (version 1709).
}
توضیحات:
- پس از تهیه‌ی JsBinaryFilesUtils و متدهای الحاقی متناظر با آن، اکنون تنها کافی است با  استفاده از متد ()HttpClient.GetByteArrayAsync، فایل PDF ارائه شده‌ی توسط یک اکشن متد را به صورت آرایه‌ای از بایت‌ها دریافت و سپس به متدهای چاپ (PrintBlazorByteArrayAsync) و دریافت (DownloadBlazorByteArrayAsync) آن ارسال کنیم.
- در مورد نمایش آرایه‌ای از بایت‌های دریافتی، وضعیت کمی متفاوت است. ابتدا باید توسط متد CreateBlobUrlAsync، آدرس موقتی این آرایه را در مرورگر تولید کرد و سپس این آدرس را برای مثال به src یک iframe انتساب دهیم تا PDF را با استفاده از امکانات توکار مرورگر، نمایش دهد.


کدهای کامل این مطلب را از اینجا می‌توانید دریافت کنید: BlazorWasmShowBinaryFiles.zip
مطالب
نمایش تقویم ماهیانه شمسی توسط PdfReport
در نگارش 1.6، قالب سلول جدیدی به نام MonthCalendar اضافه شده است که امکان نمایش تقویم ماهیانه شمسی و میلادی را فراهم می‌کند. در ادامه نحوه استفاده از آن‌را بررسی خواهیم کرد. کدهای کامل این مثال را از اینجا نیز می‌توانید دریافت کنید: (^)
فرض کنید اطلاعات حضور و غیاب کارمندان را به نحو زیر در اختیار دارید:
namespace PdfReportSamples.Models
{
    public class UserWorkedHours
    {
        public int Id { set; get; }
        public string Name { set; get; }
        public int DayNumber { set; get; }
        public int Month { set; get; }
        public int Year { set; get; }
        public string Description { set; get; }
    }
}
و برای نمونه منبع داده فرضی ما نیز به صورت زیر است (تعدادی روز، به همراه ساعات کارکرد):
        private static List<UserWorkedHours> createUsersWorkedHours()
        {
            var usersWorkedHours = new List<UserWorkedHours>();
            for (int i = 1; i < 11; i++)
            {
                for (int j = 1; j < 28; j++)
                {
                    usersWorkedHours.Add(new UserWorkedHours
                    {
                        Id = i,
                        Name = "کارمند " + i,
                        Year = 1391, // سال و ماه بر اساس نوع تقویم انتخابی مشخص می‌شود
                        Month = i,
                        DayNumber = j,
                        Description = i % 2 == 0 ? "05:00" : "08:00"
                    });
                }
            }
            return usersWorkedHours;
        }
در این منبع داده فرضی، متن Description ذیل شماره روز، در تقویم ماهیانه نمایش داده خواهد شد.
سلولی که قرار است قالب MonthCalendar را نمایش دهد نیاز به شیء‌ایی از نوع PdfRpt.Calendar.CalendarData دارد که به نحو زیر تعریف شده است:
using System.Collections.Generic;

namespace PdfRpt.Calendar
{
    public class CalendarData
    {
        public int Month { set; get; }
        public int Year { set; get; }
        public IList<DayInfo> MonthDaysInfo { set; get; }
    }
}

namespace PdfRpt.Calendar
{
    public class DayInfo
    {
        public int DayNumber { set; get; }
        public int Month { set; get; }
        public int Year { set; get; }

        public string Description { set; get; }
        public bool ShowDescriptionInFooter { set; get; }
    }
}
این ساختار بر اساس اطلاعات یک ماه و روزهای آن است. متن Description در صورت false بودن ShowDescriptionInFooter ذیل شماره روز نمایش داده خواهد شد، در غیراینصورت در پایان ماه به شکل یک سطر جدید نمایش داده می‌شود. در اینجا روزهای ماه و سال بر اساس نوع تقویم معنا خواهند شد.

اکنون نیاز است تا اطلاعات منبع داده خود را به CalendarData نگاشت کنیم تا بتوان از آن در قالب سلول جدید MonthCalendar استفاده کرد. انجام اینکار با استفاده از امکانات LINQ به نحو زیر است:
        public static IList<UserMonthCalendar> CreateDataSource()
        {
            var usersWorkedHours = createUsersWorkedHours();
            // Mapping a list of normal Users WorkedHours to a list of Users + CalendarData
            return usersWorkedHours
                        .GroupBy(x => new
                        {
                            Id = x.Id,
                            Name = x.Name
                        })
                        .Select(
                                 x => new UserMonthCalendar
                                 {
                                     Id = x.Key.Id,
                                     Name = x.Key.Name,
                                     // Calendar's cell data type should be PdfRpt.Calendar.CalendarData
                                     MonthCalendarData = new CalendarData
                                     {
                                         Year = x.First().Year,
                                         Month = x.First().Month,
                                         MonthDaysInfo = x.ToList().Select(y => new DayInfo
                                         {
                                             Description = y.Description,
                                             ShowDescriptionInFooter = false,
                                             DayNumber = y.DayNumber
                                         }).ToList()
                                     }
                                 }).ToList();
        }
UserMonthCalendar، شامل ستون‌هایی است که قرار است در گزارش ما ظاهر شوند:
using PdfRpt.Calendar;

namespace PdfReportSamples.Models
{
    public class UserMonthCalendar
    {
        public int Id { set; get; }
        public string Name { set; get; }
        // Calendar's cell data type should be CalendarData
        public CalendarData MonthCalendarData { set; get; }
    }
}
ستون اول، شماره شخص، ستون دوم شامل نام شخص و ستون سوم، شامل اطلاعات یک ماه شخص است.
برای نمایش این اطلاعات توسط PdfReport، دو ستون اول یاد شده نکته خاصی ندارند، اما نحوه تعریف ستون تقویم ماهیانه آن به صورت زیر خواهد بود:
                columns.AddColumn(column =>
                {
                    // Calendar's cell data type should be PdfRpt.Calendar.CalendarData
                    column.PropertyName<UserMonthCalendar>(x => x.MonthCalendarData);
                    column.CellsHorizontalAlignment(HorizontalAlignment.Center);
                    column.IsVisible(true);
                    column.Order(3);
                    column.Width(3);
                    column.HeaderCell("تقویم ماهیانه");
                    column.ColumnItemsTemplate(itemsTemplate =>
                    {
                        itemsTemplate.MonthCalendar(new CalendarAttributes
                        {
                            CalendarType = CalendarType.PersianCalendar,
                            UseLongDayNamesOfWeek = true,
                            Padding = 3,
                            DescriptionHorizontalAlignment = HorizontalAlignment.Center,
                            SplitRows = true,
                            CellsCustomizer = info =>
                            {
                                if (info.Year == 1391 && info.Month == 1 && info.DayNumber == 1)
                                {
                                    info.NumberCell.BackgroundColor = new BaseColor(System.Drawing.Color.LimeGreen);
                                    var phrase = info.NumberCell.Phrase;
                                    foreach (var chunk in phrase.Chunks)
                                        chunk.Font.Color = new BaseColor(System.Drawing.Color.Yellow);
                                }
                            }
                        });
                    });
                });
توسط CalendarAttributes می‌توان یک سری از خواص تقویم نمایش داده شده را تغییر داد. برای مثال CalendarType مشخص می‌کند که نوع تقویم شمسی است یا میلادی؛ UseLongDayNamesOfWeek برای نمایش نام روزها به صورت کامل «شنبه» یا «ش» (نام کوتاه شده آن) بکار می‌رود. SplitRows مشخص می‌کند که اگر تقویم در یک صفحه جا نشد، به صفحه بعد منتقل شود یا تا جایی که ممکن است در صفحه جاری اطلاعات آن نمایش داده شده و سپس مابقی را در صفحه بعد ترسیم کند (مقدار true آن). به علاوه توسط CellsCustomizer می‌توان فرمت کردن شرطی اطلاعات را انجام داد. برای مثال در اینجا اگر روز مورد نظر، روز اول سال 91 باشد، رنگ زمینه سلول و رنگ متن عدد آن تغییر خواهد کرد.