Why use View Components and not Partial Views? The biggest reason is that when inserting a Partial View into a Razor page, all the ViewData associated with the calling View is automatically associated with the Partial View. This means that a Partial View may behave very differently on one Razor page than on another. With View Components, you control what gets shared to your View Components.
VSCode برای توسعه دهندگان سیشارپ
VSCode for the C# Developer - Tim Corey - NDC London 2023
VSCode is a nimble editor that can do just about anything. In this session, we will set up and configure VSCode for use in C# development. Then we will use it to build, debug, and deploy a small .NET Core web application to Azure.
Along the way, we will go over a list of the top C#-focused plugins for VSCode. Whether you are just getting started with VSCode or you are used to VSCode but want to start building C# projects, this session will get you up to speed fast.
using System; using System.Threading.Tasks; namespace Listing_01 { class Listing_01 { static void Main(string[] args) { Task.Factory.StartNew(() => { Console.WriteLine("Hello World"); }); // wait for input before exiting Console.WriteLine("Main method complete. Press enter to finish."); Console.ReadLine(); } }
در کد بالا کلاس Task نقش اصلی را بازی میکند.این کلاس قلب کتابخانه برنامه نویسی Task یا Task Programming Library میباشد.
در این بخش با موارد زیر در مورد Taskها آشنا میشویم:
- ایجاد و به کار انداختن انواع مختلف Task ها.
- کنسل کردن Task ها.
- منتظر شدن برای پایان یک Task.
- دریافت خروجی یا نتیجه از یک Task پایان یافته.
- مدیریت خطا در طول انجام یک Task
خب بهتر است به شرح کد بالا بپردازیم:
رای استفاده از کلاس Task باید فضای نام System.Threading.Tasks را بصورت ریر مورد استفاده قرار دهیم.
using System.Threading.Tasks;
System.Threading . اگر با برنامه نویسی تریدها بروش مرسوم وکلاسیک آشنایی دارید قطعاً با این فضای نام آشنایی دارید. اگر بخواهیم با چندین Task بطور همزمان کار کنیم به این فضای نام نیاز مبرم داریم. پس :
using System.Threading;
Task.Factory.StartNew(() => { Console.WriteLine("Hello World"); });
در بخشهای بعدی چگونگی ایجاد Taskهای پیچیدهتر را بررسی خواهیم کرد . خروجی برنامه بالا بصورت زیر خواهد بود:
Main method complete. Press enter to finish. Hello World
- ایجاد کلاس Task با استفاده از یک متد دارای نام که در داخل یک کلاس Action صدا زده میشود. مثال :
Task task1 = new Task(new Action(printMessage));
Task task2 = new Task(delegate { printMessage(); });
Task task3 = new Task(() => printMessage());
Task task4 = new Task(() => { printMessage(); });
using System; using System.Threading.Tasks; namespace Listing_02 { class Listing_02 { static void Main(string[] args) { // use an Action delegate and a named method Task task1 = new Task(new Action(printMessage)); // use a anonymous delegate Task task2 = new Task(delegate { printMessage(); }); // use a lambda expression and a named method Task task3 = new Task(() => printMessage()); // use a lambda expression and an anonymous method Task task4 = new Task(() => { printMessage(); }); task1.Start(); task2.Start(); task3.Start(); task4.Start(); // wait for input before exiting Console.WriteLine("Main method complete. Press enter to finish."); Console.ReadLine(); } static void printMessage() { Console.WriteLine("Hello World"); } } }
Main method complete. Press enter to finish. Hello World Hello World Hello World Hello World
نکته 2 : اگر یک Taskدر حال اجرا باشد نمیتوان آنرا دوباره استارت نمود باید برای یک نمونه جدید از آن Task ایجاد نمود و آنرا استارت کرد.
// The getDate() function returns a nested function which refers the 'date' variable defined // by outer function getDate() function getDate() { var date = new Date(); // This variable stays around even after function returns // nested function return function () { return date.getMilliseconds(); } }
// Once getDate() is executed the variable date should be out of scope and it is, but since // the inner function // referenes date, this value is available to the inner function. var dt = getDate(); alert(dt()); alert(dt());
function myNonClosure() { var date = new Date(); return date.getMilliseconds(); }
var MyDate = function () { var date = new Date(); var getMilliSeconds = function () { return date.getMilliseconds(); } } var dt = new MyDate(); alert(dt.getMilliSeconds()); // This will throw error as getMilliSeconds is not accessible.
// This is closure var MyDate = function () { var date = new Date(); // variable stays around even after function returns var getMilliSeconds = function () { return date.getMilliseconds(); }; return { getMs : getMilliSeconds } }
var dt = new MyDate(); alert(dt.getMs()); // This should work.
(function($) { // $() is available here })(jQuery);
// file1.js function saveState(obj) { // write code here to saveState of some object alert('file1 saveState'); } // file2.js (remote team or some third party scripts) function saveState(obj, obj2) { // further code... alert('file2 saveState"); }
<script src="file1.js" type="text/javascript"></script> <script src="file2.js" type="text/javascript"></script>
function App() { var save = function (o) { // write code to save state here.. // you have acces to 'o' here... alert(o); }; return { saveState: save }; }
var app = new App(); app.saveState({ name: "rajesh"});
پروژهی DNTIdentity به ASP.NET Core 2.1 ارتقاء داده شد.
- مشاهدهی لیست کامل تغییرات
برای اجرای آن فقط کافی است:
- ابتدا SDK جدید را نصب کنید.
- سپس مجوز SSL آنرا تبدیل به مجوز امن و قابل اطمینان کنید.
- در ادامه به پوشهی DataLayer مراجعه کرده و ابتدا دستور dotnet restore را صادر کنید. بعد از آن دو فایل cmd موجود در آنرا اجرا کنید. فایل اول مهاجرتها را تولید میکند و فایل دوم، آنها را به بانک اطلاعاتی از نوع LocalDB اعمال خواهد کرد. بانک اطلاعاتی تولید شده را در پوشهی wwwroot/App_Data میتوانید مشاهده کنید.
- در آخر به پوشهی اصلی برنامه مراجعه کرده و دو فایل bat موجود در آنرا اجرا کنید. اولی وابستگیها را بازیابی میکند و دومی برنامه را کامپایل کرده و سپس بر روی پورت SSL 5001 ارائه میدهد که بلافاصله در مرورگر قابل مشاهده خواهد بود.
برای اجرای این مراحل نیاز به IDE خاصی ندارید. همینقدر که SDK جدید را نصب کرده باشید، کافی است.
Senior Developer .Net Technologies Resume : ------------------------------------
احتمالا اکثر شما هم در آگهیهای استخدام برنامه نویس، با واژه Senior Developer یا برنامه نویس ارشد برخورد داشته اید. حال به راستی به چه کسی برنامه نویس ارشد گفته میشود. یا بهتره بگم که چه زمانی ما میتونیم خودمون را یک برنامه نویس ارشد بدونیم؟
از آنجا که تعریف مشخصی برای این واژهها در فرهنگ خاصی وجود ندارد و عموما هر کسی یک تعریف خاص با توجه به سلیقه خودش در این زمینه ارائه میدهد نمیتوان یک شرح واحد از این واژهها نظیر Senior Developer یا حتی Junior Developer ارائه داد. در نتیجه قصد دارم با توجه به تجربیات شخصی، این موارد رو تشریح کنم.
در خیلی موارد منظور از برنامه نویس ارشد، کسی است که:
- در حداقل یک یا دو زبان برنامه نویسی تجربه داشته باشد؛
- با تکنولوژیهای مهم و کارآمد ارائه شده در آن زبان آشنایی کامل داشته باشد؛
- توانایی استفاده از این تکنولوژیها را در جای مناسب پروژه دارا باشد؛
- با الگوهای برنامه نویسی (Design Pattern) آشنایی کامل داشته باشد؛
- بتواند به سایر اعضای تیم در حل مشکلات و باگهای برنامه کمک کند؛
- یک خطایاب قهار باشد(منظور این است با اکثر استثنائات و خطاهای متداول زبان و تکنولوژی که پروژه با آن پیاده سازی میشود آشنا باشد و بداند که چه زمان این خطا به وجود میآید و چگونه میتوان این موارد را برطرف کرد)؛
- باید با پیاده سازی سیستمهای سرویس گرا (SOA) جدای از تکنولوژی پیاده سازی آن آشنایی کامل داشته باشد؛
- باید با روشهای تست و خطایابی و ابزارهای توسعه و پیاده سازی آن آشنا بوده و توانایی استفاده از آنها را در پروژههای خود داشته باشد؛
- یک برنامه نویس ارشد باید در زمینه تخمین زمان تکمیل پروژه مهارت داشته باشد. باید بتواند مدت زمان لازم برای تکمیل یک Task را تخمین بزند و در همین مدت زمان، Task مربوطه را به صورت کامل پیاده سازی نماید.
- در خیلی موارد باید بتواند هماهنگی لازم را بین اعضای تیم برنامه نویسی ایجاد نماید؛
- و...
*ترتیب عبارات بالا دلیلی بر اولویت موارد ذکر شده نیست.
*منظور از آشنایی در عبارات بالا، یعنی تسلط و توانایی استفاده از آن با آگاهی تمام.
چه مدت زمان برای تبدیل شدن به یک برنامه نویس ارشد نیاز است؟
برای به دست آوردن مهارتها و تواناییهای یک برنامه نویس ارشد لازم است که حداقل 8 تا 10 سال تجربه برنامه نویسی در یک زبان را داشته باشید. البته این در مورد همگان صادق نیست . شاید کسی بتواند این راه را در کمتر از 8 سال طی نماید. این بستگی به تلاش و استعداد فرد دارد. اما تجربه بیشتر یعنی شرکت در پروژههای بیشتر و آمادگی بیشتر در حل مشکلات و مسائل مختلف در طی روند تکمیل پروژه. برای مثال در زمینه تخمین اجرای یک Task داشتن تجربه بیشتر خیلی به شما کمک خواهد کرد.
در پایان چهار ردیف یا رده مختلف را که بین برنامه نویسان رواج دارد ذکر میکنم.(از پایین به بالا)
Junior : عموما به کسی گفته میشود که 1 تا 3 سال تجربه برنامه نویسی دارد. معمولا کدهای نوشته شده توسط این افراد باید بررسی شود چون احتمال اشتباه در آن زیاد است. اکثر کدهای نوشته شده توسط این افراد به صورت dirty code است. راهنمایی هایی که به این افراد داده میشود شامل راهنمایی در زمینه ساختاری و الگوریتمی نیز میباشد.
Mid-Level : برنامه نویسان در این رده بین 4 تا 6 سال تجربه دارند. میتوانند به تنهایی نیز یک مشکل موجود در پروژه را حل نمایند. با مباحث مربوطه به طراحی کامپوننت آشنایی دارند و پروژه را بی نیاز از کامپوننت خواهند کرد. حتی در بعضی موارد میتوانند به تنهایی یک پروژه در سطح کوچک با متوسط را توسعه دهند.
Senior Developer : در بالا به صورت کامل شرح داده شد.
Luminary : به صورت معمول به کسی گفته میشود که تمام تواناییهای یک برنامه نویس ارشد را داراست و فقط تجربه برنامه نویسی آن قطعا از 10 سال بیشتر است.
از این واژه کمتر در ردههای برنامه نویسی استفاده میشود و بیشتر به همان واژه Senior Developer بسنده میکنند.
Blazor 5x - قسمت 19 - کار با فرمها - بخش 7 - نکات ویژهی کار با EF-Core در برنامههای Blazor Server
طول عمر سرویسها، در برنامههای Blazor Server متفاوت هستند
هنگامیکه با یک ASP.NET Core Web API متداول کار میکنیم، درخواستهای HTTP رسیده، از میانافزارهای موجود رد شده و پردازش میشوند. اما هنگامیکه با Blazor Server کار میکنیم، به علت وجود یک اتصال دائم SignalR که عموما از نوع Web socket است، دیگر درخواست HTTP وجود ندارد. تمام رفت و برگشتهای برنامه به سرور و پاسخهای دریافتی، از طریق Web socket منتقل میشوند و نه درخواستها و پاسخهای متداول HTTP.
این روش پردازشی، اولین تاثیری را که بر روی رفتار یک برنامه میگذارد، تغییر طول عمر سرویسهای آن است. برای مثال در برنامههای Web API، طول عمر درخواستها، از نوع Scoped هستند و با شروع پردازش یک درخواست، سرویسهای مورد نیاز وهله سازی شده و در پایان درخواست، رها میشوند.
این مساله در حین کار با EF-Core نیز بسیار مهم است؛ از این جهت که در برنامههای Web API نیز EF-Core و DbContext آن، به صورت سرویسهایی با طول عمر Scoped تعریف میشوند. برای مثال زمانیکه یک چنین تعریفی را در برنامه داریم:
services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(connectionString));
public static IServiceCollection AddDbContext<TContext>( [NotNullAttribute] this IServiceCollection serviceCollection, [CanBeNullAttribute] Action<DbContextOptionsBuilder> optionsAction = null, ServiceLifetime contextLifetime = ServiceLifetime.Scoped, ServiceLifetime optionsLifetime = ServiceLifetime.Scoped) where TContext : DbContext;
اما زمانیکه مانند یک برنامهی مبتنی بر Blazor Server، دیگر HTTP Requests متداولی را نداریم، چطور؟ در این حالت زمانیکه یک اتصال SignalR برقرار شد، وهلهای از DbContext که در اختیار برنامهی Blazor Server قرار میگیرد، تا زمانیکه کاربر این اتصال را به نحوی قطع نکرده (مانند بستن کامل مرورگر و یا ریفرش صفحه)، ثابت باقی خواهد ماند. یعنی به ازای هر اتصال SignalR، طول عمر ServiceLifetime.Scoped پیشفرض تعریف شده، همانند یک وهلهی با طول عمر Singleton عمل میکند. در این حالت تمام صفحات و کامپوننتهای یک برنامهی Blazor Server، از یک تک وهلهی مشخص DbContext که در ابتدای کار دریافت کردهاند، کار میکنند و از آنجائیکه DbContext به صورت thread-safe کار نمیکند، این تک وهله مشکلات زیادی را ایجاد خواهد کرد که یک نمونه از آنرا در عمل، در پایان قسمت قبل مشاهده کردید:
«اگر برنامه را اجرا کرده و سعی در حذف یک ردیف کنیم، به خطای زیر میرسیم و یا حتی اگر کاربر شروع کند به کلیک کردن سریع در قسمتهای مختلف برنامه، باز هم این خطا مشاهده میشود:
An exception occurred while iterating over the results of a query for context type 'BlazorServer.DataAccess.ApplicationDbContext'. System.InvalidOperationException: A second operation was started on this context before a previous operation completed. This is usually caused by different threads concurrently using the same instance of DbContext. For more information on how to avoid threading issues with DbContext, see https://go.microsoft.com/fwlink/?linkid=2097913.
هر درخواست Web API نیز بر روی یک ترد جداگانه اجرا میشود؛ اما چون ابتدا و انتهای درخواستها مشخص است، طول عمر Scoped، در ابتدای درخواست شروع شده و در پایان آن رها سازی میشود. به همین جهت استثنائی را که در اینجا مشاهده میکنید، در برنامههای Web API شاید هیچگاه مشاهده نشود.
معرفی DbContextFactory در EF Core 5x
همواره باید طول عمر DbContext را تا جای ممکن، کوتاه نگه داشت. مشکل فعلی ما، Singleton رفتار کردن DbContextها (داشتن طول عمر طولانی) در برنامههای Blazor Server هستند. یک چنین رفتاری را شاید در برنامههای دسکتاپ هم پیشتر مشاهده کرده باشید. برای مثال در برنامههای دسکتاپ WPF، تا زمانیکه یک فرم باز است، Context ایجاد شدهی در آن هم برقرار است و Dispose نمیشود. در یک چنین حالتهایی، عموما Context را در زمان نیاز، ایجاد کرده و پس از پایان آن کار کوتاه، Context را رها میکنند. به همین جهت نیاز به DbContext Factory ای وجود دارد که بتواند یک چنین پیاده سازیهایی را میسر کند و خوشبختانه از زمان EF Core 5x، یک چنین امکانی خصوصا برای برنامههای Blazor Server تحت عنوان DbContextFactory ارائه شدهاست که به عنوان راه حل استاندارد دسترسی به DbContext در اینگونه برنامهها مورد استفاده قرار میگیرد.
برای کار با DbContextFactory، اینبار در فایل BlazorServer.App\Startup.cs، بجای استفاده از services.AddDbContext، از متد AddDbContextFactory استفاده میشود:
public void ConfigureServices(IServiceCollection services) { var connectionString = Configuration.GetConnectionString("DefaultConnection"); //services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(connectionString)); services.AddDbContextFactory<ApplicationDbContext>(options => options.UseSqlServer(connectionString));
روش اول کار با DbContextFactory در کامپوننتهای Blazor Server : وهله سازی از نو، به ازای هر متد
در این روش پس از ثبت AddDbContextFactory در فایل Startup برنامه مانند مثال فوق، ابتدا سرویس IDbContextFactory که به ApplicationDbContext اشاره میکند به ابتدای کامپوننت تزریق میشود:
@inject IDbContextFactory<ApplicationDbContext> DbFactory
private async Task DeleteImageAsync() { using var context = DbFactory.CreateDbContext(); var image = await context.HotelRoomImages.FindAsync(1); // ... }
روش دوم کار با DbContextFactory در کامپوننتهای Blazor Server : یکبار وهله سازی Context به ازای هر کامپوننت
در این روش میتوان طول عمر Context را معادل طول عمر کامپوننت تعریف کرد که مزیت استفادهی از Change tracking موجود در EF-Core را به همراه خواهد داشت. در این حالت کامپوننتهای Blazor Server، شبیه به فرمهای برنامههای دسکتاپ عمل میکنند:
@implements IDisposable @inject IDbContextFactory<ApplicationDbContext> DbFactory @code { private ApplicationDbContext Context; protected override async Task OnInitializedAsync() { Context = DbFactory.CreateDbContext(); await base.OnInitializedAsync(); } private async Task DeleteImageAsync() { var image = await Context.HotelRoomImages.FindAsync(1); // ... } public void Dispose() { Context.Dispose(); } }
- اما بجای اینکه به ازای هر متد، کار فراخوانی DbFactory.CreateDbContext صورت گیرد، یکبار در آغاز کار کامپوننت و در روال رویدادگردان OnInitializedAsync، کار وهله سازی Context کامپوننت انجام شده و از این تک Context در تمام متدهای کامپوننت استفاده خواهد شد.
- در این حالت کار Dispose خودکار این Context به متد Dispose نهایی کل کامپوننت واگذار شدهاست. برای اینکه این متد فراخوانی شود، نیاز است در ابتدای تعاریف کامپوننت، از دایرکتیو implements IDisposable@ استفاده کرد.
سؤال: اگر سرویسی از ApplicationDbContext تزریق شدهی در سازندهی خود استفاده میکند، چکار باید کرد؟
برای نمونه سرویسهای از پیش تعریف شدهی ASP.NET Core Identity، در سازندهی خود از ApplicationDbContext استفاده میکنند و نه از IDbContextFactory. در این حالت برای تامین ApplicationDbContextهای تزریق شده، فقط کافی است از روش زیر استفاده کنیم:
services.AddScoped<ApplicationDbContext>(serviceProvider => serviceProvider.GetRequiredService<IDbContextFactory<ApplicationDbContext>>().CreateDbContext());
سؤال: روش پیاده سازی سرویسهای یک برنامه Blazor Server به چه صورتی باید تغییر کند؟
تا اینجا روشهایی که برای استفاده از IDbContextFactory معرفی شدند (که روشهای رسمی و توصیه شدهی اینکار نیز هستند)، فرض را بر این گذاشتهاند که ما قرار است تمام منطق تجاری کار با بانک اطلاعاتی را داخل همان متدهای کامپوننتها انجام دهیم (این روش برنامه نویسی، بسیار مورد علاقهی مایکروسافت است و در تمام مثالهای رسمی آن به صورت ضمنی توصیه میشود!). اما اگر همانند مثالی که تاکنون در این سری بررسی کردیم، نخواهیم اینکار را انجام دهیم و علاقمند باشیم تا این منطق تجاری را به سرویسهای مجزایی، با مسئولیتهای مشخصی انتقال دهیم، روش استفادهی از IDbContextFactory چگونه خواهد بود؟
در این حالت از ترکیب روش دوم مطرح شدهی استفاده از IDbContextFactory که به همراه مزیت دسترسی کامل به Change Tracking توکار EF-Core و پیاده سازی الگوی واحد کار است و وهله سازی خودکار ApplicationDbContext که معرفی شد، استفاده خواهیم کرد؛ به این صورت:
الف) تمام سرویسهای EF-Core یک برنامهی Blazor Server باید اینترفیس IDisposable را پیاده سازی کنند.
این مورد برای سرویسهای پروژههای Web API، ضروری نیست؛ چون طول عمر Context آنها توسط خود IoC Container مدیریت میشود؛ اما در برنامههای Blazor Server، مطابق توضیحاتی که ارائه شد، خودمان باید این طول عمر را مدیریت کنیم.
بنابراین به پروژهی سرویسهای برنامه مراجعه کرده و هر سرویسی که ApplicationDbContext تزریق شدهای را در سازندهی خود میپذیرد، یافته و تعریف اینترفیس آنرا به صورت زیر تغییر میدهیم:
public interface IHotelRoomService : IDisposable { // ... } public interface IHotelRoomImageService : IDisposable { // ... }
public class HotelRoomService : IHotelRoomService { private bool _isDisposed; // ... public void Dispose() { Dispose(disposing: true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (!_isDisposed) { try { if (disposing) { _dbContext.Dispose(); } } finally { _isDisposed = true; } } } }
ب) Dispose دستی تمام سرویسها، در کامپوننتهای مرتبط
در ادامه تمام کامپوننتهایی را که از سرویسهای فوق استفاده میکنند یافته و ابتدا دایرکتیو implements IDisposable@ را به ابتدای آنها اضافه میکنیم. سپس متد Dispose آنها را جهت فراخوانی متد Dispose سرویسهای فوق، تکمیل خواهیم کرد:
بنابراین ابتدا به فایل BlazorServer\BlazorServer.App\Pages\HotelRoom\HotelRoomUpsert.razor مراجعه کرده و تغییرات زیر را اعمال میکنیم:
@page "/hotel-room/create" @page "/hotel-room/edit/{Id:int}" @implements IDisposable // ... @code { // ... public void Dispose() { HotelRoomImageService.Dispose(); HotelRoomService.Dispose(); } }
@page "/hotel-room" @implements IDisposable // ... @code { // ... public void Dispose() { HotelRoomService.Dispose(); } }
مشکل! اینبار خطای dispose شدن context را دریافت میکنیم!
System.ObjectDisposedException: Cannot access a disposed context instance. A common cause of this error is disposing a context instance that was resolved from dependency injection and then later trying to use the same context instance elsewhere in your application. This may occur if you are calling 'Dispose' on the context instance, or wrapping it in a using statement. If you are using dependency injection, you should let the dependency injection container take care of disposing context instances. Object name: 'ApplicationDbContext'.
مشکلی که در اینجا رخ داده این است که سرویسهایی را داریم با طول عمر به ظاهر Scoped که یکی از وابستگیهای آنها را به صورت دستی Dispose کردهایم. چون طول عمر Scoped در اینجا وجود ندارد و طول عمرها در اصل Singleton هستند، هربار که سرویس مدنظر مجددا درخواست شود، همان وهلهی ابتدایی که اکنون یکی از وابستگیهای آن Dispose شده، در اختیار برنامه قرار میگیرد.
پس از این تغییرات، اولین باری که برنامه را اجرا میکنیم، لیست اتاقها به خوبی نمایش داده میشوند و مشکلی نیست. بعد در همین حال و در همین صفحه، اگر بر روی دکمهی افزودن یک اتاق جدید کلیک کنیم، اتفاقی که رخ میدهد، فراخوانی متد Dispose کامپوننت لیست اتاقها است (بر روی آن یک break-point قرار دهید). بنابراین متد Dispose یک کامپوننت، با هدایت به یک مسیر دیگر، به صورت خودکار فراخوانی میشود. در این حالت Context برنامه Dispose شده و در کامپوننت ثبت یک اتاق جدید دیگر، در دسترس نخواهد بود؛ چون IHotelRoomService مورد استفاده مجددا وهله سازی نمیشود و از همان وهلهای که بار اول ایجاد شده، استفاده خواهد شد.
بنابراین سؤال اینجا است که چگونه میتوان سیستم تزریق وابستگیها را وادار کرد تا تمام سرویسهای تزریق شدهی به سازندههای سرویسهای HotelRoomService و HotelRoomImageService را مجددا وهله سازی کند و سعی نکند از همان وهلههای قبلی استفاده کند؟
پاسخ: یک روش این است که IHotelRoomImageService را خودمان به ازای هر کامپوننت به صورت دستی در روال رویدادگردان OnInitializedAsync وهله سازی کرده و DbFactory.CreateDbContext جدیدی را مستقیما به سازندهی آن ارسال کنیم. در این حالت مطمئن خواهیم شد که این وهله، جای دیگری به اشتراک گذاشته نمیشود:
@code { private IHotelRoomImageService HotelRoomImageService; protected override async Task OnInitializedAsync() { HotelRoomImageService = new HotelRoomImageService(DbFactory.CreateDbContext(), mapper); await base.OnInitializedAsync(); } private async Task DeleteImageAsync() { await HotelRoomImageService.DeleteAsync(1); // ... } public void Dispose() { HotelRoomImageService.Dispose(); } }
وادار کردن Blazor Server به وهله سازی مجدد سرویسهای کامپوننتها
بنابراین مشکل ما Singleton رفتار کردن سرویسها، در برنامههای Blazor است. برای مثال در برنامههای Blazor Server، تا زمانیکه اتصال SignalR برنامه برقرار است (مرورگر بسته نشده، برگهی جاری بسته نشده و یا کاربر صفحه را ریفرش نکرده)، هیچ سرویسی دوباره وهله سازی نمیشود.
برای رفع این مشکل، امکان Scoped رفتار کردن سرویسهای یک کامپوننت نیز در نظر گرفته شدهاند. برای نمونه کدهای کامپوننت HotelRoomList.razor را به صورت زیر تغییر میدهیم:
@page "/hotel-room" @*@implements IDisposable*@ @*@inject IHotelRoomService HotelRoomService*@ @inherits OwningComponentBase<IHotelRoomService>
چند نکته:
- فقط یکبار به ازای هر کامپوننت میتوان از دایرکتیو inherits استفاده کرد.
- زمانیکه طول عمر سرویسی را توسط OwningComponentBase مدیریت میکنیم، در حقیقت یک کلاس پایه را برای آن کامپوننت درنظر گرفتهایم که به همراه یک خاصیت عمومی ویژه، به نام Service و از نوع سرویس مدنظر ما است. در این حالت یا میتوان از خاصیت Service به صورت مستقیم استفاده کرد و یا میتوان به صورت زیر، همان کدهای قبلی را داشت و هربار که نیازی به HotelRoomService بود، آنرا به خاصیت عمومی Service هدایت کرد:
@code { private IHotelRoomService HotelRoomService => Service;
@page "/preferences" @using Microsoft.Extensions.DependencyInjection @inherits OwningComponentBase @code { private IHotelRoomService HotelRoomService { get; set; } private IHotelRoomImageService HotelRoomImageService { get; set; } protected override void OnInitialized() { HotelRoomService = ScopedServices.GetRequiredService<IHotelRoomService>(); HotelRoomImageService = ScopedServices.GetRequiredService<IHotelRoomImageService>(); } }
خلاصهی بحث جاری در مورد روش مدیریت DbContext برنامههای Blazor Server:
- بجای services.AddDbContext متداول، باید از AddDbContextFactory استفاده کرد:
services.AddDbContextFactory<ApplicationDbContext>(options => options.UseSqlServer(connectionString)); services.AddScoped<ApplicationDbContext>(serviceProvider => serviceProvider.GetRequiredService<IDbContextFactory<ApplicationDbContext>>().CreateDbContext());
- کامپوننتهای برنامه، سرویسهایی را که باید Scoped عمل کنند، دیگر نباید از طریق تزریق مستقیم آنها دریافت کنند؛ چون در این حالت همواره به همان وهلهای که در ابتدای کار ایجاد شده، میرسیم:
@inject IHotelRoomService HotelRoomService
@inherits OwningComponentBase<IHotelRoomService>
کدهای کامل این مطلب را از اینجا میتوانید دریافت کنید: Blazor-5x-Part-19.zip
- ادغام چند فایل PDF به صورت فایل واحد (Merging)
- اضافه کردن یک فایل PDF به یک فایل موجود (Appending)
- جدا کردن صفحات دلخواه از یک فایل و ذخیره آنها در یک فایل جدا (Splitting)
- در صورت نصب امکانات پایتون برای ویژوال استودیو مثل سایر پروژههای دات نت قابل اجرا و دیباگ است.
- از طریق خط فرمان و دستور ipy که در ادامه مثالی میزنم.
- من خودم از طریق یک برنامه کنسول ساده پروژه را خارج از محیط توسعه اجرا میکنم که در ادامه مثالی میزنم.
var process = new Process(); var startInfo = new ProcessStartInfo { WindowStyle = ProcessWindowStyle.Hidden, FileName = "CMD.exe", Arguments = "/C ipy C:\\PdfTools\\PdfTools.py" }; process.StartInfo = startInfo; process.Start();
آماده سازی برنامههای دات نت برای دسترسی به API مترو ویندوز 8.1
ابتدا یک برنامهی کنسول دات نت 4.5.1 را آغاز کنید. برای دسترسی به API ویندوز 8.1 حتما نیاز است که حداقل از دات نت 4.5.1 شروع کرد. سپس برنامه را در VS.NET بسته و فایل پروژه آنرا در یک ادیتور متنی باز کنید.
در ابتدای فایل csproj، نیاز است سطر TargetPlatformVersion ذیل اضافه شود.
<PropertyGroup> <TargetFrameworkVersion>v4.5.1</TargetFrameworkVersion> <TargetPlatformVersion>8.1</TargetPlatformVersion> </PropertyGroup>
<ItemGroup> <Reference Include="System" /> <Reference Include="System.ComponentModel.DataAnnotations" /> <Reference Include="System.Core" /> <Reference Include="System.ObjectModel" /> <Reference Include="System.Xml.Linq" /> <Reference Include="System.Data.DataSetExtensions" /> <Reference Include="Microsoft.CSharp" /> <Reference Include="System.Data" /> <Reference Include="System.Xml" /> <Reference Include="System.Threading" /> <Reference Include="System.Threading.Tasks" /> </ItemGroup> <ItemGroup> <Reference Include="Windows" /> <Reference Include="System.Runtime" /> <Reference Include="System.Runtime.WindowsRuntime" /> </ItemGroup>
یک نکته
اگر میخواهید این فرآیند را ساده و خودکار کنید، از قالبهای پروژهی مخصوص DesktopWinRT.Templates.vsix استفاده نمائید.
DesktopWinRT.Templates.vsix
افزودن ارجاعی به Nito.AsyncEx
چون برنامهی مورد استفاده کنسول است و API ویندوز 8 کاملا async طراحی شدهاست، نیاز است با کمک AsyncContext موجود در کتابخانهی Nito.AsyncEx بتوان از امکانات async و await در متد Main برنامه استفاده کرد. البته اگر از سایر برنامههای دسکتاپ استفاده میکنید، فقط کافی است امضای متد رخدادن گردان را به async تغییر دهید.
install-package Nito.AsyncEx
تبدیل استریمهای دات نت به استریمهای WinRT
اکثر متدهای WinRT با استریمهایی از نوع IRandomAccessStream کار میکنند. برای اینکه بتوان استریم استاندارد دات نت را به این نوع تبدیل کرد، میتوان از کلاسهای ذیل کمک گرفت:
using System; using System.IO; using Windows.Storage.Streams; namespace ConsoleWin81PdfApiTest { public static class MicrosoftStreamExtensions { public static IRandomAccessStream AsRandomAccessStream(this Stream stream) { return new RandomStream(stream); } } class RandomStream : IRandomAccessStream { readonly Stream _internstream; public RandomStream(Stream underlyingstream) { _internstream = underlyingstream; } public IInputStream GetInputStreamAt(ulong position) { _internstream.Position = (long)position; return _internstream.AsInputStream(); } public IOutputStream GetOutputStreamAt(ulong position) { _internstream.Position = (long)position; return _internstream.AsOutputStream(); } public ulong Size { get { return (ulong)_internstream.Length; } set { _internstream.SetLength((long)value); } } public bool CanRead { get { return _internstream.CanRead; } } public bool CanWrite { get { return _internstream.CanWrite; } } public IRandomAccessStream CloneStream() { throw new NotSupportedException(); } public ulong Position { get { return (ulong)_internstream.Position; } } public void Seek(ulong position) { _internstream.Seek((long)position, SeekOrigin.Begin); } public void Dispose() { _internstream.Dispose(); } public Windows.Foundation.IAsyncOperationWithProgress<IBuffer, uint> ReadAsync(IBuffer buffer, uint count, InputStreamOptions options) { return GetInputStreamAt(Position).ReadAsync(buffer, count, options); } public Windows.Foundation.IAsyncOperation<bool> FlushAsync() { return GetOutputStreamAt(Position).FlushAsync(); } public Windows.Foundation.IAsyncOperationWithProgress<uint, uint> WriteAsync(IBuffer buffer) { return GetOutputStreamAt(Position).WriteAsync(buffer); } } }
خواندن فایلهای PDF و تبدیل صفحات آنها به تصویر
در ادامه کد کامل استفاده از API جدید ویندوز 8.1 را جهت خواندن فایلهای PDF ملاحظه میکنید. این امکانات جدید در فضای نام Windows.Data.Pdf قرار دارند و صرفا امکان خواندن فایلهای PDF را تدارک دیدهاند.
using System; using System.IO; using System.Threading.Tasks; using Windows.Data.Pdf; using Nito.AsyncEx; namespace ConsoleWin81PdfApiTest { class Program { static void Main(string[] args) { AsyncContext.Run(async () => { await test(); }); } private static async Task test() { using (var randomAccessStream = File.Open("PieChartPdfReport.pdf", FileMode.Open).AsRandomAccessStream()) { var pdfDocument = await PdfDocument.LoadFromStreamAsync(randomAccessStream); for (uint i = 0; i < pdfDocument.PageCount; i++) { using (var page = pdfDocument.GetPage(i)) { /*var renderOptions = new PdfPageRenderOptions { BackgroundColor = Colors.LightGray, DestinationHeight = (uint) (page.Size.Height*10) };*/ using (var stream = File.Open(string.Format("page-{0}.png", i + 1), FileMode.OpenOrCreate).AsRandomAccessStream()) { await page.RenderToStreamAsync(stream/*, renderOptions*/); await stream.FlushAsync(); } } } } } } }
توضیحات:
- متد AsyncContext.Run جزو امکانات Nito.AsyncEx است و امکان نوشتن کدهای await دار را در متد Main یک برنامهی کنسول فراهم میکند.
- متد File.Openدات نت، خروجی از نوع استریم دارد. برای تبدیل آن به نوع IRandomAccessStream، از متد الحاقی AsRandomAccessStream که پیشتر تهیه کردیم، میتوان استفاده کرد.
- در ادامه متد PdfDocument.LoadFromStreamAsync این استریم خاص را دریافت کرده و امکان دسترسی به API ویندوز 8.1 را میسر میکند.
- توسط متد pdfDocument.GetPage میتوان به صفحات مختلف فایل PDF باز شده دسترسی یافت. در اینجا متد page.RenderToStreamAsync، سبب رندر شدن صفحه با فرمت PNG میشود. این خروجی نهایتا باید در یک استریم از نوع IRandomAccessStream ثبت شود. در اینجا نیز میتوان از متد File.Open در حالت FileMode.OpenOrCreate استفاده کرد.
- اگر میخواهید ابعاد تصویر نهایی و ویژگیهای آنرا تغییر دهید، میتوان از پارامتر دوم متد page.RenderToStreamAsync استفاده کرد که شیءایی از نوع PdfPageRenderOptions را میپذیرد.
کدهای کامل این پروژه را از اینجا میتوانید دریافت کنید
MicrosoftStreamExtensions.zip
برای مطالعه بیشتر
How to use specific WinRT API from Desktop apps
How to call WinRT APIs from .NET desktop apps