To Every Programmer Who’s Ever Scanned
Hacker News And /r/programming And Thought...
“How Will I Ever Keep Up?”
Here’s How To Turn “Information Overwhelm”
Into An Efficiency Edge That Can
Quickly Boost Your Income,
Earn You “MVP” Status With Your Team,
And Make You The In-Demand Developer
Companies Are Dying To Recruit
معرفی Redgate University
کتابخانه HTML5 Scratch Card
Scratch.js is a standalone library with no external dependencies which brings interactivity to your website by allowing you to generate scratch cards for your visitors.
Based on HTML5, scratch.js generate canvas on the fly and is optimized for all modern browsers and has touch support for mobile devices. You can use it to make scratch cards, coupons, promotionnal game and even advertisement.
کتابخانه ای برای ++C جهت ثبت لاگ
Single header only C++ logging library. It is extremely light-weight, robust, fast performing, thread and type safe and consists of many built-in features. It provides ability to write logs in your own customized format. It also provide support for logging your classes, third-party libraries, STL and third-party containers etc.
هدف از اینترفیس INotifyPropertyChanged که به همراه یک رخداد است:
public interface INotifyPropertyChanged { event PropertyChangedEventHandler PropertyChanged; }
partial class CarModel : INotifyPropertyChanged { private double _speedKmPerHour; public double SpeedKmPerHour { get => _speedKmPerHour; set { _speedKmPerHour = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(SpeedKmPerHour))); } } public event PropertyChangedEventHandler? PropertyChanged; }
اگر بخواهیم تولید این کدهای تکراری را به Source Generators محول کنیم، میتوان برای مثال فیلد خصوصی مرتبط را نگه داشت و تولید مابقی کدها را خودکار کرد:
partial class CarModel : INotifyPropertyChanged { private double _speedKmPerHour; }
ایجاد پروژهی Source Generator
در ابتدا برای ایجاد تولید کنندهی خودکار کدهای INotifyPropertyChanged، یک class library را به solution جاری اضافه میکنیم. سپس نیاز است ارجاعاتی را به دو بستهی نیوگت زیر نیز افزود:
<Project Sdk="Microsoft.NET.Sdk"> <ItemGroup> <PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.3"> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <PrivateAssets>all</PrivateAssets> </PackageReference> <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.2.0" PrivateAssets="all" /> </ItemGroup> </Project>
[Generator] public class NotifyPropertyChangedGenerator : ISourceGenerator { public void Initialize(GeneratorInitializationContext context) { } public void Execute(GeneratorExecutionContext context) {
- اینترفیس ISourceGenerator به همراه دو متد Initialize و Execute است که در صورت نیاز باید پیاده سازی شوند.
در متد Execute، به خاصیت context.Compilation دسترسی داریم. این خاصیت تمام اطلاعاتی را که کامپایلر از Solution جاری در اختیار دارد، به توسعه دهنده ارائه میدهد. برای نمونه پیاده سازی متد Execute تولید کنندهی کد مثال جاری، چنین شکلی را دارد:
public void Execute(GeneratorExecutionContext context) { // uncomment to debug the actual build of the target project // Debugger.Launch(); var compilation = context.Compilation; var notifyInterface = compilation.GetTypeByMetadataName("System.ComponentModel.INotifyPropertyChanged"); foreach (var syntaxTree in compilation.SyntaxTrees) { var semanticModel = compilation.GetSemanticModel(syntaxTree); var immutableHashSet = syntaxTree.GetRoot() .DescendantNodesAndSelf() .OfType<ClassDeclarationSyntax>() .Select(x => semanticModel.GetDeclaredSymbol(x)) .OfType<ITypeSymbol>() .Where(x => x.Interfaces.Contains(notifyInterface)) .ToImmutableHashSet(); foreach (var typeSymbol in immutableHashSet) { var source = GeneratePropertyChanged(typeSymbol); context.AddSource($"{typeSymbol.Name}.Notify.cs", source); } } }
نکتهی مهم و جالب در اینجا این است که نیازی نیست تا قطعه کد جدید را به صورت SyntaxTrees در آورد و به کامپایلر اضافه کرد. میتوان این قطعه کد را به نحو متداولی، به صورت یک قطعه رشتهی استاندارد #C، تولید و به کامپایلر با متد context.AddSource ارائه کرد که نمونهای از آنرا در ذیل مشاهده میکنید:
private string GeneratePropertyChanged(ITypeSymbol typeSymbol) { return $@" using System.ComponentModel; namespace {typeSymbol.ContainingNamespace} {{ partial class {typeSymbol.Name} {{ {GenerateProperties(typeSymbol)} public event PropertyChangedEventHandler? PropertyChanged; }} }}"; } private static string GenerateProperties(ITypeSymbol typeSymbol) { var sb = new StringBuilder(); var suffix = "BackingField"; foreach (var fieldSymbol in typeSymbol.GetMembers().OfType<IFieldSymbol>() .Where(x=>x.Name.EndsWith(suffix))) { var propertyName = fieldSymbol.Name[..^suffix.Length]; sb.AppendLine($@" public {fieldSymbol.Type} {propertyName} {{ get => {fieldSymbol.Name}; set {{ {fieldSymbol.Name} = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof({propertyName}))); }} }}"); } return sb.ToString(); }
کدهای source generator ما همین مقدار بیشتر نیست. اکنون میخواهیم از آن در یک برنامهی کنسول جدید (برای مثال به نام NotifyPropertyChangedGenerator.Demo) استفاده کنیم. برای اینکار نیاز است ارجاعی را به آن اضافه کنیم؛ اما این ارجاع، یک ارجاع متداول نیست و نیاز به ذکر چنین ویژگی خاصی وجود دارد:
<Project Sdk="Microsoft.NET.Sdk"> <ItemGroup> <ProjectReference Include="..\NotifyPropertyChangedGenerator\NotifyPropertyChangedGenerator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false"/> </ItemGroup> </Project>
برای آزمایش این تولید کنندهی کد، کلاس CarModel را به صورت زیر به پروژهی کنسول آزمایشی اضافه میکنیم:
using System.ComponentModel; namespace NotifyPropertyChangedGenerator.Demo { public partial class CarModel : INotifyPropertyChanged { private double SpeedKmPerHourBackingField; private int NumberOfDoorsBackingField; private string ModelBackingField = ""; public void SpeedUp() => SpeedKmPerHour *= 1.1; } }
نحوهی مشاهدهی کدهای خودکار تولید شده
اگر علاقمند باشید تا کدهای خودکار تولید شده را مشاهده کنید، در Visual Studio، در قسمت و درخت نمایشی dependencies پروژه، گرهای به نام Analyzers وجود دارد که در آن برای مثال نام NotifyPropertyChangedGenerator و ذیل آن، کلاسهای تولید شدهی توسط آن، قابل مشاهده و دسترسی هستند و حتی قابل دیباگ نیز میباشند؛ یعنی میتوان بر روی سطور مختلف آن، break-point قرار داد.
در اینجا تعدادی مثال را که توسط مایکروسافت توسعه یافتهاست، مشاهده میکنید که اتفاقا یکی از آنها پیاده سازی تولید کنندهی کد اینترفیس INotifyPropertyChanged است. در این برنامه، خروجی کدهای تولیدی نیز به سادگی قابل مشاهدهاست.
- برنامه SharpLab
برای توسعهی تولید کنندههای کد، عموما نیاز است تا با Roslyn API آشنا بود. در این برنامه اگر از منوی بالای صفحه قسمت results، گزینهی «syntax tree» را انتخاب کنید و سپس قسمتی از کد خود را انتخاب کنید، بلافاصله معادل Roslyn API آن، در سمت راست صفحه نمایش داده میشود.
- معرفی مجموعهای از Source Generators
در اینجا میتوان مجموعهای از پروژههای سورس باز Source Generators را مشاهده و کدهای آنها را مطالعه کنید و یا از آنها در پروژههای خود استفاده نمائید.
- معرفی یک cookbook در مورد Source Generators
این cookbook توسط خود مایکروسافت تهیه شدهاست و جهت شروع به کار با این فناوری، بسیار مفید است.
- مجموعه مثالهای Source generators از مایکروسافت
در اینجا میتوانید مجموعه مثالهایی از Source generators را که توسط مایکروسافت تهیه شدهاست، مشاهده کنید. شرح و توضیحات تعدادی از آنها را هم در اینجا مطالعه کنید.
چرا SQL در حال شکست NoSQL است؟
SQL is back. Not just because writing glue code to kludge together NoSQL tools is annoying. Not just because retraining workforces to learn a myriad of new languages is hard. Not just because standards can be a good thing.
But also because the world is filled with data. It surrounds us, binds us. At first, we relied on our human senses and sensory nervous systems to process it. Now our software and hardware systems are also getting smart enough to help us. And as we collect more and more data to make better sense of our world, the complexity of our systems to store, process, analyze, and visualize that data will only continue to grow as well.
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
روش های مقایسه اشیاء با null
Check | Code | Description |
Is Null | if(variable is null) return true; |
|
Is Not Null | if(variable is { }) return false |
|
Is Not Null | if(variable is object) return false |
|
Is Null | if(variable == null) return true |
|
Is Not Null | if(variable != null) return false |
|
SonarLint is a free IDE extension that lets you fix coding issues before they exist! Like a spell checker, SonarLint highlights Bugs and Security Vulnerabilities as you write code, with clear remediation guidance so you can fix them before the code is even committed. SonarLint in VS Code supports analysis of C, C++, HTML, Java, JavaScript, PHP, Python and TypeScript, and you can install it directly from the VS Code Marketplace!
3. Article 12a: No posting your own photos or videos of sports matches. Only the "organisers" of sports matches will have the right to publicly post any kind of record of the match. No posting your selfies, or short videos of exciting plays. You are the audience, your job is to sit where you're told, passively watch the game and go home.