کتاب رایگان Java Succinctly Part 2
In this second e-book on Java, Christopher Rose takes readers through some of the more advanced features of the language. Java Succinctly Part 2explores powerful and practical features of Java, such as multithreading, building GUI applications, and 2-D graphics and game programming. Then learn techniques for using these mechanisms in coherent projects by building a calculator app and a simple game with the author.
- Packages and Assert
- Reading and Writing to Files
- Polymorphism
- Anonymous Classes
- Multithreading
- Introduction to GUI Programming
- GUI Windows Builder
- 2-D Game Programming
نمونه مثالی از ASP.NET Core و Entity Framework Core به همراه معماری DDD و CQRS و Event Sourcing
Full ASP.NET Core 2.2 application with DDD, CQRS and Event Sourcing
Technologies implemented:
- ASP.NET Core 2.2 (with .NET Core 2.2)
- ASP.NET MVC Core
- ASP.NET WebApi Core
- ASP.NET Identity Core
- Entity Framework Core 2.2
- .NET Core Native DI
- AutoMapper
- FluentValidator
- MediatR
- Swagger UI
Architecture:
- Full architecture with responsibility separation concerns, SOLID and Clean Code
- Domain Driven Design (Layers and Domain Model Pattern)
- Domain Events
- Domain Notification
- CQRS (Imediate Consistency)
- Event Sourcing
- Unit of Work
- Repository and Generic Repository
تغییرات مهم ASP.NET 5 و MVC 6
فرض کنید یک صفحهی Blazor SSR، از سه کامپوننت منوی سمت راست، محتوای اصلی صفحه و فوتر سایت که به همراه متنی است، تشکیل شدهاست. منوی سمت راست، به همراه لینکهاییاست که آمار آنها را نیز نمایش میدهد و این اطلاعات را از بانک اطلاعاتی، به کمک EF-Core دریافت میکند. فوتر صفحه، سال شروع به کار و نام برنامه را از بانک اطلاعاتی دریافت میکند و محتوای اصلی صفحه نیز از بانک اطلاعاتی دریافت میشود. پس از تکمیل این سه کامپوننت مجزا، اگر برنامه را اجرا کنید، بلافاصله با خطای زیر مواجه میشوید:
A second operation started on this context before a previous operation completed
مشکل کجاست؟! مشکل اینجاست که تنها یک نمونه از DbContext، در طول درخواست جاری رسیده، بین سه کامپوننت جاری برنامه به اشتراک گذاشته میشود (به سازندهی سرویسهای مرتبط تزریق میشود) و ... در Blazor SSR، پردازش کامپوننتهای یک صفحه، به صورت موازی و همزمان انجام میشوند؛ یعنی ترتیبی نیست. اگر ابتدا کامپوننت منو، بعد محتوای صفحه و در آخر فوتر، رندر میشدند، هیچگاه پیام فوق را مشاهده نمیکردیم؛ اما ... هر سه کامپوننت، با هم و همزمان رندر میشوند و سپس نتیجهی نهایی در Response درج خواهد شد. یعنی یک DbContext بین چندین ترد به اشتراک گذاشته میشود که چنین حالتی توسط EF-Core پشتیبانی نمیشود و مجاز نیست.
روش مواجه شدن با یک چنین حالتهایی، نمونه سازی مجزای DbContext به ازای هر کامپوننت است که نمونهای از آنرا پیشتر در مطلب «نکات ویژهی کار با EF-Core در برنامههای Blazor Server» مشاهده کردهاید. در این مطلب، راهحل دیگری برای اینکار ارائه میشود که سادهتر است و نیازی به تغییرات آنچنانی در کدهای کامپوننتها و کل برنامه ندارد.
استفاده از کلاس پایهی OwningComponentBase برای نمونه سازی مجدد DbContext بهازای هر کامپوننت
زمانیکه در برنامههای Blazor SSR از روش استاندارد زیر برای دسترسی به سرویسهای مختلف برنامه استفاده میکنیم:
@inject IHotelRoomService HotelRoomService
طول عمر دریافتی سرویس، دقیقا بر اساس طول عمر اصلی تعریف شدهی آن عمل میکند (شبیه به برنامههای ASP.NET Core). یعنی برای مثال اگر Scoped باشد، DbContext تزریق شدهی در آن هم Scoped است و این DbContext، بین تمام کامپوننتهای در حال پردازش موازی در طول یک درخواست، بهاشتراک گذاشته میشود که مطلوب ما نیست. ما میخواهیم بتوانیم به ازای هر کامپوننت مجزای صفحه، یک DbContext جدید داشته باشیم. یعنی باید بتوانیم خودمان این سرویس Scoped را نمونه سازی کنیم و نه اینکه آنرا مستقیما از سیستم تزریق وابستگیها دریافت کنیم.
بنابراین اگر بخواهیم قسمتهای مختلف برنامه را تغییر ندهیم و همان تعاریف ابتدایی services.AddDbContext و Scoped تعریف کردن سرویسهای برنامه بدون تغییر باقی بمانند (و از IDbContextFactory و موارد مشابه دیگر مطلب «نکات ویژهی کار با EF-Core در برنامههای Blazor Server» هم استفاده نکنیم)، باید جایگزینی را برای نمونه سازی سرویسها ارائه دهیم. به همین جهت در ابتدا، یک ویژگی جدیدی را به صورت زیر تعریف میکنیم:
[AttributeUsage(AttributeTargets.Property)] public sealed class InjectComponentScopedAttribute : Attribute { }
تا بتوانیم بجای:
@inject IHotelRoomService HotelRoomService
بنویسیم:
[InjectComponentScoped] internal IHotelRoomService HotelRoomService { set; get; } = null!;
مرحلهی بعد، نوبت به نمونه سازی خودکار این سرویسهای درخواستی علامتگذاری شدهی با InjectComponentScoped است. برای این منظور، تمام کامپوننتهای برنامه را از کلاس پایه و استاندارد OwningComponentBase ارثبری میکنیم. مزیت اینکار، امکان دسترسی به خاصیتی به نام ScopedServices در تمام کامپوننتهای برنامه است که توسط آن میتوان به متد ScopedServices.GetRequiredService آن دسترسی یافت. یعنی با ارثبری از کلاس پایهی OwningComponentBase به ازای هر کامپوننت، به صورت خودکار Scope جدیدی شروع میشود که توسط آن میتوان به نمونهی جدیدی از سرویس مدنظر دسترسی یافت و نه به نمونهی اشتراکی در طی درخواست جاری.
اکنون اگر از این مزیت به صورت زیر استفاده کنیم، میتوان تمام سرویسهای درخواستی مزین به InjectComponentScopedAttribute یک کامپوننت را به صورت خودکار یافته و با استفاده از ScopedServices.GetRequiredService، مقدار دهی کرد:
public class BlazorScopedComponentBase : OwningComponentBase { private static readonly ConcurrentDictionary<Type, Lazy<List<PropertyInfo>>> CachedProperties = new(); private List<PropertyInfo> InjectComponentScopedPropertiesList => CachedProperties.GetOrAdd(GetType(), type => new Lazy<List<PropertyInfo>>( () => type.GetProperties(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public) .Where(p => p.GetCustomAttribute<InjectComponentScopedAttribute>() is not null) .ToList(), LazyThreadSafetyMode.ExecutionAndPublication)).Value; protected override void OnInitialized() { foreach (var propertyInfo in InjectComponentScopedPropertiesList) { propertyInfo.SetValue(this, ScopedServices.GetRequiredService(propertyInfo.PropertyType)); } } }
این سرویس، اینبار طول عمری، محدود به کامپوننت جاری را خواهد داشت و بین سایر کامپوننتهای درحال پردازش درخواست جاری، به اشتراک گذاشته نمیشود و همچنین به صورت خودکار هم در پایان درخواست، Dispose میشود.
فعالسازی ارثبری خودکار در تمام کامپوننتهای برنامه
مرحلهی بعد، ارثبری خودکار تمام کامپوننتهای برنامه از OwningComponentBase سفارشی فوق است و در اینجا قصد نداریم تمام کامپوننتها را جهت معرفی آن، به صورت دستی تغییر دهیم. برای اینکار فقط کافی است به فایل Imports.razor_ مراجعه و یک سطر زیر را در آن درج کنیم:
@inherits BlazorScopedComponentBase
با اینکار یک ارثبری سراسری در کل برنامه رخ میدهد و تمام کامپوننتها، از BlazorScopedComponentBase مشتق خواهند شد. یعنی پس از این تغییر، اگر سرویسی را به صورت زیر معرفی و با ویژگی InjectComponentScoped علامتگذاری کردیم:
[InjectComponentScoped] internal IHotelRoomService HotelRoomService { set; get; } = null!;
به صورت خودکار یافت شده و نمونه سازی Scoped محدود به طول عمر همان کامپوننت میشود که بین سایر کامپوننتها، به اشتراک گذاشته نخواهد شد.
یک نکته: اگر کامپوننت شما متد OnInitialized را بازنویسی میکند، فراموش نکنید که در ابتدای آن باید ()base.OnInitialized را هم فراخوانی کنید تا متد OnInitialized کامپوننت پایهی BlazorScopedComponentBase نیز فراخوانی شود. البته این مورد در حین بازنویسی نمونهی async آن مهم نیست؛ چون همیشه OnInitialized غیر async در ابتدا فراخوانی میشود و سپس نمونهی async آن اجرا خواهد شد.
ترکیب کامپوننتها
در ادامه، همان برنامهی تا قسمت 5 را که کار نمایش یک counter را انجام میدهد، تکمیل میکنیم. در این برنامه اگر به فایل index.js دقت کنید، کار رندر تک کامپوننت Counter را انجام میدهیم:
ReactDOM.render(<Counter />, document.getElementById("root"));
برای نمایش این مفهوم، کامپوننت جدید src\components\counters.jsx را ایجاد میکنیم. قصد داریم در این کامپوننت، لیستی از کامپوننتهای Counter را رندر کنیم. سپس در index.js، بجای رندر کامپوننت Counter، کامپوننت جدید Counters را رندر میکنیم. به این ترتیب درخت کامپوننتهای برنامه، در سطح بالایی خودش از کامپوننت Counters شروع میشود و سپس فرزندان آنرا کامپوننتهای Counter تشکیل میدهند. به همین جهت فایل index.js را به صورت زیر ویرایش میکنیم تا به کامپوننت Counters اشاره کند:
import Counters from "./components/counters"; ReactDOM.render(<Counters />, document.getElementById("root"));
import React, { Component } from "react"; import Counter from "./counter"; class Counters extends Component { state = {}; render() { return ( <div> <Counter /> <Counter /> <Counter /> <Counter /> </div> ); } } export default Counters;
در مرحلهی بعد، بجای رندر و درج دستی این کامپوننتها، آرایهای از اشیاء counter را ایجاد کرده و سپس آنها را توسط متد Array.map رندر میکنیم:
import React, { Component } from "react"; import Counter from "./counter"; class Counters extends Component { state = { counters: [ { id: 1, value: 0 }, { id: 2, value: 0 }, { id: 3, value: 0 }, { id: 4, value: 0 } ] }; render() { return ( <div> {this.state.counters.map(counter => ( <Counter key={counter.id} /> ))} </div> ); } } export default Counters;
ارسال دادهها به کامپوننتها
مشکل! مقدار value هر شیء شمارشگر تعریف شده، به کامپوننتهای مرتبط رندر شده اعمال نشدهاست. برای مثال اگر value اولین شیء را به 4 تغییر دهیم، هنوز هم این کامپوننت با همان مقدار صفر شروع به کار میکند. برای رفع این مشکل، به همان روشی که ویژگی key کامپوننت Counter را مقدار دهی کردیم، میتوان ویژگیهای سفارشی دیگری را تعریف و مقدار دهی کرد:
render() { return ( <div> {this.state.counters.map(counter => ( <Counter key={counter.id} value={counter.value} selected={true} /> ))} </div> );
class Counter extends Component { state = { count: 0 }; render() { console.log("props", this.props); //...
خاصیت this.props، یک شیء سادهی جاوا اسکریپتی است و شامل تمام ویژگیهایی میباشد که ما در کامپوننت Counters برای هر کدام از کامپوننتهای Counter رندر شدهی توسط آن، تعریف کردیم. برای نمونه دو ویژگی جدید value و selected را که به تعاریف المانهای Counter در کامپوننت Counters اضافه کردیم، در اینجا به همراه مقادیر منتسب به آنها، قابل مشاهده هستند. البته در این خروجی، key را ملاحظه نمیکنید؛ چون هدف اصلی آن، معرفی یکتای المانها در DOM مجازی React است.
بنابراین اکنون میتوان به value تنظیم شدهی در کامپوننت Counters به صورت this.props.value در کامپوننت Counter دسترسی یافت و سپس از آن جهت مقدار دهی اولیهی counter استفاده کرد.
class Counter extends Component { state = { count: this.props.value };
یک نکته: در اینجا selected={true} را داریم. اگر مقدار آنرا حذف کنیم، یعنی selected تنها درج شود، مقدار آن، همان true دریافت خواهد شد.
تعریف فرزند برای المانهای کامپوننتها
ویژگیهای اضافه شدهی به تعاریف المانهای کامپوننتها، توسط خاصیت this.props، به هر کدام از آن کامپوننتها منتقل میشوند. این خاصیت props، یک خاصیت ویژه را به نام children، نیز دارا است و از آن برای دسترسی به المانهای تعریف شدهی بین تگهای یک المان اصلی استفاده میشود:
render() { return ( <div> {this.state.counters.map(counter => ( <Counter key={counter.id} value={counter.value} selected={true}> <h4>Counter #{counter.id}</h4> </Counter> ))} </div> ); }
یک نمونه مثال واقعی این قابلیت، امکان تعریف محتوای دیالوگ باکسها، توسط استفاده کنندهی از آن است.
روش دیباگ برنامههای React
افزونهی مفید React developer tools را میتوانید برای مرورگرهای کروم و فایرفاکس، دریافت و نصب کنید. برای نمونه پس از نصب آن در مرورگر کروم، یک برگهی جدید به لیست برگههای کنسول توسعه دهندگان آن اضافه میشود:
همانطور که مشاهده کنید، درخت کامپوننتهای برنامه را در برگهی جدید Components، میتوان مشاهده کرد. در اینجا با انتخاب هر کدام از فرزندان این درخت، مشخصات آن نیز مانند props و state، در کنار صفحه ظاهر میشوند. همچنین در بالای همین قسمت، 4 آیکن مشاهدهی سورس، مشاهدهی DOM و یا لاگ کردن جزئیات شیء کامپوننت انتخابی در کنسول هم درج شدهاند:
که برای نمونه چنین خروجی را لاگ میکند:
بررسی تفاوتهای خواص props و state
در کامپوننت Counter، از props برای مقدار دهی اولیهی state استفاده میکنیم:
class Counter extends Component { state = { count: this.props.value };
- props حاوی اطلاعاتی است که به یک کامپوننت ارسال میکنیم؛ اما state حاوی اطلاعاتی است که مختص به آن کامپوننت بوده و private است. یعنی سایر کامپوننتها نمیتوانند به state کامپوننت دیگری دسترسی پیدا کنند. برای مثال در کامپوننت Counters، تمام attributes سفارشی تنظیم شدهی بر روی تعاریف المانهای کامپوننت Counter، جزئی از اطلاعات props خواهند بود. در اینجا نمیتوان به state کامپوننت مدنظری دسترسی یافت و آنرا مقدار دهی کرد. به همین ترتیب state کامپوننت Counters نیز در سایر کامپوننتها قابل دسترسی نیست.
- همچنین باید درنظر داشت که props، در مقایسه با state، فقط خواندنی است. به عبارتی مقدار ورودی به یک کامپوننت را داخل آن کامپوننت نمیتوان تغییر داد. برای مثال سعی کنید در داخل متد رویدادگردان کلیک موجود در کامپوننت Counter، مقدار this.props.value را به صفر تنظیم کنید. در این حالت با کلیک بر روی دکمهی Increment، بلافاصله خطای readonly بودن خواص شیء منتسب به props را دریافت میکنیم. در اینجا اگر نیاز است این مقدار را داخل کامپوننت تغییر دهیم، باید ابتدا این مقدار را دریافت کرده و سپس آنرا داخل state قرار دهیم. پس از آن امکان ویرایش اطلاعات منتسب به state، داخل یک کامپوننت وجود خواهد داشت.
صدور و مدیریت رخدادها
در ادامه میخواهیم در کنار هر دکمهی Increment کامپوننت شمارشگر، یک دکمهی Delete هم قرار دهیم:
مشکل! اگر کد مدیریتی handleDelete را در کامپوننت Counter قرار دهیم، چگونه باید به لیست آرایهی اشیاء counters والد آن، یعنی کامپوننت Counters که سبب رندر شدن کامپوننتهای شمارشگر شده (state = { counters: [ ] })، دسترسی یافت و شیءای را از آن حذف کرد؟ در React، کامپوننتی که state ای را تعریف میکند، باید کامپوننتی باشد که قرار است آنرا تغییر دهد و اطلاعات state هر کامپوننت، صرفا متعلق به آن کامپوننت بوده و جزو اطلاعات خصوصی آن است. بنابراین مدیریت حذف و یا افزودن کامپوننتها در لیست نمایش داده شده، باید جزو وظایف کامپوننت Counters باشد و نه Counter.
برای حل این مشکل، کامپوننت Counter تعریف شده (کامپوننت فرزند) باید سبب بروز رخداد onDelete شود تا کامپوننت Counters (کامپوننت والد)، آنرا توسط متد handleDelete مدیریت کند. بنابراین ابتدا به کامپوننت Counters (کامپوننت والد) مراجعه کرده و متد رویدادگردان handleDelete را به آن اضافه میکنیم:
handleDelete = () => { console.log("handleDelete called."); };
<Counter key={counter.id} value={counter.value} selected={true} onDelete={this.handleDelete} />
<button onClick={this.props.onDelete} className="btn btn-danger btn-sm m-2" > Delete </button>
اکنون اگر برنامه را ذخیره کرده و پس از بارگذاری مجدد برنامه در مرورگر بر روی دکمهی Delete کلیک کنیم، پیام «handleDelete called» در کنسول توسعه دهندگان مرورگر لاگ میشود. به این ترتیب کامپوننت فرزند سبب بروز رخدادی شده و والد آن، این رخداد را مدیریت میکند.
به روز رسانی state
تا اینجا دکمهی Delete فرزند، به متد handleDelete والد متصل شدهاست. مرحلهی بعد، پیاده سازی واقعی حذف یک المان از DOM مجازی و به روز رسانی state است. برای اینکار ابتدا به رخدادگردان onClick، در کامپوننت شمارشگر، مراجعه کرده و id دریافتی را به سمت والد ارسال میکنیم:
onClick={() => this.props.onDelete(this.props.id)}
<Counter key={counter.id} value={counter.value} selected={true} onDelete={this.handleDelete} id={counter.id} />
handleDelete = counterId => { console.log("handleDelete called.", counterId); const counters = this.state.counters.filter( counter => counter.id !== counterId ); this.setState({ counters }); // = this.setState({ counters: counters }); };
البته پیاده سازی ما تا به اینجا بدون مشکل کار میکند، اما به ازای هر خاصیت counter، یک ویژگی جدید را به تعریف المان مرتبط اضافه کردهایم که در طول زمان بیش از اندازه طولانی خواهد شد. برای رفع این مشکل، خود شیء counter را به صورت یک ویژگی جدید به کامپوننت مرتبط با آن ارسال میکنیم. به این ترتیب اگر در آینده خاصیتی را به این شیء اضافه کردیم، دیگر نیازی نیست تا آنرا به صورت دستی و مجزا تعریف کنیم. به همین جهت ابتدا تعریف المان Counter را به صورت زیر خلاصه میکنیم که در آن ویژگی جدید counter، حاوی کل شیء counter است:
<Counter key={counter.id} counter={counter} onDelete={this.handleDelete} />
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: sample-07.zip
کتاب رایگان UWP Succinctly
- Introduction
- The Essential Concepts: Visual Studio, XAML, and C#
- Creating the User Interface: The Controls
C#.NET for non-engineers.
The first course of "A Sr. Developer Course" courses. which contains:
1- C# Fundamentals for non-engineers.
2- DataBase for non-engineers.
3- Asp.NET WebForm for Non-engineers.
4- Application Architecture for no-engineers.
5- ASP.NET MVC for non-engineers.
6- Angular for non-engineers.
This is a course for who knows noting about C# and development if you know nothing about Array, variable, loop, and conditions you are in the right place.
at the end of this course, we will create one small university registration console application together.
You will learn in this course:
C#.NET
.NET Framework
Methods
Recursive methods
C# Primitive Types/Complex Types
conditions
switch case
Arrays
if statement
switch
loops
Creating a method
ref, out
enums
OOP/Object-oriented programing
Generics
Error handling
problem-solving
working with files
level: beginners to upper intermediate