Vue.js 3.0 منتشر شد
Today we are proud to announce the official release of Vue.js 3.0 "One Piece". This new major version of the framework provides improved performance, smaller bundle sizes, better TypeScript integration, new APIs for tackling large scale use cases, and a solid foundation for long-term future iterations of the framework.
Bootstrap 4 alpha منتشر شد
- Moved from Less to Sass
- Improved grid system
- Opt-in flexbox support is here
- Dropped wells, thumbnails, and panels for cards
- Consolidated all our HTML resets into a new module, Reboot
- Brand new customization options
- Dropped IE8 support and moved to rem and em units
- Rewrote all our JavaScript plugins
- Improved auto-placement of tooltips and popovers
- And tons more!
کلاس Kid را با تعریف زیر در نظر بگیرید. هدف از آن نگهداری اطلاعات فرزندان یک شخص خاص میباشد:
namespace IOCBeginnerGuide
{
class Kid
{
private int _age;
private string _name;
public Kid(int age, string name)
{
_age = age;
_name = name;
}
public override string ToString()
{
return "KID's Age: " + _age + ", Kid's Name: " + _name;
}
}
}
اکنون کلاس والد را با توجه به اینکه در حین ایجاد این شیء، فرزندان او نیز باید ایجاد شوند؛ در نظر بگیرید:
using System;
namespace IOCBeginnerGuide
{
class Parent
{
private int _age;
private string _name;
private Kid _obj;
public Parent(int personAge, string personName, int kidsAge, string kidsName)
{
_obj = new Kid(kidsAge, kidsName);
_age = personAge;
_name = personName;
}
public override string ToString()
{
Console.WriteLine(_obj);
return "ParentAge: " + _age + ", ParentName: " + _name;
}
}
}
و نهایتا مثالی از استفاده از آن توسط یک کلاینت:
using System;
namespace IOCBeginnerGuide
{
class Program
{
static void Main(string[] args)
{
Parent p = new Parent(35, "Dev", 6, "Len");
Console.WriteLine(p);
Console.ReadKey();
Console.WriteLine("Press a key...");
}
}
}
که خروجی برنامه در این حالت مساوی سطرهای زیر میباشد:
KID's Age: 6, Kid's Name: Len
ParentAge: 35, ParentName: Dev
مثال فوق نمونهای از الگوی طراحی ترکیب یا composition میباشد که به آن Object Dependency یا Object Coupling نیز گفته میشود. در این حالت ایجاد شیء والد وابسته است به ایجاد شیء فرزند.
مشکلات این روش:
1- با توجه به وابستگی شدید والد به فرزند، اگر نمونه سازی از شیء فرزند در سازندهی کلاس والد با موفقیت روبرو نشود، ایجاد نمونهی والد با شکست مواجه خواهد شد.
2- با از بین رفتن شیء والد، فرزندان او نیز از بین خواهند رفت.
3- هر تغییری در کلاس فرزند، نیاز به تغییر در کلاس والد نیز دارد (اصطلاحا به آن Dangling Reference هم گفته میشود. این کلاس آویزان آن کلاس است!).
چگونه این مشکلات را برطرف کنیم؟
بهتر است کار وهله سازی از کلاس Kid به یک شیء، متد یا حتی فریم ورک دیگری واگذار شود. به این واگذاری مسئولیت، delegation و یا inversion of control - IOC نیز گفته میشود.
بنابراین IOC میگوید که:
1- کلاس اصلی (یا همان Parent) نباید به صورت مستقیم وابسته به کلاسهای دیگر باشد.
2- رابطهی بین کلاسها باید بر مبنای تعریف کلاسهای abstract باشد (و یا استفاده از interface ها).
تزریق وابستگی یا Dependency injection
برای پیاده سازی IOC از روش تزریق وابستگی یا dependency injection استفاده میشود که میتواند بر اساس constructor injection ، setter injection و یا interface-based injection باشد و به صورت خلاصه پیاده سازی یک شیء را از مرحلهی ساخت وهلهای از آن مجزا و ایزوله میسازد.
مزایای تزریق وابستگیها:
1- گره خوردگی اشیاء را حذف میکند.
2- اشیاء و برنامه را انعطاف پذیرتر کرده و اعمال تغییرات به آنها سادهتر میشود.
روشهای متفاوت تزریق وابستگی به شرح زیر هستند:
تزریق سازنده یا constructor injection :
در این روش ارجاعی از شیء مورد استفاده، توسط سازندهی کلاس استفاده کننده از آن دریافت میشود. برای نمونه در مثال فوق از آنجائیکه کلاس والد به کلاس فرزندان وابسته است، یک ارجاع از شیء Kid به سازندهی کلاس Parent باید ارسال شود.
اکنون بر این اساس تعاریف، کلاسهای ما به شکل زیر تغییر خواهند کرد:
//IBuisnessLogic.cs
namespace IOCBeginnerGuide
{
public interface IBuisnessLogic
{
}
}
//Kid.cs
namespace IOCBeginnerGuide
{
class Kid : IBuisnessLogic
{
private int _age;
private string _name;
public Kid(int age, string name)
{
_age = age;
_name = name;
}
public override string ToString()
{
return "KID's Age: " + _age + ", Kid's Name: " + _name;
}
}
}
//Parent.cs
using System;
namespace IOCBeginnerGuide
{
class Parent
{
private int _age;
private string _name;
private IBuisnessLogic _refKids;
public Parent(int personAge, string personName, IBuisnessLogic obj)
{
_age = personAge;
_name = personName;
_refKids = obj;
}
public override string ToString()
{
Console.WriteLine(_refKids);
return "ParentAge: " + _age + ", ParentName: " + _name;
}
}
}
//CIOC.cs
using System;
namespace IOCBeginnerGuide
{
class CIOC
{
Parent _p;
public void FactoryMethod()
{
IBuisnessLogic objKid = new Kid(12, "Ren");
_p = new Parent(42, "David", objKid);
}
public override string ToString()
{
Console.WriteLine(_p);
return "Displaying using Constructor Injection";
}
}
}
//Program.cs
using System;
namespace IOCBeginnerGuide
{
class Program
{
static void Main(string[] args)
{
CIOC obj = new CIOC();
obj.FactoryMethod();
Console.WriteLine(obj);
Console.ReadKey();
Console.WriteLine("Press a key...");
}
}
}
توضیحات:
ابتدا اینترفیس IBuisnessLogic ایجاد خواهد شد. تنها متدهای این اینترفیس در اختیار کلاس Parent قرار خواهند گرفت.
از آنجائیکه کلاس Kid توسط کلاس Parent استفاده خواهد شد، نیاز است تا این کلاس نیز اینترفیس IBuisnessLogic را پیاده سازی کند.
اکنون سازندهی کلاس Parent بجای ارجاع مستقیم به شیء Kid ، از طریق اینترفیس IBuisnessLogic با آن ارتباط برقرار خواهد کرد.
در کلاس CIOC کار پیاده سازی واگذاری مسئولیت وهله سازی از اشیاء مورد نظر صورت گرفته است. این وهله سازی در متدی به نام Factory انجام خواهد شد.
و در نهایت کلاینت ما تنها با کلاس IOC سرکار دارد.
معایب این روش:
- در این حالت کلاس business logic، نمیتواند دارای سازندهی پیش فرض باشد.
- هنگامیکه وهلهای از کلاس ایجاد شد دیگر نمیتوان وابستگیها را تغییر داد (چون از سازندهی کلاس جهت ارسال مقادیر مورد نظر استفاده شده است).
تزریق تنظیم کننده یا Setter injection
این روش از خاصیتها جهت تزریق وابستگیها بجای تزریق آنها به سازندهی کلاس استفاده میکند. در این حالت کلاس Parent میتواند دارای سازندهی پیش فرض نیز باشد.
مزایای این روش:
- از روش تزریق سازنده بسیار انعطاف پذیرتر است.
- در این حالت بدون ایجاد وهلهای میتوان وابستگی اشیاء را تغییر داد (چون سر و کار آن با سازندهی کلاس نیست).
- بدون نیاز به تغییری در سازندهی یک کلاس میتوان وابستگی اشیاء را تغییر داد.
- تنظیم کنندهها دارای نامی با معناتر و با مفهومتر از سازندهی یک کلاس میباشند.
نحوهی پیاده سازی آن:
در اینجا مراحل ساخت Interface و همچنین کلاس Kid با روش قبل تفاوتی ندارند. همچنین کلاینت نهایی استفاده کننده از IOC نیز مانند روش قبل است. تنها کلاسهای IOC و Parent باید اندکی تغییر کنند:
//Parent.cs
using System;
namespace IOCBeginnerGuide
{
class Parent
{
private int _age;
private string _name;
public Parent(int personAge, string personName)
{
_age = personAge;
_name = personName;
}
public IBuisnessLogic RefKID {set; get;}
public override string ToString()
{
Console.WriteLine(RefKID);
return "ParentAge: " + _age + ", ParentName: " + _name;
}
}
}
//CIOC.cs
using System;
namespace IOCBeginnerGuide
{
class CIOC
{
Parent _p;
public void FactoryMethod()
{
IBuisnessLogic objKid = new Kid(12, "Ren");
_p = new Parent(42, "David");
_p.RefKID = objKid;
}
public override string ToString()
{
Console.WriteLine(_p);
return "Displaying using Setter Injection";
}
}
}
همانطور که ملاحظه میکنید در این روش یک خاصیت جدید به نام RefKID به کلاس Parent اضافه شده است که از هر لحاظ نسبت به روش تزریق سازنده با مفهومتر و خود توضیح دهندهتر است. سپس کلاس IOC جهت استفاده از این خاصیت اندکی تغییر کرده است.
ماخذ
واژهی استثناء یا exception کوتاه شدهی عبارت exceptional event است. در واقع exception یک نوع رویداد است که در طول اجرای برنامه رخ میدهد و در نتیجه، جریان عادی برنامه را مختل میکند. زمانیکه خطایی درون یک متد رخ دهد، یک شیء (exception object) حاوی اطلاعاتی دربارهی خطا ایجاد خواهد شد. به فرآیند ایجاد یک exception object و تحویل دادن آن به سیستم runtime، اصطلاحاً throwing an exception یا صدور استثناء گفته میشود که در ادامه به آن خواهیم پرداخت.
بعد از اینکه یک متد استثناءایی را صادر میکند، سیستم runtime سعی در یافتن روشی برای مدیریت آن خواهد کرد.
خوب اکنون که با مفهوم استثناء آشنا شدید اجازه دهید دو سناریو را با هم بررسی کنیم.
- سناریوی اول:
فرض کنید یک فایل XML از پیش تعریف شده (برای مثال یک لیست از محصولات) قرار است در کنار برنامهی شما باشد و باید این لیست را درون برنامهی خود نمایش دهید. در این حالت برای خواندن این فایل انتظار دارید که فایل وجود داشته باشد. اگر این فایل وجود نداشته باشد برنامهی شما با اشکال روبرو خواهد شد.
- سناریوی دوم:
فرض کنید یک فایل XML از آخرین محصولات مشاهده شده توسط کاربران را به صورت cache در برنامهتان دارید. در این حالت در اولین بار اجرای برنامه توسط کاربر انتظار داریم که این فایل موجود نباشد و اگر فایل وجود نداشته باشد به سادگی میتوانیم فایل مربوط را ایجاده کرده و محصولاتی را که توسط کاربر مشاهده شده، درون این فایل اضافه کنیم.
در واقع استثناءها بستگی به حالتهای مختلفی دارد. در مثال اول وجود فایل حیاتی است ولی در حالت دوم بدون وجود فایل نیز برنامه میتواند به کار خود ادامه داده و فایل مورد نظر را از نو ایجاد کند.
استثناها مربوط به زمانی هستند که این احتمال وجود داشته باشد که برنامه طبق انتظار پیش نرود.
برای حالت اول کد زیر را داریم:
public IEnumerable<Product> GetProducts() { using (var stream = File.Read(Path.Combine(Environment.CurrentDirectory, "products.xml"))) { var serializer = new XmlSerializer(); return (IEnumerable<Product>)serializer.Deserialize(stream); } }
در مثال دوم میدانیم که ممکن است فایل از قبل موجود نباشد. بنابراین میتوانیم موجود بودن فایل را با یک شرط بررسی کنیم:
public IEnumerable<Product> GetCachedProducts() { var fullPath = Path.Combine(Environment.CurrentDirectory, "ProductCache.xml"); if (!File.Exists(fullPath)) return new Product[0]; using (var stream = File.Read(fullPath)) { var serializer = new XmlSerializer(); return (IEnumerable<Product>)serializer.Deserialize(stream); } }
چه زمانی باید استثناءها را مدیریت کنیم؟
زمانیکه بتوان متدهایی که خروجی مورد انتظار را بر میگردانند ایجاد کرد.
اجازه دهید دوباره از مثالهای فوق استفاده کنیم:
IEnumerable<Product> GetProducts()
IEnumerable<Product> GetCachedProducts()
در واقع استثناها حالتهایی هستند که غیرقابل پیشبینی هستند. این حالتها میتوانند یک خطای منطقی از طرف برنامهنویس و یا چیزی خارج کنترل برنامهنویس باشند (مانند خطاهای سیستمعامل، شبکه، دیسک). یعنی در بیشتر مواقع این نوع خطاها را نمیتوان مدیریت کرد.
اگر میخواهید استثناءها را catch کرده و آنها را لاگ کنید در بالاترین لایه اینکار را انجام دهید.
چه استثناءهایی باید مدیریت شوند و کدامها خیر؟
مدیریت صحیح استثناءها میتواند خیلی مفید باشد. همانطور که عنوان شد یک استثناء زمانی رخ میدهد که یک حالت استثناء در برنامه اتفاق بیفتد. این مورد را بخاطر داشته باشید، زیرا به شما یادآوری میکند که در همه جا نیازی به استفاده از try/catch نیست. در اینجا ذکر این نکته خیلی مهم است:
تنها استثناءهایی را catch کنید که بتوانید برای آن راهحلی ارائه دهید.
به عنوان مثال اگر در لایهی دسترسی به داده، خطایی رخ دهد و استثناءی SqlException صادر شود، میتوانیم آن را catch کرده و درون یک استثناء عمومیتر قرار دهیم:
public class UserRepository : IUserRepository { public IList<User> Search(string value) { try { return CreateConnectionAndACommandAndReturnAList("WHERE value=@value", Parameter.New("value", value)); } catch (SqlException err) { var msg = String.Format("Ohh no! Failed to search after users with '{0}' as search string", value); throw new DataSourceException(msg, err); } } }
اگر مطمئن نیستید که تمام استثناءها توسط شما مدیریت شدهاند، میتوانید در حالتهای زیر، دیگر استثناءها را مدیریت کنید:
ASP.NET: میتوانید Aplication_Error را پیادهسازی کنید. در اینجا فرصت خواهید داشت تا تمامی خطاهای مدیریت نشده را هندل کنید.
WinForms: استفاده از رویدادهای Application.ThreadException و AppDomain.CurrentDomain.UnhandledException
WCF: پیادهسازی اینترفیس IErrorHandler
ASMX: ایجاد یک Soap Extension سفارشی
ASP.NET WebAPI
چه زمانهایی باید یک استثناء صادر شود؟
صادر کردن یک استثناء به تنهایی کار سادهایی است. تنها کافی است throw را همراه شیء exception (exception object) فراخوانی کنیم. اما سوال اینجاست که چه زمانی باید یک استثناء را صادر کنیم؟ چه دادههایی را باید به استثناء اضافه کنیم؟ در ادامه به این سوالات خواهیم پرداخت.
همانطور که عنوان گردید استثناءها زمانی باید صادر شوند که یک استثناء اتفاق بیفتد.
اعتبارسنجی آرگومانها
سادهترین مثال، آرگومانهای مورد انتظار یک متد است:
public void PrintName(string name) { Console.WriteLine(name); }
مشکل فوق را میتوانیم با صدور استثنای ArgumentNullException رفع کنیم:
public void PrintName(string name) { if (name == null) throw new ArgumentNullException("name"); Console.WriteLine(name); }
public void PrintName(string name) { if (name == null) throw new ArgumentNullException("name"); if (name.Length < 5 || name.Length > 10) throw new ArgumentOutOfRangeException("name", name, "Name must be between 5 or 10 characters long"); if (name.Any(x => !char.IsAlphaNumeric(x)) throw new ArgumentOutOfRangeException("name", name, "May only contain alpha numerics"); Console.WriteLine(name); }
حالت دیگر صدور استثناء، زمانی است که متدی خروجی مورد انتظارمان را نتواند تحویل دهد. یک مثال بحثبرانگیز متدی با امضای زیر است:
public User GetUser(int id) { }
با استفاده از بررسی null کدهایی شبیه به این را در همه جا خواهیم داشت:
var user = datasource.GetUser(userId); if (user == null) throw new InvalidOperationException("Failed to find user: " + userId); // actual logic here
public User GetUser(int id) { if (id <= 0) throw new ArgumentOutOfRangeException("id", id, "Valid ids are from 1 and above. Do you have a parsing error somewhere?"); var user = db.Execute<User>("WHERE Id = ?", id); if (user == null) throw new EntityNotFoundException("Failed to find user with id " + id); return user; }
خطاهای متداول حین کار با استثناءها
- صدور مجدد استثناء و از بین بردن stacktrace
کد زیر را در نظر بگیرید:
try { FutileAttemptToResist(); } catch (BorgException err) { _myDearLog.Error("I'm in da cube! Ohh no!", err); throw err; }
- اضافه نکردن اطلاعات استثناء اصلی به استثناء جدید
یکی دیگر از خطاهای رایج اضافه نکردن استثناء اصلی حین صدور استثناء جدید است:
try { GreaseTinMan(); } catch (InvalidOperationException err) { throw new TooScaredLion("The Lion was not in the m00d", err); //<---- استثناء اصلی بهتر است به استثناء جدید پاس داده شود }
- ارائه ندادن context information
در هنگام صدور یک استثناء بهتر است اطلاعات دقیقی را به آن ارسال کنیم تا دیباگ کردن آن به راحتی انجام شود. به عنوان مثال کد زیر را در نظر داشته باشید:
try { socket.Connect("somethingawful.com", 80); } catch (SocketException err) { throw new InvalidOperationException("Socket failed", err); }
void IncreaseStatusForUser(int userId, int newStatus) { try { var user = _repository.Get(userId); if (user == null) throw new UpdateException(string.Format("Failed to find user #{0} when trying to increase status to {1}", userId, newStatus)); user.Status = newStatus; _repository.Save(user); } catch (DataSourceException err) { var errMsg = string.Format("Failed to find modify user #{0} when trying to increase status to {1}", userId, newStatus); throw new UpdateException(errMsg, err); }
نحوهی طراحی استثناءها
برای ایجاد یک استثناء سفارشی میتوانید از کلاس Exception ارثبری کنید و چهار سازندهی آن را اضافه کنید:
public NewException() public NewException(string description ) public NewException(string description, Exception inner) protected or private NewException(SerializationInfo info, StreamingContext context)
سازندهی دوم برای تعیین description بوده و همانطور که عنوان شد ارائه دادن context information از اهمیت بالایی برخوردار است. به عنوان مثال فرض کنید استثناء KeyNotFoundException که توسط کلاس Dictionary صادر شده است را دریافت کردهاید. این استثناء زمانی صادر خواهد شد که بخواهید به عنصری که درون دیکشنری پیدا نشده است دسترسی داشته باشید. در این حالت پیام زیر را دریافت خواهید کرد:
“The given key was not present in the dictionary.”
“The key ‘abrakadabra’ was not present in the dictionary.”
سازندهی سوم شبیه به سازندهی قبلی عمل میکند با این تفاوت که توسط پارامتر دوم میتوانیم یک استثناء دیگر را catch کرده یک استثناء جدید صادر کنیم.
سازندهی سوم زمانی مورد استفاده قرار میگیرد که بخواهید از Serialization پشتیبانی کنید (به عنوان مثال ذخیرهی استثناءها درون فایل و...)
خوب، برای یک استثناء سفارشی حداقل باید کدهای زیر را داشته باشیم:
public class SampleException : Exception { public SampleException(string description) : base(description) { if (description == null) throw new ArgumentNullException("description"); } public SampleException(string description, Exception inner) : base(description, inner) { if (description == null) throw new ArgumentNullException("description"); if (inner == null) throw new ArgumentNullException("inner"); } public SampleException(SerializationInfo info, StreamingContext context) : base(info, context) { } }
اجباری کردن ارائهی Context information:
برای اجباری کردن context information کافی است یک فیلد اجباری درون سازنده تعریف کنیم. برای مثال اگر بخواهیم کاربر HTTP status code را برای استثناء ارائه دهد باید سازندهها را اینگونه تعریف کنیم:
public class HttpException : Exception { System.Net.HttpStatusCode _statusCode; public HttpException(System.Net.HttpStatusCode statusCode, string description) : base(description) { if (description == null) throw new ArgumentNullException("description"); _statusCode = statusCode; } public HttpException(System.Net.HttpStatusCode statusCode, string description, Exception inner) : base(description, inner) { if (description == null) throw new ArgumentNullException("description"); if (inner == null) throw new ArgumentNullException("inner"); _statusCode = statusCode; } public HttpException(SerializationInfo info, StreamingContext context) : base(info, context) { } public System.Net.HttpStatusCode StatusCode { get; private set; } }
public override string Message { get { return base.Message + "\r\nStatus code: " + StatusCode; } }
public class HttpException : Exception { // [...] public HttpException(SerializationInfo info, StreamingContext context) : base(info, context) { // this is new StatusCode = (HttpStatusCode) info.GetInt32("HttpStatusCode"); } public HttpStatusCode StatusCode { get; private set; } public override string Message { get { return base.Message + "\r\nStatus code: " + StatusCode; } } // this is new public override void GetObjectData(SerializationInfo info, StreamingContext context) { base.GetObjectData(info, context); info.AddValue("HttpStatusCode", (int) StatusCode); } }
در حین صدور استثناءها همیشه باید در نظر داشته باشیم که چه نوع context information را میتوان ارائه داد، این مورد در یافتن راهحل خیلی کمک خواهد کرد.
طراحی پیامهای مناسب
پیامهای exception مختص به توسعهدهندگان است نه کاربران نهایی.
نوشتن این نوع پیامها برای برنامهنویس کار خستهکنندهایی است. برای مثال دو مورد زیر را در نظر داشته باشید:
throw new Exception("Unknown FaileType"); throw new Exception("Unecpected workingDirectory");
توسعهدهندگانی که exception message را در اولویت قرار میدهند، معتقد هستند که از لحاظ تجربهی کاربری پیامها تا حد امکان باید فاقد اطلاعات فنی باشد. همچنین همانطور که پیشتر عنوان گردید این نوع پیامها همیشه باید در بالاترین سطح نمایش داده شوند نه در لایههای زیرین. همچنین پیامهایی مانند Unknown FaileType نه برای کاربر نهایی، بلکه برای برنامهنویس نیز ارزش چندانی ندارد زیرا فاقد اطلاعات کافی برای یافتن مشکل است.
در طراحی پیامها باید موارد زیر را در نظر داشته باشیم:
- امنیت:
یکی از مواردی که از اهمیت بالایی برخوردار است مسئله امنیت است از این جهت که پیامها باید فاقد مقادیر runtime باشند. زیرا ممکن است اطلاعاتی را در خصوص نحوهی عملکرد سیستم آشکار سازند.
- زبان:
همانطور که عنوان گردید پیامهای استثناء برای کاربران نهایی نیستند، زیرا کاربران نهایی ممکن است اشخاص فنی نباشند، یا ممکن است زبان آنها انگلیسی نباشد. اگر مخاطبین شما آلمانی باشند چطور؟ آیا تمامی پیامها را با زبان آلمانی خواهید نوشت؟ اگر هم اینکار را انجام دهید تکلیف استثناءهایی که توسط Base Class Library و دیگر کتابخانههای thirt-party صادر میشوند چیست؟ اینها انگلیسی هستند.
در تمامی حالتهایی که عنوان شد فرض بر این است که شما در حال نوشتن این نوع پیامها برای یک سیستم خاص هستید. اما اگر هدف نوشتن یک کتابخانه باشد چطور؟ در این حالت نمیدانید که کتابخانهی شما در کجا استفاده میشود.
اگر هدف نوشتن یک کتابخانه نباشد این نوع پیامهایی که برای کاربران نهایی باشند، وابستگیها را در سیستم افزایش خواهند داد، زیرا در این حالت پیامها به یک رابط کاربری خاص گره خواهند خورد.
خب اگر پیامها برای کاربران نهایی نیستند، پس برای کسانی مورد استفاده قرار خواهند گرفت؟ در واقع این نوع پیام میتواند به عنوان یک documentation برای سیستم شما باشند.
فرض کنید در حال استفاده از یک کتابخانه جدید هستید به نظر شما کدام یک از پیامهای زیر مناسب هستند:
"Unecpected workingDirectory"
"You tried to provide a working directory string that doesn't represent a working directory. It's not your fault, because it wasn't possible to design the FileStore class in such a way that this is a statically typed pre-condition, but please supply a valid path to an existing directory. "The invalid value was: "fllobdedy"."
همیشه برای نوشتن پیامهای مناسب سعی کنید از لحاظ نوشتاری متن شما مشکلی نداشته باشد، اطلاعات کافی را درون پیام اضافه کنید و تا حد امکان نحوهی رفع مشکل را توضیح دهید
jQuery 3.4.0 منتشر شد
jQuery has a new release! It’s been a while since our last release, but we expect this to be the last minor release in the 3.x branch, and then we will move on to the overhaul that will be jQuery 4.0. But before we get to 4.0, we’re excited to share the bug fixes and improvements included in jQuery 3.4.0
IdentityServer4 v2 منتشر شد
Wow – this was probably our biggest update ever! Version 2.0 of IdentityServer4 is not only incorporating all the feedback we got over the last year, it also includes the necessary updates for ASP.NET Core 2 – and also has a couple of brand new features. See the release notes for a complete list as well as links to issues and PRs.
«حالت» یا state، شیءای است، حاوی اطلاعاتی که برنامه با آن سر و کار دارد. بنابراین مدیریت حالت، روشی است برای ردیابی و مدیریت دادههای مورد استفادهی در برنامه و تقریبا تمام برنامهها، به نحوی به آن نیاز دارند. هر کامپوننت در Blazor، دارای state خاص خودش است و این state از سایر کامپوننتها کاملا مستقل و ایزولهاست. این مورد با بزرگتر شدن برنامه و برقراری ارتباط بین کامپوننتها، مشکل ایجاد میکند. برای مثال اگر قرار است در منوی بالای سایت، تعداد محصولات موجود در سبد خرید یک شخص را نمایش دهیم، این تعداد، حاصل تعامل او با چندین کامپوننت مجزا خواهد بود که اینها الزاما در یک سلسه مراتب هم قرار نمیگیرند و به سادگی نمیتوان اطلاعات را به صورت آبشاری در بین آنها به اشتراک گذاشت. به همین جهت نیاز به روشی برای مدیریت حالت و به اشتراک گذاری آن در بین کامپوننتهای مختلف برنامه وجود دارد و خوشبختانه چون Blazor به همراه یک سیستم تزریق وابستگیهای توکار است، پیاده سازی یک چنین مدیریت کنندهای، سادهاست.
استفاده از الگوی Observer جهت مدیریت حالت برنامههای Blazor
زمانیکه همانند تصویر فوق با یک کامپوننت کار میکنیم، کاربر همواره کارش از تعامل با یک View آغاز میشود. این تعامل سبب صدور رخدادهایی میشود که این رخدادها، حالت و state کامپوننت را تغییر میدهند. تغییر حالت کامپوننت نیز بلافاصله سبب بهروز رسانی View میشود. در این مثال، حالت کامپوننت، داخل همان کامپوننت نگهداری میشود؛ مانند فیلدهایی که در قسمت code@ یک کامپوننت Blazor تعریف میکنیم و محدود به همان کامپوننت هستند.
با بزرگتر شدن برنامه، زمانی خواهد رسید که نیاز است حالت یک کامپوننت را با کامپوننتهای دیگر به اشتراک گذاشت. در این حالت باید این state را از داخل کامپوننت مدنظر استخراج کرد و در جائی دیگر قرار داد که عموما به آن state store گفته میشود:
در تصویر فوق، در بالای آن یک state store را داریم که محل نگهداری و ذخیره سازی حالت اشتراکی بین کامپوننتها است. سپس برای نمونه دو کامپوننت دیگر را داریم که رابطهی بین آنها، همان رابطهی مثلثی است که در تصویر اول این مطلب مشاهده کردیم. برای مثال در اثر تعامل کاربری با View کامپوننت 1، رخدادی صادر خواهد شد. مدیریت این رخداد، سبب تغییر state خواهد شد، اما اینبار این state دیگر داخل کامپوننت 1 قرار ندارد؛ بلکه داخل state store است و این store پس از آگاه شدن از تغییر وضعیت خود، دو کامپوننتی را که از آن تغدیه میکنند، جهت به روز رسانی Viewهایشان، مطلع میکند. همین چرخه در مورد کامپوننت 2 نیز برقرار است. اگر تعاملی با آن صورت گیرد، در نهایت اثر آن به هر دو کامپوننت متصل به state store اشتراکی، اطلاع رسانی میشود تا Viewهای هر دوی آنها به روز رسانی شوند. الگویی را که در اینجا مشاهده میکنید، در اصل یک الگوی Observer است:
در الگوی مشاهدهگر، یک Subject را داریم که تعداد زیادی Observer، مشترک آن هستند. در این مثال ما، Subject، همان State Store است و Observerها دقیقا همان کامپوننتهای مشترک به آن. Observerها به تغییرات Subject گوش فرا داده و بلافاصله بر اساس آن واکنش مناسبی را نشان میدهند.
پیاده سازی الگوی Observer جهت مدیریت حالت برنامههای Blazor
زمانیکه یک برنامهی متداول Blazor را توسط قالب پیشفرض آن ایجاد میکنیم، به همراه یک کامپوننت Counter است:
@page "/counter" <h1>Counter</h1> <p>Current count: @currentCount</p> <button class="btn btn-primary" @onclick="IncrementCount">Click me</button> @code { private int currentCount = 0; private void IncrementCount() { currentCount++; } }
بنابراین در قدم اول نیاز به یک State Store اشتراکی را داریم که بتوانیم توسط آن، مقدار جاری currentCount را ذخیره کرده و سپس تغییرات آنرا جهت به روز رسانی دو View (در کامپوننتهای Counter و NavMenu)، به مشترکین آن اطلاع رسانی کنیم. به همین جهت ابتدا پوشهی جدید Stores را در ریشهی پروژهی Blazor ایجاد میکنیم. نام این پوشه، از این جهت یک اسم جمع است که یک برنامه بنابر نیاز خودش میتواند چندین State Store را داشته باشد. سپس داخل این پوشه، پوشهی دیگری را به نام CounterStore، ایجاد میکنیم.
در اینجا در ابتدا شیء حالت مدنظر را ایجاد میکنیم که برای نمونه بر اساس نیاز برنامه و این مثال، از مقدار نهایی کلیک بر روی دکمهی شمارشگر تشکیل میشود:
namespace BlazorStateManagement.Stores.CounterStore { public class CounterState { public int Count { get; set; } } }
using System; namespace BlazorStateManagement.Stores.CounterStore { public interface ICounterStore { void DecrementCount(); void IncrementCount(); CounterState GetState(); void AddStateChangeListener(Action listener); void BroadcastStateChange(); void RemoveStateChangeListener(Action listener); } }
using System; namespace BlazorStateManagement.Stores.CounterStore { public class CounterStore : ICounterStore { private readonly CounterState _state = new(); private Action _listeners; public CounterState GetState() { return _state; } public void IncrementCount() { _state.Count++; BroadcastStateChange(); } public void DecrementCount() { _state.Count--; BroadcastStateChange(); } public void AddStateChangeListener(Action listener) { _listeners += listener; } public void RemoveStateChangeListener(Action listener) { _listeners -= listener; } public void BroadcastStateChange() { _listeners.Invoke(); } } }
- مخزن حالت پیاده سازی شدهی بر اساس الگوی مشاهدهگر، نیاز دارد تا بتواند لیست مشاهدهگرها را ثبت کند. به همین جهت به همراه متدهای AddStateChangeListener جهت ثبت یک مشاهدهگر جدید و RemoveStateChangeListener، جهت حذف مشاهدهگری از لیست موجود است.
- همچنین الگوی مشاهدهگر باید بتواند تغییرات صورت گرفتهی در حالتی را که نگهداری میکند (CounterState در اینجا)، به مشترکین خود اطلاع رسانی کند. اینکار را توسط متد BroadcastStateChange انجام میدهد. هر زمانیکه این متد فراخوانی شود، Actionهایی که به صورت پارامتر به متد AddStateChangeListener ارسال شدهاند، به صورت خودکار اجرا خواهند شد. این کار سبب میشود تا بتوان منطق خاصی را مانند به روز رسانی UI، در سمت کامپوننتهای مشترک به این مخزن، پیاده سازی کرد.
- در اینجا همچنین متدهایی برای افزایش و کاهش مقدار Count را نیز به همراه اطلاع رسانی به مشترکین، مشاهده میکنید.
پس از این تعریف نیاز است سرویس Store ایجاد شده را به برنامه معرفی کرد:
namespace BlazorStateManagement.Client { public class Program { public static async Task Main(string[] args) { var builder = WebAssemblyHostBuilder.CreateDefault(args); //... builder.Services.AddScoped<ICounterStore, CounterStore>(); //... } } }
تغییر کامپوننتهای برنامه برای استفاده از سرویس ICounterStore
پس از معرفی سرویس ICounterStore به سیستم تزریق وابستگیهای برنامه، جهت سهولت استفادهی از آن، در ابتدا فضای نام آنرا به فایل سراسری Client\_Imports.razor اضافه میکنیم:
@using BlazorStateManagement.Stores.CounterStore
@page "/counter" @implements IDisposable @inject ICounterStore CounterStore <h1>Counter</h1> <p>Current count: @CounterStore.GetState().Count</p> <button class="btn btn-primary" @onclick="IncrementCount">Click me</button> @code { protected override void OnInitialized() { base.OnInitialized(); CounterStore.AddStateChangeListener(UpdateView); } private void IncrementCount() { CounterStore.IncrementCount(); } private void UpdateView() { StateHasChanged(); } public void Dispose() { CounterStore.RemoveStateChangeListener(UpdateView); } }
- در اینجا در ابتدا سرویس ICounterStore، به کامپوننت تزریق شدهاست.
- سپس در متد رویدادگران آغازین OnInitialized، با استفاده از متد AddStateChangeListener، مشترک سرویس مخزن حالت شمارشگر شدهایم.
- همواره جهت پاکسازی کد و عدم اشتراک بیش از اندازهی به یک مخزن حالت، نیاز است در پایان کار یک کامپوننت، با پیاده سازی implements IDisposable@، کار حذف اشتراک را انجام دهیم. در غیراینصورت هربار که کامپوننت بارگذاری میشود، یک اشتراک جدید از این کامپوننت، به مخزن حالتی که طول عمر Singleton دارد، اضافه خواهد شد که نشانی از نشتی حافظهاست.
- دو قسمت دیگر را هم تغییر دادهایم. اینبار با استفاده از متد ()GetState، این Count اشتراکی را نمایش میدهیم و همچنین عمل به روز رسانی State را هم توسط متد IncrementCount انجام دادهایم.
در ادامه کامپوننت Client\Shared\NavMenu.razor را نیز جهت نمایش مقدار جاری Count، به صورت زیر به روز رسانی میکنیم:
@inject ICounterStore CounterStore <li class="nav-item px-3"> <NavLink class="nav-link" href="counter"> <span class="oi oi-plus" aria-hidden="true"></span> Counter: @CounterStore.GetState().Count </NavLink> </li> @code { protected override void OnInitialized() { base.OnInitialized(); CounterStore.AddStateChangeListener(() => StateHasChanged()); } // ... }
- در اینجا نیز در ابتدا سرویس ICounterStore، به کامپوننت تزریق شدهاست.
- سپس در متد رویدادگران آغازین OnInitialized، با استفاده از متد AddStateChangeListener، مشترک سرویس مخزن حالت شمارشگر شدهایم و هربار که متد BroadcastStateChange ای توسط یکی از کامپوننتهای متصل به مخزن حالت فراخوانی میشود (برای مثال در انتهای متد IncrementCount خود سرویس)، سبب اجرای Action آن که در اینجا StateHasChanged است، خواهد شد. فراخوانی StateHasChanged، کار اطلاع رسانی به UI، جهت رندر مجدد را انجام میدهد. به این ترتیب مقدار جدید Count توسط CounterStore.GetState().Count@ در منو نیز ظاهر خواهد شد:
کدهای کامل این مطلب را از اینجا میتوانید دریافت کنید: BlazorStateManagement.zip
در فضای برنامه نویسی مراد از VC.NET زبان برنامه نویسی CPP تحت CLI است یعنی با سینتکس CPP و کامپایلر .NET یعنی همان کامپایلری که C#.NET و VB.NET استفاده میکنند. یا به عبارتی Managed CPP. در منوی New Project در Visual Studio گزینه CLR باید انتخاب شود.
آموزش هایی که شما ارائه کرده اید مربوط به Native CPP است. برنامه نویسی MFC یا VCL یا Win32 یا.. متفاوت با VC++.NET است.در منوی New Project در Visual Studio گزینههای غیر از CLR همگی native هستند.
بهتر است مشابه msdn از واژه ++Visual C استفاده کنید:
++Visual C
++NET Programming in Visual C.
با تشکر