قبل از شروع بحث، سورس کامل پروژه را از اینجا دریافت کنید (یک پروژه VSTO از نوع outlook add-in در VS.Net 2008 SP1).
توضیحات مربوطه را به دو قسمت تقسیم کردهام. قسمت اول یافتن تاریخهای sent و فارسی کردن آنها و قسمت بعدی نحوه اضافه کردن یک ستون و مقدار دهی آن (در روزی دیگر).
متن ایمیلهای دریافتی در آوتلوکهای جدید عموما به دو فرمت HTML و یا RichText دریافت میشوند. حالتهای دیگری هم مانند plain و unspecified هم موجود هستند که حتی اگر ایمیلی را به صورت plain ارسال نمائید، با فرمت RichText نمایش داده خواهد شد (بنابراین بر اساس آزمایشات من بررسی این دو فرمت کفایت میکند).
برای اینکه قسمتهای sent را پیدا کنیم در ابتدا باید سورس صفحه را بررسی نمائیم (کلیک راست و view source).
در حالت فرمت HTML داریم:
<p class=MsoNormal><b><span style='font-size:10.0pt;font-family:"Tahoma","sans-serif"'>From:</span></b><span
style='font-size:10.0pt;font-family:"Tahoma","sans-serif"'> Nasiri, Vahid <br>
<b>Sent:</b> <span lang=AR-SA dir=RTL>our date goes here</span><br>
<b>To:</b> xyz<br>
<b>Subject:</b> our subject<o:p></o:p></span></p>
و در حالت ایمیلهای RichText خواهیم داشت:
From: tst@tst.net<br>
Sent:<span lang=AR-SA dir=RTL>our date goes here</span><br>
To: Nasiri, Vahid<br>
Subject: <span lang=AR-SA dir=RTL>xyz</span><br>
استفاده از روشهای متداول کار با رشتهها در اینجا به علت انبوهی از تگهای HTML اصلا مقرون به صرفه نیست و کند خواهد بود. خوشبختانه با وجود کتابخانه regular expressions در دات نت، پیدا کردن عباراتی که از یک الگوی خاص پیروی میکنند به سادگی و با سرعت بسیار بالایی قابل انجام است.
پیشنهاد من برای دو فرمت بالا به صورت زیر بوده: (شاید شما الگوی دیگری را یافتید، زیبایی اوپن سورس :))
private const string REGEXHTMLPATTERN = @"(?s)>\s(.+?)<br>";
private const string REGEXPLAINTEXTPATTERN = "(?s)Sent:(.+?)<br>";
سرعت استفاده از RegularExpressions فوق العاده بالا است و برای نمونه در ایمیلی با بیش از 20 ریپلای در کسری از ثانیه کل این عملیات انجام خواهد شد.
تا اینجا بررسی کلی الگوریتم مورد استفاده قسمت اول به پایان میرسد.
بیشترین وقتی که در این پروژه صرف شد نحوه پیدا کردن شیء MailItem جاری باز شده با استفاده از رخدادهای آوتلوک بود (مدت مدیدی را برای این مورد وقت گذاشتم! چون عملا در هیچ کتابی به این مباحث پرداخته نمیشود و باید کل نت را زیر و رو کرد). دو مورد را باید بررسی کرد. الف) inspector ها (صفحهای که جهت ایجاد یک ایمیل جدید یا ریپلای به ایمیل جاری باز میشود، inspector نام دارد) ب) ActiveExplorer ها (صفحهای که لیست ایمیلها را نمایش میدهد و این صفحه میتواند در فولدرهای مختلفی که شما ایجاد کردهاید نیز نمایش داده شود بنابراین بررسی inbox به تنهایی کافی نیست)
نحوه ایجاد اشیاء مربوطه و تحت نظر قرار دادن آنها را در روال ThisAddIn_Startup فایل ThisAddIn.cs میتوانید مشاهده نمائید. نکته مهمی که اینجا وجود دارد، تعریف این اشیاء در سطح کلاس است. در غیراینصورت با اولین خانه تکانی garbage collector ، اشیاء شما (بدلیل نبود ارجاعی فعال به آنها) معدوم خواهند شد(!) و دیگر روالهای رخداد گردان تعریف شده کار نخواهند.
این نیاز اساسا به معنی دسترسی به سخت افزار کاربر از طریق مرورگر میباشد که به دلایل متعددی امکان پذیر نیست! مشکلات امنیتی ایجاد شده در این راه بسیار جدی است. اما خوشبختانه راههایی برای رسیدن به این هدف وجود دارند:
1- ActiveX : که به صورت native فقط در IE پشتیبانی میشود (در نسخههای جدیدتر IE نیاز به بروز رسانی پلاگین ActiveX controls میباشد) و برای استفادهی از آن در مرورگرهای Firefox و Chrome هم باید از پلاگینهای جانبی روی هر مرورگر استفاده کرد که مثلا برای اجرای بر روی Firefox، باید افزونهی IE Tab را نصب کرد که تنها کارش این است که سایت را از طریق موتور IE در پنجرهی فایرفاکس اجرا کند! که البته این مورد مثل این میماند که سایت در IE باز شده باشد که ممکن است زیاد خوشایند نباشد؛ در غیر اینصورت باید پروژه را از اول بر مبنای اجرای بر روی IE طراحی و پیاده سازی کرد. در ضمن از مشکلات اجرای پلاگین نوشته شده توسط برنامه نویس در IE و نسخههای مختلف آن هم چشم پوشی میکنیم. یکی از مزیتهای این روش نسبت به بقیه این هست که دانلود و نصب پلاگین مورد نیاز به صورت خودکار و توسط مرورگر انجام میشود.
2- استفاده از یک کامپوننت جانبی : مثل کامپوننتهای LEADTOOLS که ابزارهای متنوع و SDKهای بسیار قدرتمندی را برای اینکار و کارهای دیگر، مانند کار با اسکن تصاویر مغزی، پردازش تصویر، بارکد خوانها، مدیریت اسناد و ... فراهم کرده است. قیمت این ابزار بسیار زیاد است و در برخی موارد امکانات فراهم آورده بسیار بیشتر است از خواستهی ما. این ابزار، هم در HTML & Javascript و هم در DotNet قابل استفاده است و مستندات نسبتا خوبی هم در این زمینه ارائه کرده است. همچنین کامپوننت Dynamsoft که باز هم غیر رایگان هست و قیمت بالایی نیز دارد.
اگر روال کار کامپوننتهای بالا را مورد بررسی قرار دهید (از طریق اجرای Demo ها، اینجا و اینجا) متوجه خواهید شد که هر دو نیاز به نصب یک سرویس یا App سمت کلاینت جهت اجرای دستورات خود دارند. پس میشود اینطور نتیجه گرفت که انجام اینکار بدون اینکه چیزی سمت کاربر نصب شود، ممکن نیست و در هر دو، لینک نصب فایل exe سرویس برای دانلود قرار داده شده است. بر این اساس به راه حل سومی خواهیم رسید که خودمان این سرویس را جهت تعامل با اسکنر سمت کاربر طراحی و پیاده سازی نماییم.
اما چطور ممکن است که با اجرای یک فایل exe سمت کاربر (با این فرض که کاربر در یک دامین مطمئن قرار دارد و میشود درخواست نصب سرویس را نمود) این امکان را برای کاربر فراهم نمود که با یک کلیک در مرورگر، اسکنر به صورت خودکار اسکن را آغاز کرده و سپس تصویر حاصل را به یکی از کنترلرهای ما در سمت سرور ارسال نماید؟
برای اینکار ما با دو صورت مساله مواجه هستیم؛ اول اینکه چطور تصویر را سمت کاربر اسکن کنیم و دوم اینکه چطور این تصویر را به سرور ارسال نماییم!
برای مسالهی اول از کتابخانه Windows Image Acquisition (WIA) استفاده خواهیم نمود که این کتابخانه به ما این امکان را میدهد تا با سخت افزارهایی که از TWAIN پشتیبانی میکنند، بتوانیم ارتباط برقرار نماییم.
برای مسالهی دوم هم نیاز به پیاده سازی یک WCF Service و اجرای آن (هاست کردن) در سمت کلاینت داریم. در واقع با صدا زدن متدهای این سرویس، از کتابخانهی بالا استفاده کرده و اسکن را انجام میدهیم.
ادامه دارد...
*در دنیای NoSql پیاده سازیهای متفاوتی از دیتابیسها انجام شده است و هر دیتابیس، ویژگیها و مزایا و معایب خاص خودش را دارد. باید قبول کرد که همیشه و همه جا نمیتوان از یک پایگاه داده NoSql مشخص استفاده نماییم (دلایلی نظیر محدودیتهای License، هزینه پیاده سازی و...). در نتیجه بررسی یک دیتابیس دیگر با شرایط و توانمندیهای خاص آن خالی از سود نیست.
» این دیتاییس در گروه Graph databasesها قرار دارد و از زبان SPARQL (بخوانید Sparkle) برای کوئری گرفتن و کار با دادهها بهره میبرد؛
» متن باز و رایگان است
» پشتیبانی از دات نت چهار به بعد؛
» قابل استفاده در Mobile Application - Windows phone 7 , 8؛
» بدون شما (Schema Less) و با قابیلت ذخیره سازی triple و به فرمت RDF
» پشتیبانی از Linq و OData؛
» پشتیبانی از تراکنشها ؛
» پیاده سازی مدل برنامه به صورت Code First؛
» سرعت بالا جهت ذخیره سازی و لود اطلاعات؛
» نیاز به پیکربندیهای خاص جهت پیاده سازی ندارد؛
» ارائه افزونه رایگان Polaris جهت کوئری گفتن و نمایش Visual داده ها.
و غیره که در ادامه با آنها آشنا خواهید شد.
در B*Db دو روش برای ذخیره سازی اطلاعات وجود دارد:
» Append Only : در این روش تمامی تغییرات (عملیات نوشتن) در انتهای فایل index اضافه خواهد شد. این روش مزایای زیر را به دنبال خواهد داشت:
- عملیات نوشتن هیچگاه عملیات خواندن اطلاعات را block نمیکند. در
نتیجه هر تعداد عملیات خواندن فایل (منظور اجرای کوئریهای SPQRL است) میتواند به صورت موازی بر روی Storeها اجرا شود.
- به دلیل اینکه عمل ویرایش واقعی هیچ گاه انجام نمیشود (دادهها فقط اضافه خواهند شد) همیشه میتوانید وضعیت Store را به حالتهای قبلی بازگردانید.
- عملیات نوشتن اطلاعات بسیار سریع خواهد بود.
نکته ای که باید به آن دقت داشت این است که فقط در هنگام ساخت Storeها میتوانید نوع ذخیره سازی آن را تعیین نمایید، بعد از ساخت Store امکان سوئیچ بین حالات امکان پذیر نیست.
همان طور که پیشتر گفته شد B*Db برای ذخیره سازی اطلاعات از سند RDF بهره میبرد. البته با RDF Syntaxهای متفاوت :
هم چنین در B*Db چهار روش برای دست یابی با دادهها (پیاده سازی عملیات CRUD) وجود دارد از قبیل:
» B*Db EntityFramewok
» Data Object Layer
» RDF Client Api
» Dynamic API
که هر کدام در طی پستهای متفاوت بررسی خواهد شد.
بررسی یک مثال با روش B*Db EntityFramework
برای شروع ابتدا یک پروژه جدید از نوع Console Application ایجاد کنید. سپس از طریق Nuget اقدام به نصب Package زیر نمایید:
pm> install-Package BirghtStarDb
PM> Install-Package BirghtStarDbLibs
بعد از نصب پکیجهای بالا یک فایل Text Template با نام MyEntityContext.tt نیز به پروژه افزوده خواهد شد. این فایل جهت تولید خودکار مدلهای برنامه استفاده میشود. اما برای این کار لازم است به ازای هر مدل ابتدا یک اینترفیس ایجاد نمایید. برای مثال:
[Entity] public interface IBook { public int Code { get; set; } public string Title { get; set; } }
» نیاز به ایجاد یک خاصیت به عنوان Id وجود ندارد. به صورت پیش فرض خاصیت Id با نوع string برای هر مدل پیاده سازی میشود. اما اگر قصد دارید این نام خاصیت را تغییر دهید میتوانید به صورت زیر عمل کنید:
[Entity] public interface IBook { [Identifier] public string MyId { get; } public int Code { get; set; } public string Title { get; set; } }
استفاده از اینترفیس برای ساخت مدل انعطاف پذیری بالایی را در اختیار ما قرار میدهد که بعدا مفصل بحث خواهد شد. برای عملیات درج داده میتوان به صورت زیر عمل کنید:
MyEntityContext context = new MyEntityContext("type=embedded;storesdirectory=c:\brightstar;storename=test"); var book = context.Books.Create(); book.Code = 1; book.Title = "Test"; context.Books.Add(book); context.SaveChanges();
»embedded : در این حالت از طریق آدرس فیزیکی فایل مورد نظر میتوان یک Connection ایجاد کرد.
»rest : یا استفاده از HTTP یا HTTPS میتوان به سرویس B*Db دسترسی داشت.
»dotNetRdf : برای ارتباط با یک Store دیگر با استفاده از اتصال دهندههای DotNetRDf.
»sparql : اتصال به منبع داده ای دیگر با استفاده از پروتکل SPARQL
در هنگام ایجاد اتصال باید نوع مورد نظر را از حتما تعیین نمایید. با استفاده از storedirctory مکان فیزیکی فایل تعیین خواهد شد.
با توجه به این حالت، آیا در فایل پروژه نیاز است به طور صریح نسخه 1.8.10 را ذکر کنیم؟
- Explicit
- Implicit
کامپایلر JIT
همانطور که در تصویر فوق مشاهده میکنید، سورس کد توسط کامپایلر دات نت به exe و یا dll کامپایل میشود. کامپایلر JIT تنها متدهایی را که در زمان اجرا(runtime) فراخوانی میشوند را کامپایل میکند. در دات نت فریم ورک سه نوع JIT Compilation داریم:
Normal JIT Compilation
در این نوع کامپایل، متدها در زمان فراخوانی در زمان اجرا کامپایل میشوند. بعد از اجرا، متد داخل حافظه ذخیره میشود. به متدهای ذخیره شده در حافظه jitted گفته میشود. دیگر نیازی به کامپایل متد jit شده نیست. در فراخوانی بعدی، متد مستقیماً از حافظه کش در دسترس خواهد بود.
Econo JIT Compilation
این نوع کامپایل شبیه به حالت Normal JIT است با این تفاوت که متدها بلافاصله بعد از اجرا از حافظه حذف میشوند.
Pre-JIT Compilation
یکی دیگر از حالتهای کامپایل برنامههای دات نتی Pre-JIT Compilation می باشد. در این حالت به جای متدهای مورد استفاده، کل اسمبلی کامپایل میشود. در دات نت میتوان اینکار را توسط Ngen.exe یا (Native Image Generator) انجام داد. تمام دستورالعملهای CIL قبل از اجرا به کد محلی(Native Code) کامپایل میشوند. در این حالت runtime میتواند از native images به جای کامپایلر JIT استفاده کند. این نوع کامپایل عملیات تولید کد را در زمان اجرای برنامه به زمان Installation منتقل میکند، در اینصورت برنامه نیاز به یک Installer برای اینکار دارد.
Multicore JIT
در دات نت فریم ورک 4.5 یک راه حل جایگزین دیگر برای بهینه سازی و بهبود سرعت اجرای برنامههای دات نت وجود دارد. همانطور که عنوان شد Ngen.exe برای در دسترس بودن نیاز به Installer برای برنامه دارد. توسط Multicore JIT متدها بر روی دو هسته به صورت موازی کامپایل میشوند، در اینصورت میتوانید تا 50 درصد از JIT Time صرفه جویی کنید.
Multicore JIT همچنین میتواند باعث بهبود سرعت در برنامههای WPF شود. در نمودار زیر میتوانید حالتهای استفاده و عدم استفاده از Multicore JIT را در سه برنامه WPF نوشته شده مشاهده کنید.
Multicore JIT در عمل
Multicore JIT از دو مد عملیاتی استفاده میکند: مد ثبت(Recording mode)، مد بازپخش(Playback mode)
در حالت ثبت کامپایلر JIT هر متدی که نیاز به کامپایل داشته باشد را رکورد میکند. بعد از اینکه CLR تعیین کند که اجرای برنامه به اتمام رسیده است، تمام متدهایی که اجرا شده اند را به صورت یک پروفایل بر روی دیسک ذخیره میکند.
هنگامیکه Multicore JIT فعال میشود، با اولین اجرای برنامه، حالت ثبت مورد استفاده قرار میگیرد. در اجراهای بعدی، از حالت بازپخش استفاده میشود. حالت بازپخش پروفایل را از طریق دیسک بارگیری کرده، و قبل از اینکه این اطلاعات توسط ترد اصلی مورد استفاده قرار گیرد، از آنها برای تفسیر (کامپایل) متدها در پیشزمینه استفاده میکند.
در نتیجه، ترد اصلی به کامپایل دیگری نیاز ندارد، در این حالت سرعت اجرای برنامه بیشتر میشود. حالتهای ثبت و بازپخش تنها برای کامپیوترهایی با چندین هسته فعال میباشند.
استفاده از Multicore JIT
در برنامههای 4.5 ASP.NET و 5 Silverlight به صورت پیش فرض این ویژگی فعال میباشد. ازآنجائیکه این برنامهها hosted application هستند؛ در نتیجه فضای مناسبی برای ذخیره سازی پروفایل در این نوع برنامهها موجود میباشد. اما برای برنامههای Desktop این ویژگی باید فعال شود. برای اینکار کافی است دو خط زیر را به نقطه شروع برنامه تان اضافه کنید:
public App() { ProfileOptimization.SetProfileRoot(@"C:\MyAppFolder"); ProfileOptimization.StartProfile("Startup.Profile"); }
توسط متد SetProfileRoot میتوانیم مسیر ذخیره سازی پروفایل JIT را مشخص کنیم. در خط بعدی نیز توسط متد StartProfile نام پروفایل را برای فعال سازی Multicore JIT تعیین میکنیم. در این حالت در اولین اجرای برنامه پروفایلی وجود ندارد، Multicore JIT در حالت ثبت عمل میکند و پروفایل را در مسیر تعیین شده ایجاد میکند. در دومین بار اجرای برنامه CRL پروفایل را از اجرای قبلی برنامه بارگذاری میکند؛ در این حالت Multicore JIT به صورت بازپخش عمل میکند.
همانطور که عنوان شد در برنامههای ASP.NET 4.5 و Silverlight 5 قابلیت Multicore JIT به صورت پیش فرض فعال میباشد. برای غیر فعال سازی آن میتوانید با تغییر فلگ profileGuidedOptimizations به None اینکار را انجام دهید:
<?xml version="1.0" encoding="utf-8" ?> <configuration> <!-- ... --> <system.web> <compilation profileGuidedOptimizations="None" /> <!-- ... --> </system.web> </configuration>
Docker for Windows چگونه از هر دوی کانتینرهای ویندوزی و لینوکسی پشتیبانی میکند؟
زمانیکه docker for windows را اجرا میکنیم، سرویسی را ایجاد میکند که سبب اجرای پروسهی ویژهای به نام com.docker.proxy.exe میشود:
هنگامیکه برای مثال فرمان docker run nginx را توسط Docker CLI اجرا میکنیم، Docker CLI از طریق واسط یاد شده، دستورات را به MobyLinuxVM منتقل میکند. به این صورت است که امکان اجرای Linux Containers، بر روی ویندوز میسر میشوند:
اکنون اگر به Windows Containers سوئیچ کنیم (از طریق کلیک راست بر روی آیکن Docker در قسمت Tray Icons ویندوز)، پروسهی dockerd.exe یا docker daemon شروع به کار خواهد کرد:
اینبار اگر مجددا از Docker CLI برای اجرای مثلا IIS Container استفاده کنیم، دستور ما از طریق واسطهای com.docker.proxy و dockerd، به کانتینر ویندوزی منتقل و اجرا میشود:
نگاهی به معماری Docker بر روی ویندوز سرور
داکر بر روی ویندوز سرور، تنها به همراه موتور مدیریت کنندهی Windows Containers است:
در اینجا با صدور فرمانهای Docker CLI، پیامها مستقیما به dockerd یا موتور داکر بر روی ویندوز سرور ارسال شده و سپس کار اجرا و مدیریت یک Windows Container انجام میشود.
نصب Docker بر روی ویندوز سرور
جزئیات مفصل و به روز Windows Containers را همواره میتوانید در این آدرس در سایت مستندات مجازی سازی مایکروسافت مطالعه کنید (قسمت Container Host Deployment - Windows Server آن). پیشنیاز کار با آن نیز نصب حداقل ویندوز سرور 2016 میباشد و بهتر است تمام به روز رسانیهای آنرا نیز نصب کرده باشید؛ چون تعدادی از بهبودهای کار با کانتینرهای آن، به همراه به روز رسانیها آن ارائه شدهاند.
برای شروع به نصب، نیاز است کنسول PowerShell ویندوز را با دسترسی Admin اجرا کنید.
سپس اولین دستوراتی را که نیاز است اجرا کنید، کار نصب موتور Docker و CLI آنرا به صورت خودکار بر روی ویندوز سرور انجام میدهند:
Install-Module -Name DockerMsftProvider -Repository PSGallery -Force Install-Package -Name docker -ProviderName DockerMsftProvider Restart-Computer -Force
- به علاوه اگر دستور *get-service *docker را در کنسول PowerShell صادر کنید، مشاهده خواهید کرد که سرویس جدیدی را به نام Docker نیز نصب و راه اندازی کردهاست که به dockerd.exe اشاره میکند.
- و یا اگر در کنسول PowerShell دستور docker را صادر کنید، ملاحظه خواهید کرد که CLI آن، فعال و قابل استفادهاست. برای مثال، دستور docker version را صادر کنید تا بتوانید نگارش docker نصب شده را ملاحظه نمائید.
اجرای Image مخصوص NET Core. بر روی ویندوز سرور
تگهای مختلف Image مخصوص NET Core. را در اینجا ملاحظه میکنید. در ادامه قصد داریم tag مرتبط با nanoserver آنرا نصب کنیم (با حجم 802MB):
docker run microsoft/dotnet:nanoserver
docker run -it microsoft/dotnet:nanoserver
چرا حجم Image مخصوص NET Core. نگارش nanoserver آن حدود 800 مگابایت است؟
در مثال قبلی، دسترسی به command prompt مجزایی نسبت به command prompt اصلی سیستم، در داخل یک container، شاید اندکی غیر منتظره بود و اکنون این سؤال مطرح میشود که یک image، شامل چه چیزهایی است؟
یک image شاید در ابتدای کار صرفا شامل فایلهای اجرایی یک برنامهی خاص به نظر برسد؛ اما زمانیکه قرار است تبدیل به یک container قابل اجرا شود، شامل بسیاری از فایلهای دیگر نیز خواهد شد. برای درک این موضوع نیاز است لایههای نرم افزاری که یک سیستم را تشکیل میدهند، بررسی کنیم:
در این تصویر از پایینترین لایهای را که با سخت افزار ارتباط برقرار میکند تا بالاترین لایهی موجود نرم افزاری را مشاهده میکنید. دراینجا هر چیزی را که در ناحیهی کرنل قرار نمیگیرد، User Space مینامند. برنامههای قرار گرفتهی در User Space برای کار با سخت افزار نیاز است با کرنل ارتباط برقرار کنند و برای این منظور از System Calls استفاده میکنند که عموما کتابخانههایی هستند که جزئی از سیستم عامل میباشند؛ مانند API ویندوز. برای مثال MongoDB توسط Win32 API و System Calls، فرامینی را به کرنل منتقل میکند.
در روش متداول توزیع و نصب نرم افزار، ما عموما همان بالاترین سطح را توزیع و نصب میکنیم؛ برای مثال خود MongoDB را. در اینجا نصاب MongoDB فرض میکند که در سیستم جاری، تمام لایههای دیگر، موجود و آمادهی استفاده هستند و اگر اینگونه نباشد، به مشکل برخواهد خورد و اجرا نمیشود. برای اجتناب از یک چنین مشکلاتی مانند عدم حضور وابستگیهایی که یک برنامه برای اجرا نیاز دارد، imageهای docker، نحوهی توزیع نرم افزارها را تغییر دادهاند. اینبار یک image بجای توزیع فقط MongoDB، شامل تمام قسمتهای مورد نیاز User Space نیز هست:
به این ترتیب دیگر مشکلاتی مانند عدم وجود یک وابستگی یا حتی وجود یک وابستگی غیرسازگار با نرم افزار مدنظر، وجود نخواهند داشت. حتی میتوان تصویر فوق را به صورت زیر نیز خلاصه کرد:
به همین جهت بود که برای مثال در قسمت قبل موفق شدیم IIS مخصوص ویندوز سرور با تگ nanoserver را بر روی ویندوز 10 که بسیاری از وابستگیهای مرتبط را به همراه ندارد، با موفقیت اجرا کنیم.
به علاوه چون یک container صرفا به معنای یک running process از یک image است، هر فایل اجرایی داخل آن image را نیز میتوان به صورت یک container اجرا کرد؛ مانند cmd.exe داخل image مرتبط با NET Core. که آنرا بررسی کردیم.
کارآیی Docker Containers نسبت به ماشینهای مجازی بسیار بیشتر است
مزیت دیگر یک چنین توزیعی این است که اگر چندین container در حال اجرا را داشته باشیم:
در نهایت تمام آنها فقط با یک لایهی کرنل کار میکنند و آن هم کرنل اصلی سیستم جاری است. به همین جهت کارآیی docker containers نسبت به ماشینهای مجازی بیشتر است؛ چون هر ماشین مجازی، کرنل مجازی خاص خودش را نسبت به یک ماشین مجازی در حال اجرای دیگر دارد. در اینجا برای ایجاد یک لایه ایزولهی اجرای برنامهها، تنها کافی است یک container جدید را اجرا کنیم و در این حالت وارد فاز بوت شدن یک سیستم عامل کامل، مانند ماشینهای مجازی نمیشویم.
شاید مطابق تصویر فوق اینطور به نظر برسد که هرچند تمام این containers از یک کرنل استفاده میکنند، اما اگر قرار باشد هر کدام OS Apps & Libs خاص خودشان را در حافظه بارگذاری کنند، با کمبود شدید منابع روبرو شویم. دقیقا مانند حالتیکه چند ماشین مجازی را اجرا کردهایم و دیگر سیستم اصلی قادر به پاسخگویی به درخواستهای رسیده به علت کمبود منابع نیست. اما در واقعیت، یک image داکر، از لایههای مختلفی تشکیل میشود که فقط خواندنی هستند و غیرقابل تغییر و زمانیکه docker یک لایهی فقط خواندنی را در حافظه بارگذاری کرد، اگر container دیگری، از همان لایهی تعریف شده، در image خود نیز استفاده میکند، لایهی بارگذاری شدهی فقط خواندنی در حال اجرای موجود را با آن به اشتراک میگذارد (مانند تصویر زیر). به این ترتیب میزان مصرف منابع docker containers نسبت به ماشینهای مجازی بسیار کمتر است:
روش کنترل پروسهای که درون یک کانتینر اجرا میشود
با اجرای دستور docker run -it microsoft/dotnet:nanoserver ابتدا به command prompt داخلی و مخصوص این container منتقل میشویم و سپس میتوان برای مثال با NET Core CLI. کار کرد. اما امکان اجرای این CLI به صورت زیر نیز وجود دارد:
docker run -it microsoft/dotnet:nanoserver dotnet --info
بدیهی است در این حالت تمام فایلهای اجرایی داخل این container را نیز میتوان اجرا کرد. برای مثال میتوان کنسول پاورشل داخل این container را اجرا کرد:
docker run -it microsoft/dotnet:nanoserver powershell
هر کانتینر دارای یک File System ایزولهی خاص خود است
تا اینجا دریافتیم که هر image، به همراه فایلهای user space مورد نیاز خود نیز میباشد. به عبارتی هر image یک file system را نیز ارائه میدهد که تنها درون همان container قابل دسترسی میباشد و از مابقی سیستم جاری ایزوله شدهاست.
برای آزمایش آن، کنسول پاورشل را در سیستم میزبان (سیستم عامل اصلی که docker را اجرا میکند)، باز کرده و دستور \:ls c را صادر کنید. به این ترتیب میتوانید لیست پوشهها و فایلهای موجود در درایو C میزبان را مشاهده نمائید. سپس دستور docker run -it microsoft/dotnet:nanoserver powershell را اجرا کنید تا به powershell داخل کانتینر NET Core. دسترسی پیدا کنیم. اکنون دستور \:ls c را مجددا اجرا کنید. خروجی آن کاملا متفاوت است نسبت به گزارشی که پیشتر بر روی سیستم میزبان تهیه کردیم؛ دقیقا مانند اینکه هارد درایو یک container متفاوت است با هارد درایو سیستم میزبان.
این تصویر زمانی تهیه شدهاست که دستور docker run یاد شده را صادر کردهایم و درون powershell آن قرار داریم. همانطور که مشاهده میکنید یک Disk جدید، به ازای این Container در حال اجرا، به سیستم میزبان اضافه شدهاست. این Disk زمانیکه در powershell داخل container، دستور exit را صادر کنیم، بلافاصله محو میشود. چون پروسهی container، به این ترتیب خاتمه یافتهاست.
اگر دستور docker run یاد شده را دو بار اجرا کنیم، دو Disk جدید ظاهر خواهند شد:
یک نکته: اگر بر روی این درایوهای مجازی کلیک راست کرده، گزینهی change drive letter or path را انتخاب نموده و یک drive letter را به آنها نسبت دهید، میتوانید محتویات داخل آنها را توسط Windows Explorer ویندوز میزبان نیز به صورت یک درایو جدید، مشاهده کنید.
خلاصهای از ایزوله سازیهای کانتینرها تا به اینجا
تا اینجا یک چنین ایزوله سازیهایی را بررسی کردیم:
- ایزوله سازی File System و وجود یک disk مجازی مجزا به ازای هر کانتینر در حال اجرا.
- پروسههای کانتینرها از پروسههای میزبان ایزوله هستند. برای مثال اگر دستور get-process را داخل یک container اجرا کنید، خروجی آن با خروجی اجرای این دستور بر روی سیستم میزبان یکی نیست. یعنی نمیتوان از داخل کانتینرها، به پروسههای میزبان دسترسی داشت و دخل و تصرفی را در آنها انجام داد که از لحاظ امنیتی بسیار مفید است. هر چند اگر به task manager ویندوز میزبان مراجعه کنید، میتوان پروسههای داخل یک container را توسط Job Object ID یکسان آنها تشخیص دهید (مثال آخر قسمت قبل)، اما یک container، قابلیت شمارش پروسههای خارج از مرز خود را ندارد.
- ایزوله سازی شبکه مانند کارت شبکهی مجازی کانتینر IIS که در قسمت قبل بررسی کردیم. برای آزمایش آن دستور ipconfig را در داخل container و سپس در سیستم میزبان اجرا کنید. نتیجهای را که مشاهده خواهید کرد، کاملا متفاوت است. یعنی network stack این دو کاملا از هم مجزا است. شبیه به اینکه به یک سیستم، چندین کارت شبکه را متصل کرده باشید. اینکار در اینجا با تعریف virtual network adaptors انجام میشود و لیست آنها را در قسمت «All Control Panel Items\Network Connections» سیستم میزبان میتوانید مشاهده کنید. یکی از مهمترین مزایای آن این است که اگر در یک container، وب سروری را بر روی پورت 80 آن اجرا کنید، مهم نیست که در سیستم میزبان، یک IIS در حال سرویس دهی بر روی پورت 80 هم اکنون موجود است. این دو پورت با هم تداخل نمیکنند.
- در حالت کار با Windows Containers، رجیستری کانتینر نیز از میزبان آن مجزا است و یا متغیرهای محیطی اینها یکی نیست. برای مثال دستور \:ls env را در کانتینر و سیستم میزبان اجرا کنید تا environment variables را گزارش گیری کنید. خروجی این دو کاملا متفاوت است. برای مثال حداقل computer name، user nameهای قابل مشاهدهی در این گزارشها، متفاوت است و یا دستور \:ls hkcu را در هر دو اجرا کنید تا خروجی رجیستری متعلق به کاربر جاری هر کدام را مشاهده کنید که در هر دو متفاوت است.
- در حالت کار با Linux Containers هر چیزی که ذیل عنوان namespace مطرح میشود مانند شبکه، PID، User، UTS، Mount و غیره شامل ایزوله سازی میشوند.
دو نوع Windows Containers وجود دارند
در ویندوز، Windows Server Containers و Hyper-V Containers وجود دارند. در این قسمت تمام کارهایی را که بر روی ویندوز سرور انجام دادیم، در حقیقت بر روی Windows Server Containers انجام شدند و تمام Containerهای ویندوزی را که در قسمت قبل بر روی ویندوز 10 ایجاد کردیم، از نوع Hyper-V Containers بودند.
تفاوت مهم اینها در مورد نحوهی پیاده سازی ایزوله سازی آنها است. در حالت Windows Server Containers، کار ایزوله سازی پروسهها توسط کرنل اشتراکی بین کانتینرها صورت میگیرد اما در Hyper-V Containers، این ایزوله سازی توسط hypervisor آن انجام میشود؛ هرچند نسبت به ماشینهای مجازی متداول بسیار سریعتر است، اما بحث به اشتراک گذاری کرنل هاست را که پیشتر در این قسمت بررسی کردیم، در این حالت شاهد نخواهیم بود. ویندوز سرور 2016 میتواند هر دوی این ایزوله سازیها را پشتیبانی کند، اما ویندوز 10، فقط نوع Hyper-V را پشتیبانی میکند.
روش اجرای Hyper-V Containers بر روی ویندوز سرور
در صورت نیاز برای کار با Hyper-V Containers، نیاز است مانند قسمت قبل، ابتدا Hyper-V را بر روی ویندوز سرور، فعالسازی کرد:
Install-WindowsFeature hyper-v Restart-Computer -Force
docker run -it --isolation=hyperv microsoft/dotnet:nanoserver powershell
معماری N-Tier چالشهای بخصوصی را برای قابلیتهای change-tracking در EF اضافه میکند. در ابتدا دادهها توسط یک آبجکت EF Context بارگذاری میشوند اما این آبجکت پس از ارسال دادهها به کلاینت از بین میرود. تغییراتی که در سمت کلاینت روی دادهها اعمال میشوند ردیابی (track) نخواهند شد. هنگام بروز رسانی، آبجکت Context جدیدی برای پردازش اطلاعات ارسالی باید ایجاد شود. مسلما آبجکت جدید هیچ چیز درباره Context پیشین یا مقادیر اصلی موجودیتها نمیداند.
در نسخههای قبلی Entity Framework توسعه دهندگان با استفاده از قالب ویژه ای بنام Self-Tracking Entities میتوانستند تغییرات موجودیتها را ردیابی کنند. این قابلیت در نسخه EF 6 از رده خارج شده است و گرچه هنوز توسط ObjectContext پشتیبانی میشود، آبجکت DbContext از آن پشتیبانی نمیکند.
در این سری از مقالات روی عملیات پایه CRUD تمرکز میکنیم که در اکثر اپلیکیشنهای n-Tier استفاده میشوند. همچنین خواهیم دید چگونه میتوان تغییرات موجودیتها را ردیابی کرد. مباحثی مانند همزمانی (concurrency) و مرتب سازی (serialization) نیز بررسی خواهند شد. در قسمت یک این سری مقالات، به بروز رسانی موجودیتهای منفصل (disconnected) توسط سرویسهای Web API نگاهی خواهیم داشت.
بروز رسانی موجودیتهای منفصل با Web API
سناریویی را فرض کنید که در آن برای انجام عملیات CRUD از یک سرویس Web API استفاده میشود. همچنین مدیریت دادهها با مدل Code-First پیاده سازی شده است. در مثال جاری یک کلاینت Console Application خواهیم داشت که یک سرویس Web API را فراخوانی میکند. توجه داشته باشید که هر اپلیکیشن در Solution مجزایی قرار دارد. تفکیک پروژهها برای شبیه سازی یک محیط n-Tier انجام شده است.
فرض کنید مدلی مانند تصویر زیر داریم.
همانطور که میبینید مدل جاری، سفارشات یک اپلیکیشن فرضی را معرفی میکند. میخواهیم مدل و کد دسترسی به دادهها را در یک سرویس Web API پیاده سازی کنیم، تا هر کلاینتی که از HTTP استفاده میکند بتواند عملیات CRUD را انجام دهد. برای ساختن سرویس مورد نظر مراحل زیر را دنبال کنید.
- در ویژوال استودیو پروژه جدیدی از نوع ASP.NET Web Application بسازید و قالب پروژه را Web API انتخاب کنید. نام پروژه را به Recipe1.Service تغییر دهید.
- کنترلر جدیدی از نوع WebApi Controller با نام OrderController به پروژه اضافه کنید.
- کلاس جدیدی با نام Order در پوشه مدلها ایجاد کنید و کد زیر را به آن اضافه نمایید.
public class Order { public int OrderId { get; set; } public string Product { get; set; } public int Quantity { get; set; } public string Status { get; set; } public byte[] TimeStamp { get; set; } }
- با استفاده از NuGet Package Manager کتابخانه Entity Framework 6 را به پروژه اضافه کنید.
- حال کلاسی با نام Recipe1Context ایجاد کنید و کد زیر را به آن اضافه نمایید.
public class Recipe1Context : DbContext { public Recipe1Context() : base("Recipe1ConnectionString") { } public DbSet<Order> Orders { get; set; } protected override void OnModelCreating(DbModelBuilder modelBuilder) { modelBuilder.Entity<Order>().ToTable("Orders"); // Following configuration enables timestamp to be concurrency token modelBuilder.Entity<Order>().Property(x => x.TimeStamp) .IsConcurrencyToken() .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Computed); } }
- فایل Web.config پروژه را باز کنید و رشته اتصال زیر را به قسمت ConnectionStrings اضافه نمایید.
<connectionStrings> <add name="Recipe1ConnectionString" connectionString="Data Source=.; Initial Catalog=EFRecipes; Integrated Security=True; MultipleActiveResultSets=True" providerName="System.Data.SqlClient" /> </connectionStrings>
- فایل Global.asax را باز کنید و کد زیر را به آن اضافه نمایید. این کد بررسی Entity Framework Compatibility را غیرفعال میکند.
protected void Application_Start() { // Disable Entity Framework Model Compatibilty Database.SetInitializer<Recipe1Context>(null); ... }
- در آخر کد کنترلر Order را با لیست زیر جایگزین کنید.
public class OrderController : ApiController { // GET api/order public IEnumerable<Order> Get() { using (var context = new Recipe1Context()) { return context.Orders.ToList(); } } // GET api/order/5 public Order Get(int id) { using (var context = new Recipe1Context()) { return context.Orders.FirstOrDefault(x => x.OrderId == id); } } // POST api/order public HttpResponseMessage Post(Order order) { // Cleanup data from previous requests Cleanup(); using (var context = new Recipe1Context()) { context.Orders.Add(order); context.SaveChanges(); // create HttpResponseMessage to wrap result, assigning Http Status code of 201, // which informs client that resource created successfully var response = Request.CreateResponse(HttpStatusCode.Created, order); // add location of newly-created resource to response header response.Headers.Location = new Uri(Url.Link("DefaultApi", new { id = order.OrderId })); return response; } } // PUT api/order/5 public HttpResponseMessage Put(Order order) { using (var context = new Recipe1Context()) { context.Entry(order).State = EntityState.Modified; context.SaveChanges(); // return Http Status code of 200, informing client that resouce updated successfully return Request.CreateResponse(HttpStatusCode.OK, order); } } // DELETE api/order/5 public HttpResponseMessage Delete(int id) { using (var context = new Recipe1Context()) { var order = context.Orders.FirstOrDefault(x => x.OrderId == id); context.Orders.Remove(order); context.SaveChanges(); // Return Http Status code of 200, informing client that resouce removed successfully return Request.CreateResponse(HttpStatusCode.OK); } } private void Cleanup() { using (var context = new Recipe1Context()) { context.Database.ExecuteSqlCommand("delete from [orders]"); } } }
در قدم بعدی اپلیکیشن کلاینت را میسازیم که از سرویس Web API استفاده میکند.
- در ویژوال استودیو پروژه جدیدی از نوع Console Application بسازید و نام آن را به Recipe1.Client تغییر دهید.
- کلاس موجودیت Order را به پروژه اضافه کنید. همان کلاسی که در سرویس Web API ساختیم.
نکته: قسمت هایی از اپلیکیشن که باید در لایههای مختلف مورد استفاده قرار گیرند - مانند کلاسهای موجودیتها - بهتر است در لایه مجزایی قرار داده شده و به اشتراک گذاشته شوند. مثلا میتوانید پروژه ای از نوع Class Library بسازید و تمام موجودیتها را در آن تعریف کنید. سپس لایههای مختلف این پروژه را ارجاع خواهند کرد.
فایل program.cs را باز کنید و کد زیر را به آن اضافه نمایید.
private HttpClient _client; private Order _order; private static void Main() { Task t = Run(); t.Wait(); Console.WriteLine("\nPress <enter> to continue..."); Console.ReadLine(); } private static async Task Run() { // create instance of the program class var program = new Program(); program.ServiceSetup(); program.CreateOrder(); // do not proceed until order is added await program.PostOrderAsync(); program.ChangeOrder(); // do not proceed until order is changed await program.PutOrderAsync(); // do not proceed until order is removed await program.RemoveOrderAsync(); } private void ServiceSetup() { // map URL for Web API cal _client = new HttpClient { BaseAddress = new Uri("http://localhost:3237/") }; // add Accept Header to request Web API content // negotiation to return resource in JSON format _client.DefaultRequestHeaders.Accept. Add(new MediaTypeWithQualityHeaderValue("application/json")); } private void CreateOrder() { // Create new order _order = new Order { Product = "Camping Tent", Quantity = 3, Status = "Received" }; } private async Task PostOrderAsync() { // leverage Web API client side API to call service var response = await _client.PostAsJsonAsync("api/order", _order); Uri newOrderUri; if (response.IsSuccessStatusCode) { // Capture Uri of new resource newOrderUri = response.Headers.Location; // capture newly-created order returned from service, // which will now include the database-generated Id value _order = await response.Content.ReadAsAsync<Order>(); Console.WriteLine("Successfully created order. Here is URL to new resource: {0}", newOrderUri); } else Console.WriteLine("{0} ({1})", (int)response.StatusCode, response.ReasonPhrase); } private void ChangeOrder() { // update order _order.Quantity = 10; } private async Task PutOrderAsync() { // construct call to generate HttpPut verb and dispatch // to corresponding Put method in the Web API Service var response = await _client.PutAsJsonAsync("api/order", _order); if (response.IsSuccessStatusCode) { // capture updated order returned from service, which will include new quanity _order = await response.Content.ReadAsAsync<Order>(); Console.WriteLine("Successfully updated order: {0}", response.StatusCode); } else Console.WriteLine("{0} ({1})", (int)response.StatusCode, response.ReasonPhrase); } private async Task RemoveOrderAsync() { // remove order var uri = "api/order/" + _order.OrderId; var response = await _client.DeleteAsync(uri); if (response.IsSuccessStatusCode) Console.WriteLine("Sucessfully deleted order: {0}", response.StatusCode); else Console.WriteLine("{0} ({1})", (int)response.StatusCode, response.ReasonPhrase); }
Successfully updated order: OK
Sucessfully deleted order: OK
شرح مثال جاری
با اجرای اپلیکیشن Web API شروع کنید. این اپلیکیشن یک کنترلر Web API دارد که پس از اجرا شما را به صفحه خانه هدایت میکند. در این مرحله اپلیکیشن در حال اجرا است و سرویسهای ما قابل دسترسی هستند.
حال اپلیکیشن کنسول را باز کنید. روی خط اول کد program.cs یک breakpoint تعریف کرده و اپلیکیشن را اجرا کنید. ابتدا آدرس سرویس Web API را پیکربندی کرده و خاصیت Accept Header را مقدار دهی میکنیم. با این کار از سرویس مورد نظر درخواست میکنیم که دادهها را با فرمت JSON بازگرداند. سپس یک آبجکت Order میسازیم و با فراخوانی متد PostAsJsonAsync آن را به سرویس ارسال میکنیم. این متد روی آبجکت HttpClient تعریف شده است. اگر به اکشن متد Post در کنترلر Order یک breakpoint اضافه کنید، خواهید دید که این متد سفارش جدید را بعنوان یک پارامتر دریافت میکند و آن را به لیست موجودیتها در Context جاری اضافه مینماید. این عمل باعث میشود که آبجکت جدید بعنوان Added علامت گذاری شود، در این مرحله Context جاری شروع به ردیابی تغییرات میکند. در آخر با فراخوانی متد SaveChanges دادهها را ذخیره میکنیم. در قدم بعدی کد وضعیت 201 (Created) و آدرس منبع جدید را در یک آبجکت HttpResponseMessage قرار میدهیم و به کلاینت ارسال میکنیم. هنگام استفاده از Web API باید اطمینان حاصل کنیم که کلاینتها درخواستهای ایجاد رکورد جدید را بصورت POST ارسال میکنند. درخواستهای HTTP Post بصورت خودکار به اکشن متد متناظر نگاشت میشوند.
در مرحله بعد عملیات بعدی را اجرا میکنیم، تعداد سفارش را تغییر میدهیم و موجودیت جاری را با فراخوانی متد PutAsJsonAsync به سرویس Web API ارسال میکنیم. اگر به اکشن متد Put در کنترلر سرویس یک breakpoint اضافه کنید، خواهید دید که آبجکت سفارش بصورت یک پارامتر دریافت میشود. سپس با فراخوانی متد Entry و پاس دادن موجودیت جاری بعنوان رفرنس، خاصیت State را به Modified تغییر میدهیم، که این کار موجودیت را به Context جاری میچسباند. حال فراخوانی متد SaveChanges یک اسکریپت بروز رسانی تولید خواهد کرد. در مثال جاری تمام فیلدهای آبجکت Order را بروز رسانی میکنیم. در شمارههای بعدی این سری از مقالات، خواهیم دید چگونه میتوان تنها فیلدهایی را بروز رسانی کرد که تغییر کرده اند. در آخر عملیات را با بازگرداندن کد وضعیت 200 (OK) به اتمام میرسانیم.
در مرحله بعد، عملیات نهایی را اجرا میکنیم که موجودیت Order را از منبع داده حذف میکند. برای اینکار شناسه (Id) رکورد مورد نظر را به آدرس سرویس اضافه میکنیم و متد DeleteAsync را فراخوانی میکنیم. در سرویس Web API رکورد مورد نظر را از دیتابیس دریافت کرده و متد Remove را روی Context جاری فراخوانی میکنیم. این کار موجودیت مورد نظر را بعنوان Deleted علامت گذاری میکند. فراخوانی متد SaveChanges یک اسکریپت Delete تولید خواهد کرد که نهایتا منجر به حذف شدن رکورد میشود.
در یک اپلیکیشن واقعی بهتر است کد دسترسی دادهها از سرویس Web API تفکیک شود و در لایه مجزایی قرار گیرد.
مروری بر نحوهی توزیع برنامههای Blazor بر روی IIS
1- پیش از هر کاری باید مطابق نگارش ASP.NET Core در حال استفاده (که به عنوان هاست Blazor Server و یا ارائه دهندهی قسمت Web API برنامهی سمت کلاینت WASM مطرح است)، بستهی NET Core hosting bundle. را نصب کرد که عموما تحت عنوان «Hosting Bundle Installer» قابل دریافت است.
نکتهی مهم: همانند تمام نگارشهای دات نت، در اینجا نیز باید Hosting Bundle را پس از نصب IIS، بر روی سیستم نصب کرد. اگر این ترتیب تغییر کند، یکبار دیگر نصاب آنرا اجرا کرده و گزینهی ترمیم نصب را انتخاب کنید تا یکپارچگی آن با IIS صورت گیرد.
2- نیاز است برنامهی خود را اصطلاحا publish کرد تا به همراه فایلهای نهایی قابل کپی باشد که در پوشهای توسط IIS هاست خواهند شد. برای اینکار اگر از نگارش کامل ویژوال استودیو استفاده میکنید، فقط کافی است بر روی پروژهی مدنظر کلیک راست کرده و از منوی باز شده، گزینهی publish را انتخاب کنید و مراحل آنرا طی نمائید و یا این مراحل را میتوان توسط دستور خط فرمان زیر نیز خلاصه کرد که وابستگی خاصی، به IDE ویژهای ندارد و چند سکویی است:
dotnet publish -o "c:\dir1\dir2" -c Release
و یا اگر فقط دستور dotnet publish -c Release را در ریشهی پروژه اجرا کنیم، خروجی نهایی را در پوشهی bin\Release\net5.0\publish میتوان مشاهده کرد که به همراه یک web.config مخصوص برنامههای blazor هم هست و در آن mime typeهای متناظری، به همراه URL rewriting مناسب برنامههای تک صفحهای وب از پیش تنظیم شدهاست. بنابراین در اینجا نصب ماژول URL rewrite بر روی IIS نیز الزامی است.
3- در اینجا نیز همانند تنظیمات برنامههای ASP.NET Core، باید application pool منتسب به برنامه را ویرایش کرده و NET Clr Version. آنرا بر روی No Managed Code قرار داد.
روش فعالسازی توزیع مبتنی بر فشرده سازی Brotli در IIS
در حین عملیات publish استاندارد، به صورت پیشفرض از تمام فایلها، سه نسخهی اصلی، gz شده (gzip) و یا br شده (فشرده سازی Brotli که فایلهای کم حجمتری را نسبت به gz ارائه میدهد) نیز تهیه میشوند که بسته به نوع مرورگر و پشتیبانی آن از روشهای مختلف فشرده سازی، یکی از آنها در اختیار کلاینت قرار خواهد گرفت که به این صورت کاربران، تجربهی دریافت کم حجمتر و سریعتری را خواهند داشت.
باید دقت داشت Web.config ای که به همراه دستور dotnet publish ایجاد میشود، روش توزیع پیشفرض فایلهای br. تولیدی را ندارد. برای اینکار نیاز است تنظیمات این فایل web.config توصیه شدهی توسط تیم Blazor را به web.config خود اضافه کرد تا در نهایت حجم دریافتی از سرور به شدت کاهش یابد.
یک نکته: اگر میخواهید فایل web.config سفارشی خودتان را داشته باشید، نمونهای از آنرا در ریشهی پروژه قرار داده و سپس فایل csproj را به نحو زیر ویرایش کنید تا از آن در حین publish استفاده کند:
<PropertyGroup> <PublishIISAssets>true</PublishIISAssets> </PropertyGroup>
در حین publish برنامههای Blazor WASM کار IL trimming نیز انجام میشود
برای کاهش حجم نهایی برنامههای Blazor WASM، در حین publish در حالت release، کار IL Trimming نیز به صورت خودکار انجام میشود تا کدهای IL ای که در برنامه نقش نداشتهاند و مستقیما در جائی استفاده نشدهاند، به صورت خودکار حذف شوند و به این ترتیب حجم ارائهی نهایی به شدت کاهش یابد.
فقط باید دقت داشت که در این حالت اگر عملیات پویایی مانند reflection در کدهای شما صورت میگیرد، به علت نداشتن ارجاع استاتیکی به منابع مورد استفاده، در زمان اجرا با مشکل مواجه خواهد شد. اگر میخواهید اخطارهایی را در این زمینه مشاهده کنید، گزینهی زیر را به فایل csproj اضافه نمائید:
<SuppressTrimAnalysisWarnings>false</SuppressTrimAnalysisWarnings>
حذف مباحث بومی سازی در صورت عدم نیاز
اگر در برنامهی خود از مباحث time-zones استفاده نمیکنید، میتوانید با غیرفعال کردن آن در فایل csproj، حداقل 100 کیلوبایت از حجم برنامهی نهایی را کاهش دهید:
<BlazorEnableTimeZoneSupport>false</BlazorEnableTimeZoneSupport>
<InvariantGlobalization>true</InvariantGlobalization>