نظرات مطالب
نمایش گرادیان در iTextSharp
مطالب
استفاده از thickbox در بلاگر

Thickbox یکی از پلاگین‌های jQuery است که جهت نمایش صفحات modal بکار می‌رود.

روش استفاده بسیار ساده‌ای هم دارد:
الف) jquery.js باید به صفحه معرفی شود.
ب) سپس thickbox.js باید به صفحه الحاق شود.
ج) و فایل css آن یعنی thickbox.css نیز باید به صفحه افزوده شود.

برای استفاده از آن کافی است یک لینک به صفحه اضافه کنید که ویژگی class آن مساوی thickbox باشد. مثلا:
<a href="#TB_inline?height=155&width=300&inlineId=hiddenModalContent&modal=true" class="thickbox">Show hidden modal content.</a>
TB_inline و طول و عرض و غیره، جزو خواص استاندارد این پلاگین است. inlineId جهت معرفی id یک div در صفحه بکار می‌رود که قرار است محتویات آن زمانیکه روی این لینک کلیک شد به صورت modal نمایش داده شود.
<a href="iframeModal.html?placeValuesBeforeTB_=savedValues&TB_iframe=true&height=200&width=300&modal=true" title="add a caption to title attribute / or leave blank" class="thickbox">Open iFrame Modal</a>
و یا مانند مثال فوق می‌تواند یک صفحه خارجی را نیز به صورت modal بارگذاری و نمایش دهد. Title بکارگرفته شده، همان عنوان صفحه Modal ما خواهد بود.
و مثال‌های دیگری از این دست که در صفحه رسمی آن موجود است و نیازی به تکرار آن‌ها نیست.

اما مشکلی که در بلاگر وجود دارد این است که شما نمی‌توانید این ویژگی‌ها را در بسیاری از موارد به صورت دستی تنظیم کنید. یک ویجت ظاهر می‌شود که فقط به شما امکان ثبت یک عنوان و لینک مربوطه را می‌دهد و همین.
برای انتساب ویژگی‌های thickBox به این نوع لینک‌ها که کنترلی روی آن نیست می‌توان از خود jQuery کمک گرفت:
$(document).ready(function(){
$("a[href='http://www.mysite.com/page.htm?TB_iframe=true&height=340&width=530']").each(function(){
var obj = $(this);
obj.attr("title","Contact me");
obj.attr("class","thickbox");
}).bind("contextmenu",function(e){
return false;
});
});
در اینجا http://www.mysite.com/page.htm?TB_iframe=true&height=340&width=530 همان صفحه‌ای که قرار است پس از کلیک بر روی لینکی منتسب به آن در صفحه به صورت modal باز شود. با استفاده از جی‌کوئری آن‌را یافته (لینکی که href آن مساوی مقدار یاد شده است) و سپس ویژگی‌های title و class آن را به صورت پویا تنظیم می‌کنیم تا توسط thickbox قابل شناسایی شود.
همچنین در اینجا کلیک راست نیز بر روی این لینک بسته شده تا کاربر فقط کلیک معمولی را انجام دهد (فقط روی همین یک لینک در صفحه اعمال می‌شود و نه کل صفحه).

مطالب
AngularJS #2
بهتر است قبل از این که به ادامه‌ی آموزش بپردازم، دو نکته را متذکر شوم:
1) روند آموزشی این فریمورک از کل به جز است؛ به این معنا که ابتدا تمامی قابلیت‌های اصلی فریمورک را به صورت کلی و بدون وارد شدن به جزئیات بیان می‌کنم و پس از آن، جزئیات را در قالب مثال‌هایی واقعی بیان خواهم کرد.
2) IDE مورد استفاده بنده Visual Studio 2012 است. همچنین از ابتدا پروژه را با ASP.NET MVC شروع می‌کنم. شاید بگویید که می‌شود Angular را بدون درگیر شدن با مباحث ASP.NET MVC بیان کرد؛ اما پاسخ من این است که این مثال‌ها باید قابل پیاده‌سازی در نرم‌افزارهای واقعی باشند و یکی از بسترهای مورد علاقه‌ی من ASP.NET MVC است. اگرچه باز هم تاکید می‌کنم که کلیه‌ی مباحث ذکرشده، برای کلیه‌ی زبان‌های سمت سرور دیگر هم قابل استفاده است و هدف من در اینجا بیان یک سری چالش‌ها در ASP.NET MVC است.

نحوه‌ی دریافت AngularJS
1) NuGet Package Manager
2) دریافت از وب‌سایت angularjs.org

دریافت از طریق Nuget Package Manager
روش ارجح افزودن کتابخانه‌های جانبی در یک پروژه‌ی واقعی، استفاده از NuGet Package Manager است. دلیل آن هم بارها بیان‌شده است از جمله: باخبر شدن از آخرین به‌روزرسانی کتابخانه‌ها، دریافت وابستگی‌های کتاب‌خانه‌ی مورد نظر و نبودن محدودیت تحریم برای دریافت فایل‌ها است.
روش کار هم بسیار ساده است، کافی است که بر روی پروژه کلیک راست کرده و گزینه‌ی Manage NuGet Packages را انتخاب کنید و با جست جو angularjs نسبت به نصب آن اقدام نمایید.
اگر هم با ابزارهای گرافیکی رابطه‌ی خوبی ندارید، می‌توانید از Package Manager Console فراهم‌شده توسط NuGet استفاده کنید. کافی است در کنسول پاورشل آن عبارت زیر را تایپ کنید:
Install-Package angularjs

پس از نصب angularjs، شاهد تغییراتی در پوشه‌ی Scripts پروژه‌ی خودخواهید بود. تعداد زیادی فایل جاوا اسکریپت که با عبارت angular شروع‌شده‌اند، به این پوشه اضافه شده است. در حال حاضر ما تنها به فایل angular.js نیاز داریم و احتیاجی به فایل‌های دیگر نیست.
همچنین یک پوشه به نام i18n نیز اضافه شده است که برای مباحث Globalization و Internationalization به کار گرفته می‌شود. 
 
دریافت از سایت angularjs.org
برای دریافت Angular از وب سایت رسمی‌اش، به angularjs.org مراجعه کنید؛ اما گویا به دلیل تحریم‌ها این سایت برای IP ایران مسدود شده است (البته افرادی نیز بدون مشکل به آن دسترسی دارند). دکمه‌ی Download را فشار داده و در نهایت کلید دریافت را بزنید. اگر نسخه‌ی کامل آن را دریافت کنید، لیستی از مستندات AngularJS را نیز در فایل دریافتی، خواهید داشت. در هر صورت این روش برای استفاده از angular دریک پروژه‌ی واقعی توصیه نمی‌شود.
پس به عنوان یک best practice، همیشه کتاب‌خانه‌های جانبی را با NuGet دریافت و نصب کنید. رفع موانع تحریم‌ها، یکی از مزایای مهم آن است.
   
پس از دریافت angular،  نوشتن برنامه‌ی معروف Hello, World به وسیله‌ی آن ، می‌تواند بهترین شروع باشد؛ اما اگر اجازه بدهید، نوشتن این برنامه را در قالب توضیح قالب‌های سمت کلاینت انجام دهیم.
   
قالب‌های سمت کلاینت (Client Side Templates)
در برنامه‌های وب چند صفحه‌ای و یا اکثر وب سایت‌های معمول، داده‌ها و کدهای HTML، در سمت سرور اصطلاحا سرهم و مونتاژ شده و خروجی نهایی که HTML خام است به مرورگر کاربر ارسال می‌شود. با یک مثال بیشتر توضیح می‌دهم: در ASP.NET MVC معمولا از لحظه‌ای که کاربر صفحه‌ای را درخواست می‌کند تا زمانی که پاسخ خود را در قالب HTML می‌بیند، این فرآیند طی می‌شود: ابتدا درخواست به Controller هدایت می‌شود و سپس اطلاعات مورد نیاز از پایگاه داده خوانده‌شده و در قالب یک Model به View که یک فایل HTML ساده است، منتقل می‌شود. سپس به کمک موتور نمایشی Razor، داده‌ها در جای مناسب خود قرار می‌گیرند و در نهایت، خروجی که HTML خام است به مرورگر کلاینت درخواست‌کننده ارسال می‌شود تا در مرورگر خود نتیجه را مشاهده نماید. روال کار نیز در اکثر SPA‌های معمول و یا اصطلاحا برنامه‌های AJAX، باکمی تغییر به همین شکل است.
 اما در Angular داستان به شکل دیگری اتفاق می‌افتد؛ Angular قالب HTML و داده‌ها را به صورت جداگانه از سرور دریافت می‌کند و در مرورگر کاربر آن‌ها را سرهم و مونتاژ می‌کند. بدیهی است که در اینجا قالب، یک فایل HTML ساده و داده‌ها می‌تواند به فرم JSON باشد. در نتیجه کار سرور دیگر فراهم کردن قالب و داده‌ها برای کلاینت است و بقیه‌ی ماجرا در سمت کلاینت رخ می‌دهد.
خیلی خوب، مزیت این کار نسبت به روش‌های معمول چیست؟ اگر اجازه بدهید این را با یک مثال شرح دهم:
در بسیاری از سایت ها، ویژگی ای به نام اسکرول نامحدود وجود دارد. در همین سایت نیز دکمه ای با عنوان بیشتر در انتهای لیستی از مطالب، برای مشاهده‌ی ادامه‌ی لیست قرار گرفته است. سعی کنید پس از فشردن دکمه‌ی بیشتر، داده‌های دریافتی از سرور را مشاهده کنید. پس از انجام این کار مشاهده خواهید کرد که پاسخ سرور HTML خام است. اگر تعداد 10 پست از سرور درخواست شود، 10 بار محتوای HTML تکراری نیز دریافت خواهد شد؛ در صورتی که ساختار HTML یک پست هم کفایت می‌کرد و تنها داده‌ها در آن 10 پست متفاوتند؛ چرا که قالب کار مشخص است و فقط به ازای هر پست باید آن داده‌ها در جای مناسب خود قرار داد.
دیدگاه‌های یک پست هم به خوبی با Angular قابل پیاده سازی است. قالب HTML یک دیدگاه را برای angular تعریف کرده و داده‌های مناسب که احتمالا JSON خام است از سرور دریافت شود. نتیجه‌ی این کار هم صرفه جوی در پهنای باند مصرفی و افزایش فوق العاده‌ی سرعت است، همچنین در صورت نیاز می‌توان داده‌ها و قالب‌ها راکش کرد تا مراجعه به سرور به حداقل برسد.
 چگونگی انجام این کار در AngularJs به صورت خلاصه به این صورت است که در angular یک directive به نام ng-repeat تعریف شده است که مانند یک حلقه‌ی foreach برای HTML عمل می‌کند. شما در داخل حلقه، قالب را مشخص می‌کنید و به ازای تعداد داده‌ها، آن حلقه تکرار می‌شود و بر روی داده‌ها پیمایش صورت می‌گیرد.
البته این مثال‌ها فقط دو نمونه از کاربرد این ویژگی در دنیای واقعی بود و مطمئن باشید که در مقالات آینده مثال‌های زیادی از این موضوع را پیاده‌سازی خواهیم کرد.
بهتر است که دیگر خیلی وارد جزئیات نشویم و اولین برنامه‌ی خود را به کمک angularjs بنویسیم. این برنامه، همان برنامه‌ی معروف Hello ,World است؛ اما در این برنامه به جای نوشتن یک Hello, World ساده در صفحه، آن را با ساختار angularjs پیاده‌سازی می‌کنیم.
در داخل ویژوال استادیو یک فایل HTML ساده ایجاد کنید و کد‌های زیر را داخل آن بنویسید.
<!DOCTYPE html>
<html ng-app>
<head>
    <title>Sample 1</title>
</head>
<body>
    <div ng-controller="GreetingController">
        <p>{{greeting.text}}, World!</p>
    </div>

    <script src="../Scripts/angular.js"></script>
    <script>
        function GreetingController($scope) {
            $scope.greeting = {
                text: "Hello"
            };
        }
    </script>
</body>
</html>
سپس فایل فوق را در مرورگر اجرا کنید. بله؛ عبارت Hello, World را مشاهده خواهید کرد. یک بار دیگر خاصیت text  را در scope.greeting$ به hi تغییر بدهید و باز هم نتیجه را مشاهده کنید.
این مثال در نگاه اول خیلی ساده است، اما دنیایی از مفاهیم angular را در بر دارد. شما خواص جدیدی را برای عناصر HTML مشاهده می‌کنید: ng-app، ng-controller، آکلود‌ها و عبارت درون آن و متغیر scope$ به عنوان پارامتر.
حال بیایید ویژگی‌ها و مفاهیم جالب کدهای نوشته شده را بررسی کنیم؛ چرا که فرصت برای بررسی ng-app و بقیه‌ی موارد نا آشنا زیاد است:

- هیچ id و یا class برای عناصر html در نظر گرفته نشده تا با استفاده از آنها، رویدادی را برای عناصر مورد نظر مشخص کنیم.

- وقتی در GreetingController مقدار greeting.text را مشخص کرده ایم، باز هم هیچ رویدادی را صدا نزده و یا مشخص نکرده ایم.

- GreetingController یک کلاس ساده‌ی جاوا اسکریپت (POJO) است و از هیچ چیزی که توسط angular فراهم شده باشد، ارث بری نکرده است.

- اگر به متد سازنده‌ی کلاس GreetingController دقت کنید، متغیر scope$ به عنوان پارامتر تعریف شده است. نکته‌ی جالب این است که ما هیچ گاه به صورت دستی سازنده‌ی کلاس GreetingController را صدا نزده ایم و حتی درون سازنده هم scope$ را ایجاد نکرده ایم؛ پس چگونه توانسته ایم خاصیتی را به آن نسبت داده و برنامه به خوبی کار کند. بهتر است برای پاسخ به این سوال خودتان دست به کار شوید؛ ابتدا نام متغیر scope$ را به نام دلخواه دیگری تغییر دهید و سپس برنامه را اجرا کنید. بله برنامه دیگر کار نمی‌کند. دلیل آن چیست؟ همان طور که گفتم Angular دارای یک سیستم تزریق وابستگی توکار است و در اینجا نیز scope$ به عنوان وابستگی در سازنده‌ی  این کلاس مشخص شده است تا نمونه‌ی مناسب آن توسط angular به کلاس GreetingController ما تزریق شود؛ اما چرا به نام آن یعنی scope$ حساس است؟ به این دلیل که زبان جاوا اسکریپت یک زبان پویا است و نوع در آن مطرح نیست؛ angular مجبور است که از نام پارامترها برای تزریق وابستگی استفاده می‌کند. در مقالات آینده چگونگی عملکرد سیستم تزریق وابستگی angular را به تشریح بیان می‌کنم.

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

 تا همین جا فکر کنم کاملا برای شما مشخص شده است که ساختار فریمورک Angular با تمامی کتاب خانه‌های مشابه متفاوت است و با ساختاری کاملا اصولی و حساب شده طرف هستیم. همچنین در مقالات آینده توجه شما را به قابلیت‌هایی بسیار قدرتمند‌تر جلب خواهم کرد.
   
MVC ،MVP ، MVVM و یا MVW
 در بخش اول این مقاله، الگوی طراحی پیشنهادی فریمورک Angular را MVC بیان کرده‌ام؛ اما همان طور که گفته بودم AngularJS از انقیاد داده دوطرفه (Two Way Data Binding) نیز به خوبی پشتیبانی می‌کند و به همین دلیل عده ای آن را یک MVVM Framework تلقی می‌کنند. حتی داستان به همین جا ختم نمی‌شود و عده ای آن را به چشم MVP  Framework  نیز نگاه می‌کنند. در ابتدا سایت رسمی AngularJS الگوی طراحی مورد استفاده را MVC بیان می‌نمود ولی در این چند وقت اخیر عنوانش را به MVW Framework تغییر داده است.
MVW مخفف عبارت Model View Whatever هست و کاملا مفهومش مشخص است. Model و View بخش‌های مشترک تمام الگو‌ها بودند و تنها بخش سوم مورد اختلاف توسعه دهندگان بود؛ در نتیجه انتخاب آن را بر عهده‌ی استفاده کننده قرار داده اند و تمام امکانات لازم برای پیاده‌سازی این الگو‌های طراحی را فراهم کرده اند. در طی این مقالات صرف نظر از تمام الگوهای طراحی فوق، من بیشتر بر روی MVC تمرکز خواهم کرد.
الگوی طراحی MVC در سال 1970 به عنوان بخشی از زبان برنامه نویسی Smalltalk معرفی شد و از همان ابتدا به سرعت محبوبیت زیادی در بین محیط‌های توسعه‌ی دسکتاپی از قبیل ++C و Java  که رابط کاربری گرافیکی به نوعی در آن‌ها دخیل است، پیدا کرد.
تفکر MVC این را بیان می‌کند که باید جداسازی واضح و روشنی بین مدیریت داده‌ها (Model)، منطق برنامه (Controller) و نمایش داده‌ها به کاربر (View) وجود داشته باشد و در اصل هدفش جداسازی اجزای رابط کاربری به بخش هایی مجزا است.
     
شاید این سوال برای شما پیش بیاید که چرا باید چنین الگویی را در برنامه‌ها پیاده کرد؟
احتمالا تا کنون از بین برنامه هایی که نوشته اید، رابط کاربری بیشتر از آن‌ها را نیز خودتان مجبور شده اید طراحی کنید؛ به این دلیل که برنامه‌ی شما بدون رابط کاربری قابل اجرا شدن نبوده است. اجرای برنامه‌ی شما منوط به وجود تعدادی دکمه و textbox و ... بوده است و به قولی منطق برنامه به رابط گرافیکی گره خورده بوده است. پس می‌توان گفت که پیاده‌سازی الگوی طراحی وقتی ضرورت پیدا می‌کند که رابط گرافیکی، قسمتی از برنامه‌ی شما را تشکیل دهد.
آیا با وجود زبان‌های طراحی ساده ای مثل HTML و XAML و ... احتیاجی است که برنامه نویس وقت خود را صرف طراحی رابط کاربری کند؟ مسلما خیر، چون دیگر با این امکانات یک طراح هم از پس این کار به خوبی و یا حتی بهتر بر می‌آید. دیگر وظیفه‌ی برنامه نویس نوشتن کد‌های مربوط به منطق برنامه است. کدهایی که بدون UI هم قابل تست شدن باشد و به راحتی بتوان برای آن‌ها آزمون‌های واحد نوشت. برنامه نویس باید این را در نظر بگیرد که UI وجود ندارد و حتی ممکن است هیچ گاه هم ایجاد نشود و  این کد‌ها تبدیل به یک کتابخانه شود و مورد استفاده قرار بگیرد تا در یک برنامه با رابط کاربری گرافیکی.
در MVC، روال عمومی کار به این شکل است که View داده‌ها را از Model دریافت می‌کند و به کاربر نمایش می‌دهد. وقتی که کاربر با کلیک کردن و تایپ کردن با برنامه ارتباط برقرار می‌نماید، Controller به این درخواست‌ها پاسخ می‌دهد و داده‌های موجود در Model را به روز رسانی می‌کند. در نهایت هم Model  تغییرات خود را به View منعکس می‌کند تا View آن چه را که پیش از آن نمایش می‌داده است، تغییر دهد و View را از تغییرات رخ داده آگاه نماید.
اما در برنامه‌های Angular قضیه از چه قرار است؟ در Angular، قالب HTML  یا اگر بخواهم دقیق‌تر بگویم (Document Object Model(DOM معادل View است؛ کلاس‌های جاوا اسکریپتی نقش Controller را دارند؛ و خواص اشیای جاوا اسکریپتی و یا حتی خود اشیا نقش Model را بر عهده دارند.

ساختار بخشیدن به برنامه با استفاده MVC یک مزیت مهم دیگر نیز دارد: ساختار کار کاملا مشخص است و هر کسی نمی‌تواند به صورت سلیقه ای آن را پیاده سازی کند. با یک مثال این موضوع را تشریح می‌کنم: اگر کسی پروژه‌ی بنده را که با ASP.NET MVC نوشتم، بررسی کند، اصلا احساس غریبی نمی‌کند و به راحتی می‌تواند آن را توسعه دهد. دلیل این موضوع این است که ASP.NET MVC یک ساختار مشخص را به توسعه دهندگان اجبار کرده است و هر کسی این ساختار را رعایت کند و با آن آشنا باشد، به راحتی می‌تواند با آن کار کند. توسعه دهنده می‌داندکه من Model را کجا تعریف کرده ام، Controller مربوط به هر View کجاست و در کدام قسمت با پایگاه داده ارتباط برقرار کرده‌ام؛ اما در مورد کد‌های JavaScript و سمت کلاینت چه طور؟ توسعه دهنده ای که می‌خواهد کار من را ادامه بدهد دچار وحشت می‌شود! الگوی مشخصی وجود ندارد؛ معلوم نیست که کجا DOM را دستکاری کرده‌ام، در کدام قسمت با سرور ارتباط برقرار شده و... به قول معروف با یک اسپاگتی کد تمام عیار طرف می‌شود. AngularJS این مشکل را حل نموده و ساختار خاصی را سعی کرده به شما دیکته کند و تا حد ممکن دست شما را نیز باز گذاشته است. جدا از همه‌ی اینها، برنامه‌های مبتنی بر Angular به راحتی نگه داری  و تست می‌شوند و بدون هیچ دغدغه ای آن‌ها را می‌توان توسعه داد.
   
در حاشیه
شاید در هنگام دریافت فایل angularjs و افزودن آن به پروژه‌ی خود شروع به اعتراض کرده اید که نسخه‌ی فشرده شده‌ی آن 87 کیلو بایت حجم دارد در صورتی که این حجم در کتابخانه‌های مشابه ممکن است حتی به 10 کیلوبایت هم نرسد. اگر دقت کرده باشید من در بیان AngularJS از واژه‌ی کتاب خانه استفاده نکردم و فقط از واژه‌ی فریمورک استفاده کردم. بله نمی‌شود angular را با کتاب خانه هایی مقایسه کرد که مهمترین ویژگی خود را Data Binding می‌دانند. AngularJS یک بستر کاری قدرتمند است که تمام راه حل‌های موجود را در خود جمع کرده است. تیم توسعه دهنده‌ی آن هم هیچ ادعایی ندارد و می‌گویند که ما هیچ چیزی را خودمان اختراع نکرده ایم، بلکه راه حل‌های عالی را برگزیدیم، تفکرهای خوب را ارتقا بخشیده و در فریمورک خود استفاده کردیم و حتی از ایده‌های خوب دیگر کتاب خانه‌ها هم استفاده کرده ایم. بنابر این نباید به حجم آن در مقابل توانایی هایی که دارد اعتراض کرد.
   
همچنین به نظر می‌آید که AngularJS یک فریمورک پیچیده است. ولی من همیشه بین پیچیده و پیچیده شده تفاوت قائل می‌شوم. به نظر شخصی خودم Angular به دلیل مشکلات خاص و پیچیده ای که حل می‌کند پیچیده است و پیچیده شده نیست. اگر آن را پیچیده شده حس می‌کنید، تنها دلیلش، نحوه‌ی آموزش دادن بنده است، تمام سعی خود را می‌کنم که مفاهیم را تا حد ممکن ساده بیان کنم و امیدوارم در آینده که با مثال‌های بیشتری روبرو می‌شوید، این مفاهیم به کارتان بیاید.
     
در مقاله‌ی بعدی به مفاهیم انقیاد داده، تزریق وابستگی، هدایت گر‌ها (Directives) و سرویس‌ها در AngularJS می‌پردازم.
  
مطالب
روش صحیح مقایسه دو عدد اعشاری با هم
استفاده از عملگر == برای مقایسه اعداد اعشاری عموما جواب نخواهد داد و کار صحیحی نیست. از این جهت که اعداد، اساسا به صورت یک سری صفر و یک ذخیره شده و امکان ذخیره سازی کامل و دقیق قسمت اعشاری وجود ندارد.
برای مثال نوع‌های double و float امکان ذخیره سازی دقیق عدد یک دهم را ندارند. عدد 1/10 به صورت ...0.000110011001100 ذخیره می‌شود (در حالت باینری) و مقایسه دقیق مقادیر ثابت 0.00011 یا 0.00011001100 با آن میسر نیست؛ چون دقت نهایی این اعداد متفاوت است.
در زبان #C، نوع double، مطابق استاندارد IEEE-754 تهیه شده‌است و تنها 15 رقم اعشار دقت دارد و ذخیره سازی اعداد اعشاری در آن، به یک گرد سازی نهایی ختم خواهد شد. بنابراین به دلیل وجود این rounding error طبیعی، امکان استفاده از عملگری مانند == جهت مقایسه‌ی اعداد اعشاری همیشه پاسخ صحیحی را به همراه نخواهد داشت.
برای نمونه مثال زیر را بررسی کنید:
double d1 = 12.14;
double d2 = 12.13;
double d3 = d1 - d2; // Should be 0.01
bool check = (d3 == 0.01); // should be true
که یک چنین خروجی را به همراه دارد (حاصل آن برخلاف تصور مساوی 0.01 نیست):


روشی که برای حل این مساله پیشنهاد شده‌است، تفریق دو عدد از هم و مقایسه‌ی نتیجه‌ی آن با epsilon است؛ بجای مقایسه با صفر:
        public static bool ApproxEquals(double d1, double d2)
        {
            const double epsilon = 2.2204460492503131E-16;

            if (d1 == d2)
                return true;

            double tolerance = ((Math.Abs(d1) + Math.Abs(d2)) + 10.0) * epsilon;
            double difference = d1 - d2;

            return (-tolerance < difference && tolerance > difference);
        }
متد فوق را در فایل MathUtils کتابخانه‌ی JSON.NET می‌تواند مشاهده کنید.
با این خروجی:


در این حالت می‌توان نتیجه گرفت که d3 و 0.01 بسیار بسیار نزدیک به هم هستند؛ یا تقریبا مساوی.
پاسخ به بازخورد‌های پروژه‌ها
خطا هنگام باز شدن کلاینت در ویندوز 8.1
- آیا در کنار فایل اجرایی برنامه، فایلی به نام ErrorsLog.Log تشکیل می‌شود؟ اگر بله، چه خطایی در آن گزارش شده‌است.
- اگر فایل ErrorsLog.Log تشکیل نشده‌است، مراجعه کنید به این مسیر جهت مشاهده‌ی خطای لاگ شده در event viewer ویندوز:

 Computer management -> event viewer -> windows logs -> application  
مطالب
معرفی یک ابزار گزارشگیری رایگان مخصوص WPF

تا صحبت از گزارشگیری به میان بیاید احتمالا معرفی ابزارهای تجاری مانند Reporting services ، کریستال ریپورت، stimulsoft.com ، fast-report.com و امثال آن درصدر لیست توصیه کنندگان و مشاوران قرار خواهند داشت. اما خوب برای ایجاد یک گزارشگیری ساده حتما نیازی نیست تا به این نوع ابزارهای تجاری مراجعه کرد. ابزار رایگان و سورس باز جالبی هم در این باره جهت پروژه‌های WPF در دسترس است:



در ادامه در طی یک مثال قصد داریم از این کتابخانه استفاده کنیم:

1) تنظیم وابستگی‌ها
پس از دریافت کتابخانه فوق، ارجاعات زیر باید به پروژه شما اضافه شوند:
CodeReason.Reports.dll (از پروژه فوق) و ReachFramework.dll (جزو اسمبلی‌های استاندارد دات نت است)

2) تهیه منبع داده‌ گزارش
کتابخانه‌ی فوق به صورت پیش فرض با DataTable‌ کار می‌کند. بنابراین کوئری‌های شما یا باید خروجی DataTable داشته باشد یا باید از یک سری extension methods برای تبدیل IEnumerable به DataTable استفاده کرد (در پروژه پیوست شده در پایان مطلب، این موارد موجود است).
برای مثال فرض کنید می‌خواهیم رکوردهایی را از نوع کلاس Product زیر در گزارش نمایش دهیم:

namespace WpfRptTests.Model
{
public class Product
{
public string Name { set; get; }
public int Price { set; get; }
}
}
3) تعریف گزارش
الف) اضافه کردن فایل تشکیل دهنده ساختار و ظاهر گزارش
گزارش‌‌های این کتابخانه مبتنی است بر اشیاء FlowDocument استاندارد WPF . بنابراین از منوی پروژه گزینه‌ی Add new item در قسمت WPF آن یک FlowDocument جدید را به پروژه اضافه کنید ( باید دقت داشت که Build action این فایل باید به Content تنظیم گردد). ساختار ابتدایی این FlowDocument به صورت زیر خواهد بود که به آن FlowDirection و FontFamily مناسب جهت گزارشات فارسی اضافه شده است. همچنین فضای نام مربوط به کتابخانه‌ی گزارشگیری CodeReason.Reports نیز باید اضافه گردد.
<FlowDocument xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
FlowDirection="RightToLeft" FontFamily="Tahoma"
xmlns:xrd="clr-namespace:CodeReason.Reports.Document;assembly=CodeReason.Reports"
PageHeight="29.7cm" PageWidth="21cm" ColumnWidth="21cm">

</FlowDocument>

مواردی که در ادامه ذکر خواهند شد محتوای این گزارش را تشکیل می‌دهند:
ب) مشخص سازی خواص گزارش

<xrd:ReportProperties>
<xrd:ReportProperties.ReportName>SimpleReport</xrd:ReportProperties.ReportName>
<xrd:ReportProperties.ReportTitle>گزارش از محصولات</xrd:ReportProperties.ReportTitle>
</xrd:ReportProperties>
در اینجا ReportName و ReportTitle باید مقدار دهی شوند (دو dependency property که در کتابخانه‌ی CodeReason.Reports تعریف شده‌اند)

ج) مشخص سازی Page Header و Page Footer
اگر می‌خواهید عباراتی در بالا و پایین تمام صفحات گزارش تکرار شوند می‌توان از SectionReportHeader و SectionReportFooter این کتابخانه به صورت زیر استفاده کرد:
    <xrd:SectionReportHeader PageHeaderHeight="2" Padding="10,10,10,0" FontSize="12">
<Table CellSpacing="0">
<Table.Columns>
<TableColumn Width="*" />
<TableColumn Width="*" />
</Table.Columns>
<TableRowGroup>
<TableRow>
<TableCell>
<Paragraph>
<xrd:InlineContextValue PropertyName="ReportTitle" />
</Paragraph>
</TableCell>
<TableCell>
<Paragraph TextAlignment="Right">
<xrd:InlineDocumentValue PropertyName="PrintDate" Format="dd.MM.yyyy HH:mm:ss" />
</Paragraph>
</TableCell>
</TableRow>
</TableRowGroup>
</Table>
</xrd:SectionReportHeader>

<xrd:SectionReportFooter PageFooterHeight="2" Padding="10,0,10,10" FontSize="12">
<Table CellSpacing="0">
<Table.Columns>
<TableColumn Width="*" />
<TableColumn Width="*" />
</Table.Columns>
<TableRowGroup>
<TableRow>
<TableCell>
<Paragraph>
نام کاربر:
<xrd:InlineDocumentValue PropertyName="RptBy" Format="dd.MM.yyyy HH:mm:ss" />
</Paragraph>
</TableCell>
<TableCell>
<Paragraph TextAlignment="Right">
صفحه
<xrd:InlineContextValue PropertyName="PageNumber" FontWeight="Bold" /> از
<xrd:InlineContextValue PropertyName="PageCount" FontWeight="Bold" />
</Paragraph>
</TableCell>
</TableRow>
</TableRowGroup>
</Table>
</xrd:SectionReportFooter>

دو نکته در اینجا حائز اهمیت هستند: xrd:InlineDocumentValue و xrd:InlineContextValue
InlineDocumentValue را می‌توان در کد‌های برنامه به صورت سفارشی اضافه کرد. بنابراین هر جایی که نیاز بود مقدار ثابتی از طریق کد نویسی به گزارش تزریق و اضافه شود می‌توان از InlineDocumentValue استفاده کرد. برای مثال در کدهای ViewModel برنامه که در ادامه ذکر خواهد شد دو مقدار PrintDate و RptBy به صورت زیر تعریف و مقدار دهی شده‌اند:
data.ReportDocumentValues.Add("PrintDate", DateTime.Now);
data.ReportDocumentValues.Add("RptBy", "وحید");
برای مشاهده مقادیر مجاز مربوط به InlineContextValue به فایل ReportContextValueType.cs سورس کتابخانه مراجعه کنید که شامل PageNumber, PageCount, ReportName, ReportTitle است و توسط CodeReason.Reports به صورت پویا تنظیم خواهد شد.

د) مشخص سازی ساختار تولیدی گزارش

<Section Padding="80,10,40,10" FontSize="12">
<Paragraph FontSize="24" TextAlignment="Center" FontWeight="Bold">
<xrd:InlineContextValue PropertyName="ReportTitle" />
</Paragraph>
<Paragraph TextAlignment="Center">
گزارش از لیست محصولات در تاریخ:
<xrd:InlineDocumentValue PropertyName="PrintDate" Format="dd.MM.yyyy HH:mm:ss" />
توسط:
<xrd:InlineDocumentValue PropertyName="RptBy" Format="dd.MM.yyyy HH:mm:ss" />
</Paragraph>
<xrd:SectionDataGroup DataGroupName="ItemList">
<Table CellSpacing="0" BorderBrush="Black" BorderThickness="0.02cm">
<Table.Resources>
<!-- Style for header/footer rows. -->
<Style x:Key="headerFooterRowStyle" TargetType="{x:Type TableRowGroup}">
<Setter Property="FontWeight" Value="DemiBold"/>
<Setter Property="FontSize" Value="16"/>
<Setter Property="Background" Value="LightGray"/>
</Style>

<!-- Style for data rows. -->
<Style x:Key="dataRowStyle" TargetType="{x:Type TableRowGroup}">
<Setter Property="FontSize" Value="12"/>
</Style>

<!-- Style for data cells. -->
<Style TargetType="{x:Type TableCell}">
<Setter Property="Padding" Value="0.1cm"/>
<Setter Property="BorderBrush" Value="Black"/>
<Setter Property="BorderThickness" Value="0.01cm"/>
</Style>
</Table.Resources>

<Table.Columns>
<TableColumn Width="0.8*" />
<TableColumn Width="0.2*" />
</Table.Columns>
<TableRowGroup Style="{StaticResource headerFooterRowStyle}">
<TableRow>
<TableCell>
<Paragraph TextAlignment="Center">
<Bold>نام محصول</Bold>
</Paragraph>
</TableCell>
<TableCell>
<Paragraph TextAlignment="Center">
<Bold>قیمت</Bold>
</Paragraph>
</TableCell>
</TableRow>
</TableRowGroup>

<TableRowGroup Style="{StaticResource dataRowStyle}">
<xrd:TableRowForDataTable TableName="Product">
<TableCell>
<Paragraph>
<xrd:InlineTableCellValue PropertyName="Name" />
</Paragraph>
</TableCell>
<TableCell>
<Paragraph TextAlignment="Center">
<xrd:InlineTableCellValue PropertyName="Price" AggregateGroup="Group1" />
</Paragraph>
</TableCell>
</xrd:TableRowForDataTable>
</TableRowGroup>

<TableRowGroup Style="{StaticResource headerFooterRowStyle}">
<TableRow>
<TableCell>
<Paragraph TextAlignment="Right">
<Bold>جمع کل</Bold>
</Paragraph>
</TableCell>
<TableCell>
<Paragraph TextAlignment="Center">
<Bold>
<xrd:InlineAggregateValue AggregateGroup="Group1"
AggregateValueType="Sum"
EmptyValue="0"
FontWeight="Bold" />
</Bold>
</Paragraph>
</TableCell>
</TableRow>
</TableRowGroup>

</Table>

<Paragraph TextAlignment="Center" Margin="5">
در این گزارش
<xrd:InlineAggregateValue AggregateGroup="Group1"
AggregateValueType="Count"
EmptyValue="هیچ"
FontWeight="Bold" /> محصول با جمع کل قیمت
<xrd:InlineAggregateValue AggregateGroup="Group1"
AggregateValueType="Sum"
EmptyValue="0"
FontWeight="Bold" /> وجود دارند.
</Paragraph>
</xrd:SectionDataGroup>
</Section>
برای اینکه بتوان این قسمت‌ها را بهتر توضیح داد، نیاز است تا تصاویر مربوط به خروجی این گزارش نیز ارائه شوند:




در ابتدا توسط دو پاراگراف، عنوان گزارش و یک سطر زیر آن نمایش داده شده‌اند. بدیهی است هر نوع شیء و فرمت مجاز در FlowDocument را می‌توان در این قسمت نیز قرار داد.
سپس یک SectionDataGroup جهت نمایش لیست آیتم‌ها اضافه شده و داخل آن یک جدول که بیانگر ساختار جدول نمایش رکوردهای گزارش می‌باشد، ایجاد گردیده است.
سه TableRowGroup در این جدول تعریف شده‌اند.
TableRowGroup های اولی و آخری دو سطر اول و آخر جدول گزارش را مشخص می‌کنند (سطر عناوین ستون‌ها در ابتدا و سطر جمع کل در پایان گزارش)
از TableRowGroup میانی برای نمایش رکوردهای مرتبط با نام جدول مورد گزارشگیری استفاده شده است. توسط TableRowForDataTable آن نام این جدول باید مشخص شود که در اینجا همان نام کلاس مدل برنامه است. به کمک InlineTableCellValue، خاصیت‌هایی از این کلاس را که نیاز است در گزارش حضور داشته باشند، ذکر خواهیم کرد. نکته‌ی مهم آن AggregateGroup ذکر شده است. توسط آن می‌توان اعمال جمع، محاسبه تعداد، حداقل و حداکثر و امثال آن‌را که در فایل InlineAggregateValue.cs سورس کتابخانه ذکر شده‌اند، به فیلدهای مورد نظر اعمال کرد. برای مثال می‌خواهیم جمع کل قیمت را در پایان گزارش نمایش دهیم به همین جهت نیاز بود تا یک AggregateGroup را برای این منظور تعریف کنیم.
از این AggregateGroup در سومین TableRowGroup تعریف شده به کمک xrd:InlineAggregateValue جهت نمایش جمع نهایی استفاده شده است.
همچنین اگر نیاز بود در پایان گزارش اطلاعات بیشتری نیز نمایش داده شود به سادگی می‌توان با تعریف یک پاراگراف جدید، اطلاعات مورد نظر را نمایش داد.

4) نمایش گزارش تهیه شده
نمایش این گزارش بسیار ساده است. View برنامه به صورت زیر خواهد بود:
<Window x:Class="WpfRptTests.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:c="clr-namespace:CodeReason.Reports.Controls;assembly=CodeReason.Reports"
xmlns:vm="clr-namespace:WpfRptTests.ViewModel"
Title="MainWindow" WindowState="Maximized" Height="350" Width="525">
<Window.Resources>
<vm:ProductViewModel x:Key="vmProductViewModel" />
</Window.Resources>
<Grid DataContext="{Binding Source={StaticResource vmProductViewModel}}">
<c:BusyDecorator IsBusyIndicatorHidden="{Binding RptGuiModel.IsBusyIndicatorHidden}">
<DocumentViewer Document="{Binding RptGuiModel.Document}" />
</c:BusyDecorator>
</Grid>
</Window>

تعریف ابتدایی RptGuiModel به صورت زیر است (جهت مشخص سازی مقادیر IsBusyIndicatorHidden و Document در حین بایندینگ اطلاعات):

using System.ComponentModel;
using System.Windows.Documents;

namespace WpfRptTests.Model
{
public class RptGuiModel
{
public IDocumentPaginatorSource Document { get; set; }
public bool IsBusyIndicatorHidden { get; set; }
}
}
و این View اطلاعات خود را از ViewModel زیر دریافت خواهد نمود:
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using CodeReason.Reports;
using WpfRptTests.Helper;
using WpfRptTests.Model;

namespace WpfRptTests.ViewModel
{
public class ProductViewModel
{
#region Constructors (1)

public ProductViewModel()
{
RptGuiModel = new RptGuiModel();
if (Stat.IsInDesignMode) return;
//انجام عملیات نمایش گزارش در یک ترد دیگر جهت قفل نشدن ترد اصلی برنامه
showReportAsync();
}

#endregion Constructors

#region Properties (1)

public RptGuiModel RptGuiModel { set; get; }

#endregion Properties

#region Methods (3)

// Private Methods (3)

private static List<Product> getProducts()
{
var products = new List<Product>();
for (var i = 0; i < 100; i++)
products.Add(new Product { Name = string.Format("Product{0}", i), Price = i });

return products;
}

private void showReport()
{
try
{
//Show BusyIndicator
RptGuiModel.IsBusyIndicatorHidden = false;

var reportDocument =
new ReportDocument
{
XamlData = File.ReadAllText(@"Report\SimpleReport.xaml"),
XamlImagePath = Path.Combine(Environment.CurrentDirectory, @"Report\")
};

var data = new ReportData();

// تعریف متغیرهای دلخواه و مقدار دهی آن‌ها
data.ReportDocumentValues.Add("PrintDate", DateTime.Now);
data.ReportDocumentValues.Add("RptBy", "وحید");

// استفاده از یک سری اطلاعات آزمایشی به عنوان منبع داده
data.DataTables.Add(getProducts().ToDataTable());

var xps = reportDocument.CreateXpsDocument(data);
//انقیاد آن به صورت غیر همزمان در ترد اصلی برنامه
DispatcherHelper.DispatchAction(
() => RptGuiModel.Document = xps.GetFixedDocumentSequence()
);
}
catch (Exception ex)
{
//وجود این مورد ضروری است زیرا بروز استثناء در یک ترد به معنای خاتمه آنی برنامه است
//todo: log errors
}
finally
{
//Hide BusyIndicator
RptGuiModel.IsBusyIndicatorHidden = true;
}
}

private void showReportAsync()
{
var thread = new Thread(showReport);
thread.SetApartmentState(ApartmentState.STA); //for DocumentViewer
thread.Start();
}

#endregion Methods
}
}

توضیحات:
برای اینکه حین نمایش گزارش، ترد اصلی برنامه قفل نشود، از ترد استفاده شد و استفاده ترد به همراه DocumentViewer کمی نکته دار است:
- ترد تعریف شده باید از نوع STA باشد که در متد showReportAsync مشخص شده است.
- حین بایندیگ Document تولید شده توسط کتابخانه‌ی گزارشگیری به خاصیت Document کنترل، حتما باید کل عملیات در ترد اصلی برنامه صورت گیرد که سورس کلاس DispatcherHelper را در فایل پیوست خواهید یافت.

کل عملیات این ViewModel در متد showReport رخ می‌دهد، ابتدا فایل گزارش بارگذاری می‌شود، سپس متغیرهای سفارشی مورد نظر تعریف و مقدار دهی خواهند شد. در ادامه یک سری داده آزمایشی تولید و به DataTables گزارش ساز اضافه می‌شوند. در پایان XPS Document متناظر آن تولید شده و به کنترل نمایشی برنامه بایند خواهد شد.

دریافت سورس این مثال

نظرات مطالب
ASP.NET MVC #11
من از ویندوز 10 استفاده می‌کنم. زمانی که تاریخ ویندوز بر روی میلادی تنظیم شده رشته تاریخ فارسی به معادل میلادی بدون مشکل تبدیل می‌شود. اما وقتی تاریخ ویندوز در حالت شمسی هست بعد از عملیات تعریف شده در بایندر باز هم تاریخ بصورت شمسی هست .


بعد از عملیات

اگر ممکنه راهنمائی بفرمائید.

مطالب
معرفی Bit Platform
پلتفرم Bit یک پروژه تماما Open Source در GitHub میباشد که هدف آن تسهیل توسعه نرم افزار با کیفیت و پرفرمنس بالا بر بستر ASP.NET Core و زبان #C است که با آن بتوان فقط با یکبار کدنویسی و با کمک استانداردهای وب همچون HTML / CSS و Web Assembly ، خروجی‌هایی چون Android / iOS / Windows را با دسترسی کامل به امکانات سیستم‌عامل به همراه برنامه‌های تحت وب SPA و PWA (با یا بدون Pre-Rendering) گرفت.
پلتفرم Bit تا به اینجا از دو قسمت Bit Blazor Components (شامل بیش از ۳۰ کمپوننت کاربردی، کم حجم و High Performance مانند Tree / Multi Select / Data Grid / Date Picker / Color Picker و...) به همراه Bit Project Templates (قالب پروژه‌های حاوی امکانات پر استفاده) تشکیل شده است.
برخی مواردی که در رابطه با آنها صحبت شد، ممکن است برای شما آشنا نباشند، بنابراین قبل از بررسی مفصل‌‌تر Bit Platform، نگاهی به آن می‌اندازیم:


وب اسمبلی چیست؟

برای درک بهتر وب اسمبلی ابتدا باید بدانیم این تکنولوژی اصلا از کجا آمده و هدف آن چیست؟
میدانیم که مرورگر‌ها پروایدر صفحات وب هستند و ما برای اینکه بتوانیم اپلیکیشنی که ساختیم را در محیط وب برای کاربران به اشتراک بگذاریم باید از این مرورگر‌ها و زبان ارتباطی آن‌ها پیروی کنیم. این زبان‌های ارتباطی مشخصا سه چیز است: HTML CSS JavaScript
اما آیا راهی هست که بتوان بجای JavaScript از زبان‌های دیگر هم در سمت مرورگر استفاده کرد؟
وب اسمبلی یا همان WASM، آمده تا به ما اجازه دهد از هر زبانی که خروجی به Web Assembly دارد، برای تعاملات UI استفاده کنیم. یعنی با زبان هایی مثل #C / C++ / C و... میتوان کدی نوشت که مرورگر آن را اجرا کند. این یک تحول بزرگ است که امروزه تمامی مرورگرها (به جز مرورگرهایی که از دور خارج شده اند) از آن پشتیانی می‌کنند چرا که Web Assembly به یکی از اجزای استاندارد وب تبدیل شده است.
اطلاعات بیشتر در رابطه با وب اسمبلی را میتوانید از این مقاله بخوانید. 


تعریف SPA و PWA:
SPA: Single Page Application
PWA: Progressive Web Application
در گذشته برای رندر کردن صفحات وب با عوض شدن URL یا درخواست کاربر برای دریافت اطلاعات جدید از سرور و نمایش آن ، صفحه مرورگر ملزم به رفرش شدن مجدد و از سر گیری کل فرایند تولید HTML میشد. طبیعتا این تکرار برای کاربر هنگام استفاده از اپلیکیشن خیلی خوشایند نبود چرا که هربار میبایست زمانی بیشتر صرف تولید مجدد صفحات را منتظر میماند. اما در مقابل آن Single Page Application (SPA)‌‌ها این پروسه را تغیر داد.
در رویکرد SPA، کل CSS , HTML و JS ای که برای اجرای هر صفحه ای از اپلیکیشن نیاز هست در همان لود اولیه برنامه توسط مرورگر دانلود خواهد شد. با این روش هنگام تغییر URL صفحات مرورگر دیگر نیازی به لود دوباره اسکریپت‌ها ندارد. همچنین وقتی قرار است اطلاعات جدیدی از سرور آپدیت و نمایش داده شود این درخواست بصورت یک دستور Ajax به سرور ارسال شده و سرور با فرمت JSON اطلاعات درخواست شده را پاسخ میدهد. در نهایت مرورگر نیز اطلاعات برگشتی از سرور را مجدد جای گذاری میکند و کل این روند بدون هیچگونه رفرش دوباره صفحه انجام میشود.
در نتیجه‌ی این امر، کاربر تجربه خوشایند‌تری هنگام کار کردن با SPA‌ها خواهد داشت. اما همانقدر که این تجربه در طول زمان استفاده از برنامه بهبود یافت، لود اولیه اپلیکیشن را کند‌تر کرد، چرا که اپلیکیشن میبایست همه کدهای مورد نیاز خود برای صفحاتش را در همان ابتدا دانلود کند.(در ادامه با قابلیت Pre-Rendering این اشکال را تا حدود زیادی رفع میکنیم)
با استفاده از PWA میتوانید وبسایت‌های SPA را بصورت یک برنامه نصبی و تمام صفحه، با آیکن مجزایی همانند اپلیکیشن‌های دیگر در موبایل و دسکتاپ داشته باشید. همچنین وقتی از PWA استفاده میکنیم برنامه وب میتواند به صورت آفلاین نیز کار کند.
البته حتی در برنامه‌هایی که لازم نیست آفلاین کار کنند، در صورت قطعی ارتباط کاربر با شبکه، به جای دیدن دایناسور معروف، اینکه برنامه در هر حالتی باز شود و به صورتی کاربر پسند و قطعی نت به وی اعلام شود ایده خیلی بهتری است (": 


قابلیت Pre-Rendering:
هدف Pre-Rendering بهبود گشت گذار کاربر در سایت است. شیوه کارکرد آن به این صورت است که وقتی کاربر وارد وبسایت میشود، سرور در همان ابتدای کار و جلوتر از اتمام دانلود اسمبلی‌ها، فایلی حاوی HTML ، CSS‌های صفحه ای که کاربر درخواست کرده را در سمت سرور می‌سازد و به مرورگر ارسال میکند. در همین حین، اسمبلی‌ها نیز توسط مرورگر دانلود می‌شوند و برنامه از محتوای صرف خارج شده و حالت تعاملی می‌گیرد. اصطلاحا به این قابلیت Server-Side Rendering(SSR) نیز میگویند. در این حالت کاربر زودتر محتوایی از برنامه را میبیند و تجربه بهتری خواهد داشت. این امر در بررسی Search Engine‌‌ها و سئو وبسایت نیز تاثیر بسزایی دارد. 


نگاهی به Blazor:
تا اینجا هر آنچه که نیاز بود برای درک بهتر از Blazor بدانیم را فهمیدیم، اما خود Blazor چیست؟ در یک توضیح کوتاه، فریمورکی ارائه شده توسط مایکروسافت میباشد برای پیاده‌سازی UI و منطق برنامه‌ها شامل امکانات Routing ، Binding و...
بلیزر در انواع مختلفی ارائه شده که هرکدام کاربرد مشخصی دارد:

Blazor Server
در بلیزر سرور پردازش‌ها جهت تعامل UI درون سرور اجرا خواهد شد. برای مثال وقتی کاربر روی دکمه ای کلیک میکند و آن دکمه مقداری عددی را افزایش می‌دهد که از قضا متن یک Label به آن عدد وابسته است، رویداد کلیک شدن این دکمه توسط SignalR WebSocket به سرور ارسال شده و سرور تغیر متن Label را روی همان وب سوکت به کلاینت ارسال می‌کند.
با توجه به این که تعامل کاربر با صفحات برنامه، بسته به میزان کندی اینترنت کاربر، ممکن است کند شود و همچنین Blazor Server قابلیت PWA شدن ندارد و علاوه بر این بار پردازش زیادی روی سرور می‌اندازد (بسته به پیچیدگی پروژه) و در نهایت ممکن است در آن از Component هایی استفاده کنیم که چون در حالت Blazor Server پردازش سمت سرور بوده، متوجه حجم دانلود بالای آنها نشویم و کمی بعدتر که با Blazor Hybrid می‌خواهیم خروجی Android / iOS بگیریم متوجه حجم بالای آنها شویم، استفاده از Blazor Server را در Production توصیه نمی‌کنیم، ولی این حالت برای Debugging بهترین تجربه را ارائه می‌دهد، بالاخص با امکان Hot Reload و دیدن آنی تغییرات C# / HTML / CSS در ظاهر و رفتار برنامه موقع کدنویسی.

Blazor WebAssembly
در بلیزر وب اسمبلی منطق مثال قبلی که با C# .NET نوشته شده است، روی مرورگر و با کمک Web Assembly اجرا می‌شود و نیازی به ارتباط جاری با سرور توسط SignalR نیست. این باعث میشود که با بلیزر وب اسمبلی بتوان اپلیکیشن‌های PWA نیز نوشت.
یک برنامه Blazor Web Assembly می‌تواند چیزی در حدود دو الی سه مگ حجم داشته باشد که در دنیای امروزه حجم بالایی به حساب نمیاید، با این حال با کمک Pre Rendering و CDN می‌توان تجربه کاربر را تا حدود زیادی بهبود داد.
برای مثال سایت Component‌های Bit Platform جزو معدود دموهای Component‌های Blazor است که در حالت Blazor Web Assembly ارائه می‌شود و شما می‌توانید با باز کردن آن، تجربه حدودی کاربرانتان را در حین استفاده از Blazor Web Assembly ببینید. به علاوه، در dotnet 7 سرعت عملکرد Blazor Web Assembly بهبود قابل توجهی پیدا کرده است.

Blazor Hybrid
از Blazor Hybrid زمانی استفاده می‌کنیم که بخواهیم برنامه‌های موبایل را برای Android , iOS و برنامه‌های Desktop را برای ویندوز، با کمک HTML , CSS توسعه دهیم. نکته اصلی در Blazor Hybrid این است که اگر چه از Web View برای نمایش HTML / CSS استفاده شده، اما منطق سمت کلاینت برنامه که با C# .NET توسعه داده شده است، بیرون Web View و به صورت Native اجرا می‌شود که ضمن داشتن Performance بالا، به تمامی امکانات سیستم عامل دسترسی دارد. علاوه بر دسترسی به کل امکانات Android / iOS Sdk در همان C# .NET ، عمده کتابخانه‌های مطرح مانند Firebase، با ابزار Binding Library ارائه شده توسط مایکروسافت، دارای Wrapper قابل استفاده در C# .NET هستند و ساختن Wrapper برای هر کتابخانه Objective-C ، Kotlin، Java، Swift با این ابزار فراهم است.

اگر شما در حال حاضر فقط #HTML , CSS , C بدانید، اکنون با بلیزر میتوانید هر اپلیکیشنی که بخواهید توسعه دهید. از وبسایت‌های SPA گرفته تا اپ‌های موبایل Android ، IOS و برنامه‌های دسکتاپی برای Windows , Mac و بزودی نیز برای Linux
سری آموزش بلیزر را میتوانید از این لینک‌ها (1 ، 2) دنبال کنید. 


معرفی پکیج Bit Blazor UI:
پکیج Bit Blazor UI مجموعه ای از کامپوننت‌های مرسومی است که بر پایه بلیزر نوشته شده و به ما این امکان را میدهد تا المان‌های پیچیده ای مثل Date Picker , Grid , Color Picker , File Upload , DropDown و بسیاری از المان‌های دیگر را با شکلی بهینه، بدون نیاز به اینکه خودمان بخواهیم برای هر یک از اینها از نو کدنویسی کنیم، آن را در اختیار داشته باشیم.
عمده مشکل کامپوننت‌های ارائه شده برای بلیزر حجم نسبتا بالای آن است که باعث میشد بیشتر در مصارفی از قبیل ایجاد Admin Panel کارایی داشته باشد. اما این موضوع به خوبی در Bit Blazor UI مدیریت شده و در حال حاضر با بیش از 30 کامپوننت با حجم بسیار پایینی، چیزی حدود 200 کیلوبایت قابل نصب است. از لحاظ حجمی نسبت به رقبای خود برتری منحصر به فردی دارد که باعث میشود به راحتی حتی در اپلیکیشن‌های موبایل هم قابل استفاده باشد و کماکان پرفرمنس خوبی ارائه دهد.
این کامپوننت‌ها با ظاهر Fluent پیاده سازی شده و میتوانید لیست کامپوننت‌های بلیزر Bit را از این لینک ببینید. 


معرفی Bit TodoTemplate:
قبل از اینکه به معرفی Bit TodoTemplate بپردازیم باید بدانیم که اصلا پروژه‌های Template چه هستند. در واقع وقتی شما Visual Studio را باز میکنید و روی گزینه Create New Project کلیک میکنید با لیستی از پروژه‌های تمپلیت روبرو میشوید که هرکدام چهارچوب خاصی را با اهدافی متفاوت در اختیارتان قرار میدهند.
Bit Platform هم Project Template ای با نام TodoTemplate توسعه داده که میتوانید پروژه‌های خودتان را از روی آن بسازید، اما چه امکاناتی به ما میدهد؟
در یک جمله، هر آنچه تا به اینجا توضیح داده شد بصورت یکجا در TodoTemplate وجود دارد.
در واقع TodoTemplate چهارچوبی را فراهم کرده تا شما تنها با دانستن همین مفاهیمی که در این مقاله خواندید، از همان ابتدا امکاناتی چون صفحات SignUp، SignIn یا Email Confirmation و... را داشته باشید و در نهایت بتوانید تمامی خروجی‌های قابل تصور را بگیرید.
اما چگونه؟
در TodoTemplate همه این قابلیت‌ها تنها درون یک فایل و با کمترین تغیر ممکن نوع خروجی کدی که نوشته اید را مشخص میکند. این تنظیمات به شکل زیر است :
<BlazorMode> ... </BlazorMode>
<WebAppDeploymentType> ... </WebAppDeploymentType>
شما میتوانید با تنظیم <BlazorMode> بین انواع hosting model‌های بلیزر سوییچ کنید. مثلا برای زمانی که در محیط development هستید نوع بلیزر را Blazor Server قرار دهید تا از قابلیت‌های debugging بهتری برخوردار باشید ، وقتی میخواهید وبسایت تکمیل شده تان را بصورت SPA / PWA پابلیش کنید نوع بلیزر را به Blazor WebAssembly و برای پابلیش‌های Android ، IOS ، Windows ، Mac نوع بلیزر را به Blazor Hybrid تغیر دهید.
به علاوه، شما تنها با تغیر <WebAppDeploymentType> قادر خواهید بود بین SPA (پیش فرض)، SSR و PWA سوئیچ کنید.
قابلیت‌های TodoTemplate در اینجا به پایان نمیرسد و بخشی دیگر از این قابلیت‌ها به شرح زیر است :
  • وجود سیستم Exception handling در سرور و کلاینت (این موضوع به گونه ای بر اساس Best Practice‌‌ها پیاده سازی شده که اپلیکیشن را از بروز هر خطایی که بخواهد موجب Crash کردن برنامه شود ایزوله کرده)
  • وجود سیستم User Authentication بر اساس JWT که شما در همان ابتدا که از این تمپلیت پروژه جدیدی میسازید صفحات SignIn ، SignUp را خواهید داشت.
  • پکیج Bit Blazor UI که بالاتر درمورد آن صحبت کرده ایم از همان ابتدا در TodoTemplate نصب و تنظیم شده تا بتوانید به راحتی صفحات جدید با استفاده از آن بسازید.
  • کانفیگ استاندارد Swagger در سمت سرور.
  • ارسال ایمیل در روند SignUp.
  • وجود خاصیت AutoInject برای ساده‌سازی تزریق وابستگی ها.
  • و بسیاری موراد دیگر که در داکیومنت‌های پروژه میتوانید آنهارا ببینید.
با استفاده از TodoTemplate پروژه ای با نام Todo ساخته شده که میتوانید چندین مدل از خروجی‌های این پروژه را در لینکهای پایین ببینید و پرفرمنس آن را بررسی کنید.
توجه داشته باشید هدف TodoTemplate ارائه ساختار Clean Architecture نبوده ، بلکه هدف ارائه بیشترین امکانات با ساده‌ترین حالت کدنویسی ممکن بوده که قابل استفاده برای همگان باشد و شما میتوانید از هر پترنی که میخواهید براحتی در آن استفاده کنید.
پلتفرم Bit یک تیم توسعه کاملا فعال تشکیل داده که بطور مداوم در حال بررسی و آنالیز خطاهای احتمالی ، ایشو‌های ثبت شده و افزودن قابلیت‌های جدید میباشد که شما به محض استفاده از این محصولات میتوانید در صورت بروز هر اشکال فنی برای آن ایشو ثبت کنید تا تیم مربوطه آن را بررسی و در دستور کار قرار دهد. در ادامه پلتفرم Bit قصد دارد بزودی تمپلیت جدیدی با نام Admin Panel Template با امکاناتی مناسب برای Admin Panel مثل Dashboard و Chart و... با تمرکز بر Clean Architecture نیز ارائه کند. چیزی که مشخص است اوپن سورس بودن تقریبا %100 کارها میباشد از جلسات و گزارشات کاری گرفته تا جزئیات کارهایی که انجام میشود و مسیری که در آینده این پروژه طی خواهد کرد.
میتوانید اطلاعات بیشتر و مرحله به مرحله برای شروع استفاده از این ابزار‌ها را در منابعی که معرفی میشود دنبال کنید.

منابع:

مشارکت در پروژه:
  • شما میتوانید این پروژه را در گیتهاب مشاهده کنید.
  • برای اشکالات یا قابلیت هایی که میخواهید برطرف شود Issue ثبت کنید.
  • پروژه را Fork کنید و Star دهید.
  • ایشوهایی که وجود دارد را برطرف کنید و Pull Request ارسال کنید.
  • برای در جریان بودن از روند توسعه در جلسات برنامه ریزی (Planning Meeting) و گزارشات هفتگی (Standup Meeting ) که همه اینها در Microsoft Teams برگزار میشود شرکت کنید. 
نظرات نظرسنجی‌ها
اگر بخواهید کنار دات نت بر روی یک پلتفرم یا زبان دیگری نیز کار کنید کدام را انتخاب می کنید؟
خیلی از همسنای من (لااقل) برنامه نویسی رو با پاسکال و سی++ شروع کردن، و مطمئنا هم دوسش دارن، منتها به خاطر سختی مدیریت حافظه و درگیریهای زیادی که توی کار با اشاره گرها وجود داره از سی یا سی++ فاصله گرفتن. من فکر نمیکردم کسی که برنامه نویسی روی زبانهای تا این حد انتزاعی انجام میده دیگه حوصله کنه و برگرده به سی++، با اینحال QT رو به این دلیل نوشتم که در شبکه‌های اجتماعی مختلف دوستانی رو دیدم که  باهاش کار میکنن و پروژه‌های خوبی زدن.
مطالب
کار با Docker بر روی ویندوز - قسمت ششم - کار با بانک‌های اطلاعاتی درون Containerها
تا اینجا نحوه‌ی اجرای برنامه‌ها را داخل کانتینرها بررسی کردیم؛ اما هنوز در مورد داده‌های آن‌ها بحث نکرده‌ایم. اگر بانک‌های اطلاعاتی را به درون کانتینرها منتقل کنیم، چه بر سر داده‌های آن‌ها می‌آید؟


بررسی روش اجرای MS SQL Server Express درون یک Container

اگر مخازن Imageهای رسمی مایکروسافت را در داکرهاب بررسی کنیم، به مخازنی مانند mssql-server-windows-express ، mssql-server و یا mssql-server-linux نیز خواهیم رسید. در اینجا آخرین نگارش Image مربوط به SQL Server Express آن، حدود 7GB حجم دارد. برای دریافت آن ابتدا به Windows Containers سوئیچ کنید و سپس دستور زیر را صادر نمائید:
 docker pull microsoft/mssql-server-windows-express
پس از دریافت آن، اگر به مستندات رسمی آن در داکر هاب مراجعه کنیم، دستوری را به صورت زیر برای اجرای آن عنوان کرده‌است:
 docker run -d -p 1433:1433 -e sa_password=<SA_PASSWORD> -e ACCEPT_EULA=Y microsoft/mssql-server-windows-express
در این دستور:
- سوئیچ d سبب می‌شود تا پس از اجرای این دستور، بلافاصله به command prompt بازگشت داده شویم و SQL Server Express در background اجرا شود.
- سپس پورت 1433 میزبان به پورت 1433 کانتینر، نگاشت شده‌است که پورت استاندارد SQL Server است.
- سوئیچ e، امکان تنظیم متغیرهای محیطی را میسر می‌کند؛ برای مثال ورود کلمه‌ی عبور کاربر SA و یا پذیرش مجوز آن. برای نمونه، این کلمه‌ی عبور را مساوی password وارد کنید؛ هرچند کار نخواهد کرد، اما بررسی خطاهای به همراه آن مفید است.
- و در آخر نام image مرتبط ذکر شده‌است.

پس از اجرای این دستور، کانتینر SQL Server Express، در پس زمینه شروع به کار خواهد کرد و بلافاصله به خط فرمان بازگشت داده می‌شویم. در اینجا ممکن است آغاز SQL Server اندکی طول بکشد. برای اینکه دریابیم در این لحظه وضعیت پروسه‌ی آن به چه صورتی است، دستور docker logs id را صادر کنید. پس از آن خطایی مانند password validation failed را مشاهده خواهیم کرد. عنوان می‌کند که پیچیدگی کلمه‌ی عبور وارد شده کافی نیست.

یک نکته: زمانیکه دستور docker run را اجرا می‌کنیم، یک هش طولانی را نمایش می‌دهد و پس از آن به خط فرمان بازگشت داده می‌شویم. این هش طولانی، همان id کانتینر در حال اجرا است. برای مثال در دستور docker logs id می‌توان 3 حرف ابتدای این هش را بجای id وارد کرد. البته این id را توسط دستور docker ps نیز می‌توان بدست آورد.

بنابراین با توجه به اینکه دستور docker logs id، خطایی را گزارش کرده‌است، توسط دستور docker stop id، این کانتینر را متوقف کرده و آن‌را مجددا با کلمه‌ی عبوری مانند pass!w0rd1 اجرا می‌کنیم:
 docker run -d -p 1433:1433 -e sa_password=pass!w0rd1 -e ACCEPT_EULA=Y microsoft/mssql-server-windows-express
اینبار نیز مجددا دستور docker logs id را بر اساس id جدید این کانتینر اجرا می‌کنیم که پیام Started SQL Server را نمایش می‌دهد. بنابراین تا به اینجا موفق شدیم پروسه‌ی SQL Server Express را بدون مشکلی آغاز کنیم.


همانطور که در قسمت سوم نیز عنوان شد، اگر این کانتینر را بر روی ویندوز سرور، در حالت Windows Containers اجرا کنیم (و نه در حالت Hyper-V)، پروسه‌های اجرای شده‌ی داخل یک Container را می‌توان با Job Object Idهای یکسانی که دارند، در Task Manager ویندوز، در کنار سایر پروسه‌های سیستم، شناسایی کرد.


اتصال به SQL Server Express اجرا شده‌ی داخل یک Container توسط SQL Server Management Studio

پس از اجرای SQL Server Express دخل کانتینر، مطابق تنظیمات آن، چه در سیستم میزبان و چه در داخل کانتینر، به پورت 1433 گوش فرا داده می‌شود. به همین منظور نیاز است IP این کانتینر را نیز بدست آوریم. برای اینکار دستور ipconfig را در سیستم میزبان صادر کنید تا بر اساس مشخصات کارت شبکه‌ی مجازی آن، بتوان IP آن‌را بدست آورد (دستور docker inspect id نیز چنین اطلاعاتی را به همراه دارد). اکنون می‌توان از داخل سیستم راه دور دیگری که SQL Server Management Studio بر روی آن نصب است، توسط این IP و پورت، به SQL Server Express متصل شد.


البته در اینجا نیازی به ذکر پورت نیست؛ چون پورت 1433، شماره پورت پیش‌فرض است. بعد از اتصال، می‌توان کارهای متداولی مانند ایجاد یک بانک اطلاعاتی جدید را انجام داد.
برای آزمایش، یکبار دستور docker ps را صادر کنید تا id این کانتینر مشخص شود. سپس دستور docker stop id را صادر کنید تا پروسه SQL Server Express خاتمه یابد. اکنون اگر در SQL Server Management Studio قصد کار با آن‌را داشته باشیم، پیام عدم اتصال مشاهده می‌شود. اکنون برای اجرای مجدد کانتینر، دستور docker start id را صادر کنید.


بررسی روش اجرای MySQL داخل یک Container

برای اجرای MySQL نیاز است به Linux Containers سوئیچ کنیم. حجم tag ویژه‌ی latest آن نیز حدود 138MB است که نسبت به SQL Server Express هفت گیگابایتی، بسیار کمتر است!
در همان صفحه‌ی مستندات آن در داکرهاب، دستور اجرایی آن نیز ذکر شده‌است:
 docker run --name some-mysql -e MYSQL_ROOT_PASSWORD=my-secret-pw -d mysql:tag
در اینجا نیز توسط سوئیچ e که مخفف environment است، یکسری از متغیرهای محیطی MySQL، مانند کلمه‌ی عبور آن قابل تنظیم هستند. همچنین سوئیچ d نیز برای اجرای آن در پس زمینه، ذکر شده‌است. همین دستور را به همین شکل، صرفا با حذف tag آن، جهت اشاره‌ی به آخرین نگارش موجود این image، اجرا می‌کنیم:
 docker run --name some-mysql -e MYSQL_ROOT_PASSWORD=my-secret-pw -d mysql
با اجرای این دستور، در ابتدا MySQL از داکرهاب دریافت شده و سپس در پس زمینه اجرا خواهد شد. پیش از بازگشت به command prompt، یک هش طولانی نیز نمایش داده می‌شود که همان id کانتینر در حال اجرای آن است. برای اینکه بتوانیم ریز جزئیات رخ داده را بهتر مشاهده کنیم، می‌توان از دستور docker logs id استفاده کرد.

یک نکته: می‌توان یک command prompt جدید را باز کرد و سپس دستور docker logs -f id را در آن صادر کرد. به این صورت لاگ‌های لحظه‌ای یک کانتینر را نیز می‌توان مشاهده کرد (f در اینجا به معنای follow است).

اکنون می‌خواهیم MySQL Client موجود در همین Container در حال اجرا را، اجرا کنیم (اجرای پروسه‌ای درون یک کانتینر در حال اجرا). برای اینکار از دستور docker exec استفاده می‌شود:
docker ps
docker exec -it id mysql --user=root --password=my-secret-pw
ابتدا توسط دستور docker ps مقدار id این کانتینر را بدست می‌آوریم و سپس در دستور بعدی، از آن استفاده خواهیم کرد.
در اینجا توسط دستور docker exec ابتدا یک interactive shell را درخواست کرده‌ایم (اجرای foreground یک برنامه‌ی شل). سپس id این کانتینر باید ذکر شود. پس از آن نام فایل اجرایی MySQL Client قید شده و در پایان، نام کاربری و کلمه‌ی عبور اتصال به آن که در دستور docker run تنظیم شده‌اند، ذکر می‌شوند.
با اجرای این دستور، به خط فرمان MySQL Client داخل این کانتینر دسترسی پیدا می‌کنیم. در اینجا می‌توان دستورات مختلفی را برای کار با پروسه‌ی mysql اجرا کرد؛ مانند اجرای دستور show databases که لیست بانک‌های اطلاعاتی موجود را نمایش می‌دهد:
mysql> show databases;
use mysql;
show tables;
select * from user;
exit;


روش مدیریت داده‌های بانک‌های اطلاعاتی توسط Docker

در قسمت قبل دریافتیم که لایه‌ی رویی یک container، دارای قابلیت read/write است و برای مثال می‌توان فایل‌های یک وب سایت استاتیک را در آنجا کپی و سپس هاست کرد. اما این لایه، لایه‌ی مناسبی برای ذخیره سازی داده‌های یک بانک اطلاعاتی نیست. در اینجا برای مدیریت بهتر این نوع داده‌ها، از مفهومی به نام volume استفاده می‌شود.
برای درک روش مدیریت داده‌ها توسط داکر، دستور docker volume ls را اجرا کنید. مشاهده خواهید کرد که docker یک volume پیش‌فرض را نیز ایجاد کرده‌است. البته با volumes پیشتر در قسمت چهارم، در بخش «روش به اشتراک گذاری فایل سیستم میزبان با کانتینرها» نیز آشنا شده‌ایم. این volume پیش‌فرض، کار ذخیره سازی اطلاعات را حتی اگر کانتینری در حال اجرا نباشد نیز انجام می‌دهد. وجود یک چنین قابلیتی جهت از دست نرفتن اطلاعات ارزشمند ذخیره شده‌ی در بانک‌های اطلاعاتی بسیار ضروری است.
البته لازم به ذکر است، این volume ای را که در اینجا مشاهده می‌کنید، توسط Dockerfile خود mysql به صورت خودکار ایجاد می‌شود. برای مثال در داکرهاب، در قسمت full description این image، در ابتدای توضیحات قسمتی است به نام supported tags and respective dockerfile links. در اینجا هر tag نامبرده شده، در حقیقت لینکی است به یک Dockerfile. اگر یکی از آن‌ها را باز کنید، چنین سطری را در آن مشاهده خواهید کرد:
  VOLUME /var/lib/mysql
این دستور سبب می‌شود چنین مسیری (مسیر پیش‌فرض ثبت اطلاعات mysql) به صورت یک volume جدید، خارج از فایل سیستم کانتینر، بر روی سیستم میزبان، ایجاد شود. سپس این مسیر و volume جدید، توسط داکر به صورت خودکار به این کانتینر mount خواهد شد و برای این موارد نیازی نیست کار خاصی توسط ما انجام شود.
اینکار نه فقط برای بالابردن کارآیی اعمال read/write انجام شده‌ی توسط container انجام می‌شود، بلکه حتی اگر این کانتینر را توسط دستور docker rm id حذف کنیم، دستور docker volume ls، هنوز همان volume ای را که در حین نصب mysql به صورت خودکار ایجاد شده بود، نمایش می‌دهد. علت اینجا است که طول عمر این volume، وابسته‌ی به طول عمر کانتینر آن نیست. به این ترتیب حذف تصادفی یک کانتینر، سبب از دست رفتن اطلاعات ارزشمند داخل بانک اطلاعاتی آن نمی‌شود.


روش تعیین صریح یک volume برای یک کانتینر بانک اطلاعاتی، توسط volumeهای نامدار

دستور docker run ای را که برای اجرای mysql صادر کردیم، یک volume خودکار را ایجاد کرده‌است و اگر آن‌را با دستور docker volume ls بررسی کنیم، دارای یک نام هش مانند است که به آن anonymous volume هم گفته می‌شود. در ادامه قصد داریم یک volume نامدار را ایجاد کنیم و سپس از آن جهت ذخیره سازی اطلاعات چندین وهله از کانتینر mysql استفاده نمائیم.
پیش از ادامه بحث، ابتدا توسط دستور docker rm id، کانتینر mysql ای را که پیشتر ایجاد کردیم حذف کنید؛ هرچند این دستور، volume متناظر با آن‌را حذف نمی‌کند.
سپس برای اینکه یک کانتینر جدید mysql را با ذکر صریح volume آن ایجاد و اجرا کنیم، می‌توان از دستور زیر استفاده کرد:
 docker run --name some-mysql -e MYSQL_ROOT_PASSWORD=my-secret-pw -d -v db:/var/lib/mysql mysql
در اینجا از سوئیچ v برای ایجاد یک volume نامدار استفاده شده‌است و در آن بجای ذکر قسمت مسیر پوشه‌ای در سمت میزبان، صرفا یک نام، مانند db، پیش از ذکر : قید شده‌است. پس از :، مسیری که این volume قرار است در آن کانتینر به آن نگاشت شود، ذکر شده‌است.
اکنون اگر دستور docker volume ls را صادر کنیم، در لیست خروجی آن، نام db قابل مشاهده‌است.

در ادامه پروسه‌ی MySQL Client داخل این کانتینر را اجرا کرده:
 docker exec -it some-mysql mysql --user=root --password=my-secret-pw
و تغییراتی را به صورت زیر اعمال می‌کنیم:
mysql> show databases;
create database pets;
show databases;
exit;
در اینجا بانک اطلاعاتی جدید pets ایجاد شده‌است.

اکنون در ابتدا این کانتینر را متوقف کرده و سپس آن‌را حذف می‌کنیم:
docker ps
docker stop id
docker rm id
هرچند اگر دستور حذف را با سوئیچ f- نیز اجرا کنیم (به معنای force)، کار stop را به صورت خودکار انجام می‌دهد.

در ادامه مجددا همان دستور قبلی را که توسط آن volume نامداری، ایجاد کردیم، اجرا می‌کنیم:
 docker run --name some-mysql -e MYSQL_ROOT_PASSWORD=my-secret-pw -d -v db:/var/lib/mysql mysql
اینبار اگر دستور docker volume ls را مجددا صادر کنیم، مشاهده خواهیم کرد این کانتینر جدید، بجای ایجاد یک volume جدید، از همان volume موجود db که آن‌را پیشتر ایجاد کردیم، استفاده می‌کند؛ هرچند کانتینری که آن‌را ایجاد کرده‌است، دیگر وجود خارجی ندارد. در این حالت اگر MySQL Client این کانتینر را اجرا نمائیم:
 docker exec -it some-mysql mysql --user=root --password=my-secret-pw
و سپس دستور نمایش بانک‌های اطلاعاتی آن‌را صادر کنیم:
 mysql> show databases;
در خروجی آن هنوز بانک اطلاعاتی pets که پیشتر ایجاد شده بود، قابل مشاهده‌است. بنابراین حذف و یا ایجاد کانتینرها، تاثیری را بر روی volumeهای ایجاد شده، نخواهند داشت.


روش حذف volumes اضافی

با توجه به اینکه volumeها، طول عمر متفاوتی را نسبت به کانتینرها دارند، ممکن است پس از مدتی فضای دیسک سخت شما را پر کنند. برای مثال به ازای هربار اجرای دستور docker run مربوط با MYSQL با نامی متفاوت، یک volume جدید نیز ایجاد می‌شود.
خروجی دستور docker inspect id به همراه قسمتی است به نام mounts که خاصیت name آن، دقیقا مساوی نام volume متناظر با کانتینر بررسی شده‌است. همچنین خاصیت source آن، محل دقیق ذخیره سازی این volume را بر روی فایل سیستم میزبان مشخص می‌کند.
برای حذف آن‌ها، ابتدا نیاز است کانتینرها را متوقف کرد. دستور زیر تمام کانتینرهای در حال اجرا را متوقف می‌کند. در اینجا دستور docker ps -q، لیست id تمام کانتینرهای در حال اجرا را باز می‌گرداند (در این دستورات، افزودن پارامتر q، سبب بازگشت صرفا idها می‌شود):
 docker stop $(docker ps -q)
اگر می‌خواهید تمام کانتینرهای موجود را حذف کنید:
 docker rm $(docker ps -aq)
و یا دستور زیر ابتدا تمام کانتینرهای موجود را متوقف کرده و سپس آن‌ها را حذف می‌کند:
 docker rm -f $(docker ps -aq)
دستور زیر تمام volumes موجود را حذف می‌کند:
 docker volume rm $(docker volume ls -q)
دستور زیر یک کانتینر با id مشخص شده را به همراه volume نامگذاری نشده‌ی مرتبط با آن، متوقف و سپس حذف می‌کند:
 docker rm -fv id
دستور زیر، لیست تمام volumes غیراستفاده شده‌ی توسط کانتینرهای موجود را نمایش می‌دهد (به یک چنین volumeهای در اینجا dangling گفته می‌شود؛ volume ای که کانتینر آن حذف شده‌است):
 docker volume ls -f dangling=true
 که می‌تواند لیست مناسبی برای حذف باشند:
 docker volume rm $(docker volume ls -qf dangling=true)