کار با Areas در ASP.NET Core
ساخت منوهای چند سطحی در ASP.NET MVC
زمانی که منو رو به صورت پویا ایجاد میکنیم در هر بار لود صفحه باید اطلاعات مربوط به منو از بانک اطلاعاتی دریافت بشه آیا اطلاعات کش میشه از روش خاصی استفاده میشه که در هربار لود صفحه درخواست به بنک ارسال نشه
تا اینجای کار ساخت کامپوننتها را با React.createClass که تفاوتی با توسعه (ارث بری) از کلاس React.Component ندارد، انجام دادهایم. اما ساخت کامپوننتها به صورت یک تابع هم مزیتهایی را دارد. اول از همه باید بدانیم که ساخت کامپوننت توسط تابع، بدون وضعیت خواهد بود که به آن Stateless میگویند. به دلیل نداشتن وضعیت، کامپوننتهای تابعی را کمی بهتر میشود برای استفاده مجدد به کار برد. در کامپوننتهای غیر تابعی که Stateful هستند به دلیل احتمال وابستگی وضعیت کامپوننت به خارج از کلاس، مانند مثال قسمت چهارم که کامپوننت در انتظار کلیک یک دکمه خاص توسط کاربر بود، مدیریت استفاده مجدد ازکامپوننت چالش برانگیز خواهد شد.
اعتبارسنجی دادههای ورودی
برای مدیریت بهتر کامپوننتها جهت استفاده مجدد از آنها بهتر است ورودیهای کامپوننت را اعتبارسنجی کنیم. این ورودیها چه برای استفاده داخلی کامپوننت، یا جهت مشخص کردن وضعیت آن، بر رفتار کامپوننت تاثیر زیادی دارند. React مجموعهای از اعتبار سنجیها را دارد که میشود به کامپوننت اضافه کرد. باید توجه داشته باشیم که پیامهای خطای این اعتبارسنجیها فقط در حالت Development Mode قابل استفاده هستند. به زبان ساده اگر از react.min.js استفاده کنید، پیامهای خطا را نخواهید دید. باید فایلها را به نوع react.js تبدیل کنید. اعتبارسنجی React در زمان توسعه و برای توسعه دهندگان استفاده میشود.
مثال نوشیدنیها در قسمت چهارم میتوانست نام نوشیدنی و قیمت آن را نمایش دهد و همچنین میتوانستیم به لیست نوشیدنیها، موردی را اضافه کنیم. اگر ورودی قیمت، اعتبارسنجی نشود، میتوان رشتهای را بجای عدد به عنوان قیمت به کامپوننت ارسال کرد. نحوه اعتبارسنجی قیمت نوشیدنیها به صورت زیر است.
const MenuItem = props => ( <li className="list-group-item"> <span className="badge">{props.price}</span> <p>{props.item}</p> </li> ) MenuItem.propTypes = { price: React.PropTypes.number };
شیء propTypes را به کامپوننت اضافه کردهایم و در تنظیمات آن میتوانیم برای هر پارامتر ورودی یکی از اعضای PropTypes از React را که مناسب حال پارامتر است، انتخاب کنیم. در مثال بالا مشخص کردهایم که ورودی price باید عدد باشد و اگر مثلا رشتهای را بجای عدد ارسال کنیم، پیام خطای زیر را در Console خواهیم داشت.
Warning: Failed prop type: Invalid prop `price` of type `string` supplied to `MenuItem`, expected `number`. in MenuItem (created by Menu) in Menu
یا میتوانستیم از React.PropTypes.number.isRequired استفاده کنیم تا درج مقداری برای این ورودی الزامی باشد. اگر اعتبارسنجیهای React کافی نبودند میتوانیم اعتبارسنجیهای سفارشی خودمان را ایجاد کنیم. در مثال زیر میخواهیم ورودی price بیشتر از 15000 نباشد.
MenuItem.propTypes = { price: (props, price)=>{ if(props[price] > 15000){ return new Error("Too expensive!"); } } };
let MenuItem = React.createClass({ propTypes: { price: React.PropTypes.number } }); class MenuItem extends React.Component{ static propTypes = { price: React.PropTypes.number }; }
نوعهای دیگر برای اعتبارسنجی شامل موارد زیر هستند و البته مرجع تمام اعتبارسنجیهای React را میتوانید در اینجا بررسی کنید.
- React.PropTypes.array
- React.PropTypes.bool
- React.PropTypes.number
- React.PropTypes.object
- React.PropTypes.string
مقدار پیشفرض دادههای ورودی
یکی از امکانات مفید دیگر برای مدیریت مقدارهای ورودی، مشخص کردن مقدار پیشفرضی برای یک پارامتر است. برای مثال اگر برای قیمت یک نوشیدنی مقداری وارد نشد، یک حداقل قیمت برای آن در نظر بگیریم، هر چند که ایده و روشی اشتباه است!
MenuItem.defaultProps = { price: 1000 };
همانطور که میبینید روش کار مشابه با اعتبارسنجی است و برای مشخص کردن مقدار پیشفرض برای React.creatClass از متد getDefaultProps که عضوی از React است، استفاده میکنیم.
let MenuItem = React.createClass({ getDefaultProps() { return { price: 200 } }, render() { return ( <li className="list-group-item"> <span className="badge">{this.props.price}</span> <p>{this.props.item}</p> </li> ); } });
class HandlerChain
{
setNextObj(nextObjInChain){}
processMultiple(req){
console.log("No multiple for: " + req.getMultiple());
}
}
class Multiple
{
constructor(multiple){
this.multiple = multiple;
}
getMultiple(){
return this.multiple;
}
}
class MultipleofTwoHandler extends HandlerChain
{
constructor(){
super()
this.nextObjInChain = new HandlerChain()
}
setNextObj(nextObj){
this.nextObjInChain = nextObj;
}
processMultiple(req) {
if ((req.getMultiple() % 2) == 0) {
console.log("Multiple of 2: " + req.getMultiple());
}else{
this.nextObjInChain.processMultiple(req);
}
}
}
class MultipleofThreeHandler extends HandlerChain
{
constructor(){
super()
this.nextObjInChain = new HandlerChain()
}
setNextObj(nextObj){
this.nextObjInChain = nextObj;
}
processMultiple(req)
{
if ((req.getMultiple() % 3) == 0) {
console.log("Multiple of 3: " + req.getMultiple());
}else{
this.nextObjInChain.processMultiple(req);
}
}
}
class MultipleofFiveHandler extends HandlerChain
{
constructor(){
super()
this.nextObjInChain = new HandlerChain()
}
setNextObj(nextObj){
this.nextObjInChain = nextObj;
}
processMultiple(req) {
if ((req.getMultiple() % 5) == 0) {
console.log("Multiple of 5: " + req.getMultiple());
}else{
this.nextObjInChain.processMultiple(req);
}
}
}
//configuring the chain of handler objects
var c1 = new MultipleofTwoHandler();
var c2 = new MultipleofThreeHandler();
var c3 = new MultipleofFiveHandler();
c1.setNextObj(c2);
c2.setNextObj(c3);
//the chain handling different cases
c1.processMultiple(new Multiple(95)); // Multiple of 5: 95
c1.processMultiple(new Multiple(50)); // Multiple of 2: 50
c1.processMultiple(new Multiple(9)); // Multiple of 3: 9
c1.processMultiple(new Multiple(4)); // Multiple of 2: 4
c1.processMultiple(new Multiple(21)); // Multiple of 3: 21
c1.processMultiple(new Multiple(23)); // No multiple for: 23
- MultipleofTwoHandler: بررسی میکند که آیا عدد وارد شده، مضربی از 2 است یا نه.
- MultipleofThreeHandler: بررسی میکند که آیا عدد وارد شده، مضربی از 3 است یا نه
- MultipleofFiveHandler: بررسی میکند که آیا عدد وارد شده، مضربی از 5 است یا نه.
class HandlerChain { setNextObj(nextObjInChain){} processMultiple(req){ console.log("No multiple for: " + req.getMultiple()); } }
- تنظیم کردن handler بعدی در زنجیره
- پردازش عدد وارد شده به این منظور که آیا در مضرب مورد نظر قرار دارد یا نه.
class MultipleofTwoHandler extends HandlerChain { constructor(){/*code*/} setNextObj(nextObj){/*code*/} processMultiple(req){/*code*/} } class MultipleofThreeHandler extends HandlerChain { constructor(){/*code*/} setNextObj(nextObj){/*code*/} processMultiple(req){/*code*/} } class MultipleofFiveHandler extends HandlerChain { constructor(){/*code*/} setNextObj(nextObj){/*code*/} processMultiple(req){/*code*/} }
constructor(){ super() this.nextObjInChain = new HandlerChain() }
مقدار دهی اولیه میشود که تعیین کنندهی شیء بعدی در زنجیره میباشد.
setNextObj(nextObj){ this.nextObjInChain = nextObj; }
var c1 = new MultipleofTwoHandler(); var c2 = new MultipleofThreeHandler(); var c3 = new MultipleofFiveHandler(); c1.setNextObj(c2); c2.setNextObj(c3);
class Multiple { constructor(multiple){ this.multiple = multiple } getMultiple(){ return this.multiple; } }
processMultiple(req) { if ((req.getMultiple() % 2) == 0) { console.log("Multiple of 2: " + req.getMultiple()); }else{ this.nextObjInChain.processMultiple(req); } }
c1.processMultiple(new Multiple(95)) // Multiple of 5: 95
تنظیمات IIS برای توزیع فونتهای وب
- حجم فونتهای WOFF2 از WOFF (نسخه اول)، 20 درصد کمتر است.
- شاید مرورگر نتواند فونتهای WOFF2 را بدلیل عدم نگاشت آنها در IIS دریافت نماید و از سایر فونتهای تعریف شده استفاده نماید اما در هر درخواست ارسالی به سرور خطای 404 رخ میدهد که باعث اتلاف پهنای باند و رفت و برگشتهای بیهوده به سرور میشود.
- درخواست منابعی که با موفقیت انجام میشوند مرورگر از ارسال مجدد آنها خودداری میکند ولی درخواستهای 404 سمت مرورگر کش نمیشوند و در هر درخواست ارسال میگردند، بنابراین جلوگیری از این اتفاق برای منابعی که در هر بارگذاری صفحه فراخوانی میشوند مانند favicon.ico الزامی است.
namespace EntitySample1.DomainClasses { public class Person { public int Id { get; set; } public string FirstName { get; set; } public string LastName { get; set; } public DateTime BirthDate { get; set; } public virtual PersonInfo PersonInfo { get; set; } public virtual ICollection<PhoneNumber> PhoneNumbers { get; set; } public virtual ICollection<Address> Addresses { get; set; } } }
namespace EntitySample1.DomainClasses { public class PersonInfo { public int Id { get; set; } public string Note { get; set; } public string Major { get; set; } } }
namespace EntitySample1.DomainClasses { public enum PhoneType { Home, Mobile, Work } public class PhoneNumber { public int Id { get; set; } public string Number { get; set; } public PhoneType PhoneType { get; set; } public virtual Person Person { get; set; } } }
namespace EntitySample1.DomainClasses { public class Address { public int Id { get; set; } public string City { get; set; } public string Street { get; set; } public virtual ICollection<Person> Persons { get; set; } } }
namespace EntitySample1.DataLayer { public class PhoneBookDbContext : DbContext { public DbSet<Person> Persons { get; set; } public DbSet<PhoneNumber> PhoneNumbers { get; set; } public DbSet<Address> Addresses { get; set; } } }
استفاده از ردیابی تغییر عکس فوری
ردیابی تغییر عکس فوری، وابسته به این است که EF بفهمد، چه زمانی تغییرات رخ داده است. رفتار پیش فرض DbContext API ، این هست که به صورت خودکار بازرسی لازم را در نتیجهی رخدادهای DbContext انجام دهد. DetectChanges تنها اطلاعات مدیریت حالت context، که وظیفهی انعکاس تغییرات صورت گرفته به پایگاه داده را دارد، به روز نمیکند، بلکه اصلاح رابطه(ralationship) ترکیبی از خواص راهبری مرجع ، مجموعه ای و کلیدهای خارجی را انجام میدهد. این خیلی مهم خواهد بود که درک روشنی داشته باشیم از این که چگونه و چه زمانی تغییرات تشخیص داده میشوند،چه چیزی باید از آن انتظار داشته باشیم و چگونه کنترلش کنیم.
چه زمانی تشخیص خودکار تغییرات اجرا میشود؟
متد DetectChanges کلاس ObjectContext، از EF نسخهی 4 به عنوان بخشی از الگوی ردیابی تغییر عکس فوری اشیای POCO ،در دسترس بوده است. تفاوتی که در مورد DataContext.ChangeTracker.DetectChanges( در حقیقت ObjectContext.DetectChanges فراخوانی میشود) وجود دارد این است که، رویدادهای خیلی بیشتری وجود دارند که به صورت خودکار DetectChanges را فراخوانی میکنند.
لیستی از متدهایی که باعث انجام عمل تشخیص تغییرات (DetectChanges)، میشوند را در ادامه مشاهده میکنید:
• DbSet.Add
• DbSet.Find
• DbSet.Remove
• DbSet.Local
• DbSet.SaveChanges
• فراخوانی Linq Query از DbSet
• DbSet.Attach
• DbContext.GetValidationErrors
• DbContext.Entry
• DbChangeTracker.Entries
کنترل زمان فراخوانی DetectChanges
بیشترین زمانی که EF احتیاج به فهمیدن تغییرات دارد، در زمان SaveChanges است، اما حالتهای زیاد دیگری نیز هست. برای مثال، اگر ما از ردیاب تغییرات، درخواست وضعیت فعلی یک شی را بکنیم،EF احتیاج به اسکن کردن و بررسی تغییرات رخ داده را دارد. همچنین وضعیتی را در نظر بگیرید که شما از پایگاه داده یک شماره تلفن را واکشی میکنید و سپس آن را به مجموعه شماره تلفنهای یک شخص جدید اضافه میکنید.آن شماره تلفن اکنون تغییر کرده است، چرا که انتساب آن به یک شخص جدید،خاصیت PersonId آن را تغییر داده است. ولی EF برای اینکه بفهمد تغییر رخ داده است(یا حتی نداده است) ، احتیاج به اسکن کردن همهی اشیا Person دارد.
بیشتر عملیاتی که بر روی DbContext API انجام میدهید، موجب فراخوانی DetectChanges میشود. در بیشتر موارد DetectChanges به اندازه کافی سریع هست تا باعث ایجاد مشکل کارایی نشود. با این حال ممکن است ، شما تعداد خیلی زیادی اشیا در حافظه داشته باشید، و یا تعداد زیادی عملیات در DbContext ، در مدت خیلی کوتاهی انجام دهید، رفتار تشخیص خودکار تغییرات ممکن است، باعث نگرانیهای کارایی شود. خوشبختانه گزینه ای برای خاموش کردن رفتار تشخیص خودکار تغییرات وجود دارد و هر زمانی که میدانید لازم است، میتوانید آن را به صورت دستی فراخوانی کنید.
EF بر مبنای این فرض ساخته شده است که شما ، در صورتی که در فراخوانی آخرین API، موجودیتی تغییر پیدا کرده است، قبل از فراخوانی API جدید، باید DetectChanges صدا زده شود. این شامل فراخوانی DetectChanges، قبل از اجرای هر query نیز میشود.اگر این عمل ناموفق یا نابجا انجام شود،ممکن است عواقب غیر منتظره ای در بر داشته باشد. DbContext انجام این وظیفه را بر عهده گرفته است و به همین دلیل به طور پیش فرض تشخیص تغییرات خودکار آن فعال است.
نکته: تشخیص اینکه چه زمانی احتیاج به فراخوانی DetectChanges است،آن طور که ساده و بدیهی به نظر میآید نیست. تیم EF شدیدا توصیه کرده اند که فقط، وقتی با مشکلات عدم کارایی روبرو شدید، تشخیص تغییرات را به حالت دستی در بیاورید.همچنین توصیه شده که در چنین مواقعی، تشخیص خودکار تغییرات را فقط برای قسمتی از کد که با کارایی پایین مواجه شدید خاموش کنید و پس از اینکه اجرای آن قسمت از کد تمام شد،دوباره آن را روشن کنید.
برای خاموش یا روشن کردن تشخیص خودکار تغییرات، باید متغیر بولین DbContext.Configuration.AutoDetectChangesEnabled را تنظیم کنید.
در مثال زیر، ما در متد ManualDetectChanges، تشخیص خودکار تغییرات را خاموش کرده ایم و تاثیرات آن را بررسی کرده ایم.
private static void ManualDetectChanges() { using (var context = new PhoneBookDbContext()) { context.Configuration.AutoDetectChangesEnabled = false; // turn off Auto Detect Changes var p1 = context.Persons.Single(p => p.FirstName == "joe"); p1.LastName = "Brown"; Console.WriteLine("Before DetectChanges: {0}", context.Entry(p1).State); context.ChangeTracker.DetectChanges(); // call detect changes manually Console.WriteLine("After DetectChanges: {0}", context.Entry(p1).State); } }
در کدهای بالا ابتدا تشخیص خودکار تغییرات را خاموش کرده ایم و سپس یک شخص با نام joe را از دیتابیس فراخواندیم و سپس نام خانوادگی آن را به Brown تغییر دادیم. سپس در خط بعد، وضعیت فعلی موجودیت p1 را از context جاری پرسیدیم. در خط بعدی، DetectChanges را به صورت دستی صدا زده ایم و دوباره همان پروسه را برای به دست آوردن وضیعت شی p1، انجام داده ایم. همان طور که میبینید ، برای به دست آوردن وضعیت فعلی شی مورد نظر از متد Entry متعلق به ChangeTracker API استفاده میکنیم، که در آینده مفصل در مورد آن بحث خواهد شد. اگر شما متد Main را با صدا زدن ManualDetectChanges ویرایش کنید ، خروجی زیر را مشاهده خواهید کرد:
Before DetectChanges: Unchanged After DetectChanges: Modified
همان طور که انتظار میرفت، به دلیل خاموش کردن تشخیص خودکار تغییرات، context قادر به تشخیص تغییرات صورت گرفته در شی p1 نیست، تا زمانی که متد DetectChanges را به صورت دستی صدا بزنیم. دلیل این که در دفعه اول، ما نتیجهی غلطی مشاهده میکنیم، این است که ما قانون را نقض کرده ایم و قبل از صدا زدن هر API ، متد DetectChanges را صدا نزده ایم. خوشبختانه چون ما در اینجا وضعیت یک شی را بررسی کردیم، با عوارض جانبی آن روبرو نشدیم.
نکته: به این نکته توجه داشته باشید که متد Entry به صورت خودکار، DetectChanges را فراخوانی میکند. برای اینکه دانسته بخواهیم این رفتار را غیر فعال کنیم، باید AutoDetectChangesEnabled را غیر فعال کنیم.
در مثال فوق ،خاموش کردن تشخیص خودکار تغییرات، برای ما مزیتی به همراه نداشت و حتی ممکن بود برای ما دردسر ساز شود. ولی حالتی را در نظر بگیرید که ما یک سری API را فراخوانی میکنیم ،بدون این که در این بین ،در حالت اشیا تغییری ایجاد کنیم.در نتیجه میتوانیم از فراخوانیهای بی جهت DetectChanges جلوگیری کنیم.
در متد AddMultiplePersons مثال بعدی، این کار را نشان داده ام:
private static void AddMultiplePerson() { using (var context = new PhoneBookDbContext()) { context.Configuration.AutoDetectChangesEnabled = false; context.Persons.Add(new Person { FirstName = "brad", LastName = "watson", BirthDate = new DateTime(1990, 6, 8) }); context.Persons.Add(new Person { FirstName = "david", LastName = "brown", BirthDate = new DateTime(1990, 6, 8) }); context.Persons.Add(new Person { FirstName = "will", LastName = "smith", BirthDate = new DateTime(1990, 6, 8) }); context.SaveChanges(); } }
استفاده از DetectChanges برای فراخوانی اصلاح رابطه
DetectChanges همچنین مسئولیت انجام اصلاح رابطه ، برای هر رابطه ای که تشخیص دهد تغییر کرده است را دارد.اگر شما بعضی از روابط را تغییر دادید و مایل بودید تا همهی خواص راهبری و خواص کلید خارجی را منطبق کنید، DetectChanges این کار را برای شما انجام میدهد. این قابلیت میتواند برای سناریوهای data-binding که در آن ممکن است در رابط کاربری(UI) یکی از خواص راهبری (یا حتی یک کلید خارجی) تغییر کند، و شما بخواهید که خواص دیگری این رابطه به روز شوند و تغییرات را نشان دهند، مفید واقع شود.
متد DetectRelationshipChanges در مثال زیر از DetectChanges برای انجام اصلاح رابطه استفاده میکند.
private static void DetectRelationshipChanges() { using (var context = new PhoneBookDbContext()) { var phone1 = context.PhoneNumbers.Single(x => x.Number == "09351234567"); var person1 = context.Persons.Single(x => x.FirstName == "will"); person1.PhoneNumbers.Add(phone1); Console.WriteLine("Before DetectChanges: {0}", phone1.Person.FirstName); context.ChangeTracker.DetectChanges(); // ralationships fix-up Console.WriteLine("After DetectChanges: {0}", phone1.Person.FirstName); } }
در اینجا ابتدا ما شماره تلفنی را از دیتابیس لود میکنیم. سپس شخص دیگری را نیز با نام will از دیتابیس میخوانیم. قصد داریم شماره تلفن خوانده شده را به این شخص نسبت دهیم و مجموعه شماره تلفنهای وی اضافه کنیم و ما این کار را با افزودن phone1 به مجموعه شماره تلفنهای person1 انجام داده ایم. چون ما از اشیای POCO استفاده کرده ایم،EF نمیفهمد که ما این تغییر را ایجاد کرده ایم و در نتیجه کلید خارجی PersonId شی phone1 را اصلاح نمیکند. ما میتوانیم تا زمانی صبر کنیم تا متدی مثل SaveChanges، متد DetectChanges را فراخوانی کند،ولی اگر بخواهیم این عمل در همان لحظه انجام شود، میتوانیم DetectChanges را دستی صدا بزنیم.
اگر ما متد Main را با اضافه کردن فرخوانی DetectRealtionShipsChanges تغییر بدهیم و آن را اجرا کنیم، نتیجه زیر را مشاهده میکنید:
Before DetectChanges: david After DetectChanges: will
تا قبل از فراخوانی تشخیص تغییرات(DetectChanegs)، هنوز phone1 منتسب به شخص قدیمی(david) بوده، ولی پس از فراخوانی DetectChanges ، اصلاح رابطه رخ داده و همه چیز با یکدیگر منطبق میشوند.
فعال سازی و کار با پروکسیهای ردیابی تغییر
اگر پروفایلر کارایی شما، فراخوانیهای بیش از اندازه DetectChnages را به عنوان یک مشکل شناسایی کند، و یا شما ترجیح میدهید که اصلاح رابطه به صورت بلادرنگ صورت گیرد ، ردیابی تغییر پروکسیهای پویا، به عنوان گزینه ای دیگر مطرح میشود.فقط با چند تغییر کوچک در کلاسهای POCO، EF قادر به ساخت پروکسیهای پویا خواهد بود.پروکسیهای ردیابی تغییر به EF اجازه ردیابی تغییرات در همان لحظه ای که ما تغییری در اشیای خود میدهیم را میدهند و همچنین امکان انجام اصلاح رابطه را در هر زمانی که تغییرات روابط را تشخیص دهد، دارد.
برای اینکه پروکسی ردیابی تغییر بتواند ساخته شود، باید قوانین زیر رعایت شود:
• کلاس باید public باشد و seald نباشد.
• همهی خواص(properties) باید virtual تعریف شوند.
• همهی خواص باید getter و setter با سطح دسترسی public داشته باشند.
• همهی خواص راهبری مجموعه ای باید نوعشان، از نوع ICollection<T> تعریف شوند.
کلاس Person مثال خود را به گونه ای بازنویسی کرده ایم که تمام قوانین فوق را پیاده سازی کرده باشد.
نکته: توجه داشته باشید که ما دیگر در داخل سازنده کلاس ،کدی نمینویسیم و منطقی که باعث نمونه سازی اولیه خواص راهبری میشدند، را پیاده سازی نمیکنیم. این پروکسی ردیاب تغییر، همهی خواص راهبری مجموعه ای را تحریف کرده و ار نوع مجموعه ای مخصوص خود(EntityCollection<T>) استفاده میکند. این نوع مجموعه ای، هر تغییری که در این مجموعه صورت میگیرد را زیر نظر گرفته و به ردیاب تغییر گزارش میدهد. اگر تلاش کنید تا نوع دیگری مانند List<T> که معمولا در سازنده کلاس از آن استفاده میکردیم را به آن انتساب دهیم، پروکسی، استثنایی را پرتاب میکند.
namespace EntitySample1.DomainClasses { public class Person { public virtual int Id { get; set; } public virtual string FirstName { get; set; } public virtual string LastName { get; set; } public virtual DateTime BirthDate { get; set; } public virtual PersonInfo PersonInfo { get; set; } public virtual ICollection<PhoneNumber> PhoneNumbers { get; set; } public virtual ICollection<Address> Addresses { get; set; } } }
با این که احتیاجات رسیدن به پروکسیهای ردیابی تغییر خیلی ساده هستند، اما سادهتر از آن ها، فراموش کردن یکی از آن هاست.حتی از این هم سادهتر میشود که در آینده تغییری در آن کلاسها ایجاد کنید و ناخواسته یکی از آن قوانین را نقض کنید.به این خاطر، فکر خوبیست که یک آزمون واحد نیز اضافه کنیم تا مطمئن شویم که EF توانسته، پروکسی ردیابی تغییر را ایجاد کند یا نه.
در مثال زیر یک متد نوشته شده که این مورد را مورد آزمایش قرار میدهد. همچنین فراموش نکنید که فضای نام System.Data.Object.DataClasses را به usingهای خود اضافه کنید.
private static void TestForChangeTrackingProxy() { using (var context = new PhoneBookDbContext()) { var person = context.Persons.First(); var isProxy = person is IEntityWithChangeTracker; Console.WriteLine("person is a proxy: {0}", isProxy); } }
اکنون متد ManualDetectChanges را که کمی بالاتر بررسی کرده ایم را در نظر بگیرید و کد context.ChangeTracker.DetectChanges آن را حذف کنید و بار دیگر آن را فرا بخوانید و نتیجه را مشاهده کنید:
Before DetectChanges: Modified After DetectChanges: Modified
اکنون متد DetectRelationshipChanges را ویرایش کرده و برنامه را اجرا کنید:
Before DetectChanges: will After DetectChanges: will
نکته: زمانی که شما از پروکسیهای ردیابی تغییر استفاده میکنید،احتیاجی به غیرفعال کردن تشخیص خودکار تغییرات نیست. DetectChanges برای همه اشیایی که تغییرات را به صورت بلادرنگ گزارش میدهند،فرآیند تشخیص تغییرات را انجام نمیدهد. بنابراین فعال سازی پروکسیهای ردیابی تغییر،برای رسیدن به مزایای کارایی بالا در هنگام عدم استفاده از DetectChanges کافی است. در حقیقت زمانی که EF، یک پروکسی ردیابی پیدا میکند، از مقادیر خاصیت ها، عکس فوری نمیگیرد. همچنین DetectChanges این را نیز میداند که نباید تغییرات موجودیت هایی که عکسی از مقادیر اصلی آنها ندارد را اسکن کند.
تذکر: اگر شما موجودیت هایی داشته باشید که شامل انواع پیچیده(Complex Types) میشوند،EF هنوز هم از ردیابی تغییر عکس فوری، برای خواص موجود در نوع پیچیده استفاده میکند، و از این جهت لازم است کهEF، برای نمونهی نوع پیچیده، پروکسی ایجاد نمیکند.شما هنوز هم، تشخیص خودکار تغییرات خواصی که مستقیما درون آن موجودیت(Entity) تعریف شده اند را دارید، ولی تغییرات رخ داده درون نوع پیچیده، فقط از طریق DetectChanges قابل تشخیص است.
چگونگی اطمینان از اینکه نمونههای جدید ، پروکسیها را دریافت خواهند کرد
EF به صورت خودکار برای نتایج حاصل از کوئری هایی که شما اجرا میکنید، پروکسیها را ایجاد میکند. با این حال اگر شما فقط از سازندهی کلاس POCO خود برای ایجاد نمونهی جدید استفاده کنید،دیگر پروکسیها ایجاد نخواهند شد.بدین منظور برای دریافت پروکسی ها، شما باید از متد DbSet.Create برای دریافت نمونههای جدید آن موجودیت استفاده کنید.
نکته: اگر شما، پروکسیهای ردیابی تغییر را برای موجودیتی از مدلتان فعال کرده باشید،هنوز هم میتوانید،نمونههای فاقد پروکسی آن موجودیت را ایجاد و بیافزایید.خوشبختانه EF با موجودیتهای پروکسی و غیر پروکسی در همان مجموعه(set) کار میکند.شما باید آگاه باشید که ردیابی خودکار تغییرات و یا اصلاح رابطه، برای نمونه هایی که پروکسی هایی ردیابی تغییر نیستند، قابل استفاده نیستند.داشتن مخلوطی از نمونههای پروکسی و غیر پروکسی در همان مجموعه، میتواند گیج کننده باشد.بنابر این عموما توصیه میشود که برای ایجاد نمونههای جدید از DbSet.Create استفاده کنید، تا همهی موجودیتهای موجود در مجموعه، پروکسیهای ردیابی تغییر باشند.
متد CreateNewProxies را به برنامهی خود اضافه کرده و آن را اجرا کنید.
private static void CreateNewProxies() { using (var context = new PhoneBookDbContext()) { var phoneNumber = new PhoneNumber { Number = "987" }; var davidPersonProxy = context.Persons.Create(); davidPersonProxy.FirstName = "david"; davidPersonProxy.PhoneNumbers.Add(phoneNumber); Console.WriteLine(phoneNumber.Person.FirstName); } }
خروجی مثال فوق david خواهد بود.همان طور که میبینید با استفاده از context.Persons.Create، نمونهی ساخته شده، دیگر شی POCO نیست، بلکه davidPersonProxy، از جنس پروکسی ردیابی تغییر است و تغییرات آن به طور خودکار ردیابی شده و رابطه آن نیز به صورت خودکار اصلاح میشود.در اینجا نیز با افزودن phoneNumber به شماره تلفنهای davidPersonProxy، به طور خودکار رابطهی بین phoneNumber و davidPersonPeroxy بر قرار شده است. همان طور که میدانید این عملیات بدون استفاده از پروکسیهای ردیابی تغییرات امکان پذیر نیست و موجب بروز خطا میشود.
ایجاد نمونههای پروکسی برای انواع مشتق شده
اورلود جنریک دیگری برای DbSet.Create وجود دارد که برای نمونه سازی کلاسهای مشتق شده در مجموعه ما استفاده میشود .برای مثال، فراخوانی Create بر روی مجموعهی Persons ،نمونه ای از کلاس Person را بر میگرداند.ولی ممکن است کلاس هایی در مجموعهی Persons وجود داشته باشند، که از آن مشتق شده باشند، مانند Student. برای دریافت نمونهی پروکسی Student، از اورلود جنریک Create استفاده میکنیم.
var newStudent = context.Persons.Create<Student>();
تا به این جای کار باید متوجه شده باشید که ردیابی تغییرات، فرآیندی ساده و بدیهی نیست و مقداری سربار در کار است. در بعضی از بخشهای برنامه تان، احتمالا دادهها را به صورت فقط خواندنی در اختیار کاربران قرار میدهید و چون اطلاعات هیچ وقت تغییر نمیکنند، شما میخواهید که سربار ناشی از ردیابی تغییرات را حذف کنید.
خوشبختانه EF شامل متد AsNoTracking است که میتوان از آن برای اجرای کوئریهای بدون ردیابی استفاده کرد.یک کوئری بدون ردیابی، یک کوئری ساده هست که نتایج آن توسط context برای تشخیص تغییرات ردیابی نخواهد شد.
متد PrintPersonsWithoutChangeTracking را به برنامه اضافه کنید و آن را اجرا کنید:
private static void PrintPersonsWithoutChangeTracking() { using (var context = new PhoneBookDbContext()) { var persons = context.Persons.AsNoTracking().ToList(); foreach (var person in persons) { Console.WriteLine(person.FirstName); } } }
نکته: واکشی دادهها بدون ردیابی تغییرات،معمولا وقتی باعث افزایش قابل توجه کارایی میشود که بخواهیم تعداد خیلی زیادی داده را به صورت فقط خواندنی نمایش دهیم. اگر برنامهی شما داده ای را تغییر میدهد و میخواهد آن را ذخیره کند، باید از AsNoTracking استفاده نکنید.
AsNoTracking یک متد الحاقی است، که در <IQueryable<T تعریف شده است، در نتیجه شما میتوانید از آن، در کوئریهای LINQ نیز استفاده کنید. شما میتوانید از AsNoTracking، در انتهای DbSet ،در خط from کوئری استفاده کنید.
var query = from p in context.Persons.AsNoTracking() where p.FirstName == "joe" select p;
شما همچنین از AsNoTracking میتوانید برای تبدیل یک کوئری LINQ موجود، به یک کوئری فاقد ردیابی استفاده کنید. این نکته را به یاد داشته باشید که فقط AsNoTracking بر روی کوئری، فرانخوانده شده است، بلکه متغیر query را با نتیجهی حاصل از فراخوانی AsNoTracking بازنویسی(override) کرده است و این، از این جهت لازم است که AsNoTracking ،تغییری در کوئری ای که بر روی آن فراخوانده شده نمیدهد، بلکه یک کوئری جدید بر میگرداند.
var query = from p in context.Persons where p.FirstName == "joe" select p; query = query.AsNoTracking();
منبع: ترجمه ای آزاد از کتاب Programming Entity Framework: DbContext
در Asp.net دو چرخهی حیات مهم
وجود دارند که اساس چارچوب MVC را تشکیل میدهند
:
- چرخهی حیات برنامه (Application)؛ از لحظهای که برنامه برای اولین بار اجرا میشود و تا لحظهی خاتمهی آن را شامل میشود.
- چرخهی حیات یک درخواست ( Request )؛ مسیری که یک درخواست طی میکند، اصطلاحا PipeLine نامیده میشود که همان چرخهی حیات یک درخواست نیز هست و از لحظهای که درخواست تحویل asp.net شده، تا زمانیکه درخواست ارسال میشود را شامل میشود.
تمرکز بنده بیشتر بر روی روند و مسیری است که یک درخواست طی میکند و قصد دارم با بهره گیری از کتاب Pro Asp.net Mvc 5 و دیگر منابع، چرخهی حیات درخواست را در برنامههای Mvc بررسی کرده و در مقالات آتی ماژولها و هندلرها را بررسی کنم.
در asp.net ، برنامه global فایلهای شامل دو فایل Global.asax , Global.asax.cs است.
فایل Global.asax که هیچ گاه نیاز به ویرایش آن نداریم محتویاتی مانند زیر دارد:
<%@ Application Codebehind="Global.asax.cs" Inherits="YourAppName.MvcApplication" Language="C#" %>
در این مقاله منظور از فایل global فایل Global.asax.cs است که مشتق شده از کلاس System.Web.HttpApplication است:
public class MvcApplication : System.Web.HttpApplication { protected void Application_Start() { ...// } }
Asp.net برای پاسخگویی به درخواستهای واصله، وهلههایی از کلاس MvcApplication را میسازد ولی این دو متد صرفا در نقاط شروع و پایان برنامه فراخوانی شده و عملا در وهلههای یاد شده صدا زده نخواهند شد و به جای آنها رویدادهایی را که در ذیل آنها را معرفی میکنیم، فراخوانی شده و چرخهی حیات درخواست را برای ما مشخص میسازند .
BeginRequest : به عنوان اولین رویداد، به محض وصول یک درخواست جدید رخ خواهد داد.AuthenticateRequest ,PostAuthenticateRequest : رویداد AuthenticateRequest برای شناسایی کاربر ارسال کننده درخواست، کاربرد دارد و پس از پردازش کلیهی توابع، رویداد PostAuthenticateRequest صدا زده میشود.
AuthorizeRequest :بههنگام صدور مجوزهای یک درخواست رخ میدهد و مشابه رویداد بالا پس از پردازش کلیهی توابع، رویداد PostAuthorizeRequest صدا زده خواهد شد.ResolveRequestCache : پس از صدور مجوزهای یک درخواست در رویداد authorization زمانیکه ماژولهای کش میخواهند اطلاعاتی را از کش سرور مطالبه کنند، رخ میدهد و به مانند دو رخداد قبلی، PostResolveRequestCache نیز پس از اتمام پردازش توابع رویداد رخ میدهد.
MapRequestHandler : زمانی که Asp.net میخواهد هندلری را برای پاسخگویی به درخواست واصله انتخاب کند رخ میدهد و PostMapRequestHandler نیز پس از این انتخاب، تریگر میشود.AcquireRequestState : جهت بدست آوردن دادههایی نظیر سشن و ... مرتبط با درخواست جاری کاربرد داشته و PostAcquireRequestState نیز پس از پردازش توابع رویداد رخ خواهد داد.
PreRequestHandlerExecute : بالافاصله قبل و همچنین بلافاصله بعد از این که یک هندلر بخواهد درخواستی را پردازش کند، رخ میدهد. PostRequestHandlerExecute نیز همانند دیگر رویدادهای گذشته، پس از اتمام پردازش توابع، این رویداد رخ خواهد داد.ReleaseRequestState : زمانی رخ میدهد که دادههای مرتبط با درخواست جاری، در ادامهی روند پردازش درخواست مورد نیاز نباشند و پس از پردازش توابع رویداد، PostReleaseRequestState رخ خواهد داد .
UpdateRequestCache : به جهت اینکه ماژولهای مسئول کش، توانایی به روز رسانی دادههای خود، برای پاسخگویی به درخواستهای بعدی را داشته باشند، این رویداد رخ میدهد.
LogRequest : قبل از انجام عملیات لاگین برای درخواست جاری رخ میدهد و پس از پردازش توابع رویداد نیز PostLogRequest تریگر میشود.EndRequest : پس از پایان کار پردازش درخواست جاری و مهیا شدن پاسخ مرتبط جهت ارسال به مرورگر تریگر خواهد شد.
PreSendRequestHeaders : قبل از ارسال HTTP headers به مرورگر این رویداد رخ خواهد داد.
PreSendRequestContent : بعد از ارسال شدن هدرها و قبل از ارسال محتوای صفحه به مرورگر رخ میدهد.Error : هر زمان و در هر مرحله از پردازش درخواست، چنانچه خطایی صورت پذیرد این رویداد رخ خواهد داد.
فریم ورک Asp.net جهت مدیریت بهتر یک درخواست، در تمام مسیر پردازش، رویدادهای بالا را مهیا کرده است. در ادامه نحوهی هندل کردن رویدادهای چرخهی حیات درخواست را در فایل global توضیح میدهم. هر چند که استفاده از این فایل بدین منظور، صرفا برای مدیریت مسائل ابتدایی مناسب بوده و در یک پروژهی بزرگ موجب به هم ریختگی فایل global با کدهای زیاد و خوانایی پایین بوده که قابلیت استفاده مجدد در دیگر پروژهها را نیز ندارد.
Asp.net این مشکل را با معرفی ماژولها که در مقالات آتی توضیح خواهم داد، مرتفع کرده است.
در فایل global هر گاه متدی را با پیشوند Application_ و نام یکی از رویدادهای بالا بنویسید Asp.net آن را به عنوان هندلری برای رویداد مذکور میشناسد. به عنوان مثال متدی با نام Application_BeginRequest متد رویداد BeginRequest میباشد.
ابتدا یک پروژهی MVC جدید را به نام SimpleApp ایجاد کرده و فایل global آن را مطابق ذیل تغییر میدهیم:
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; using System.Web.Routing; namespace SimpleApp { public class MvcApplication : System.Web.HttpApplication { protected void Application_Start() { AreaRegistration.RegisterAllAreas(); RouteConfig.RegisterRoutes(RouteTable.Routes); } protected void Application_BeginRequest() { RecordEvent("BeginRequest"); } protected void Application_AuthenticateRequest() { RecordEvent("AuthenticateRequest"); } protected void Application_PostAuthenticateRequest() { RecordEvent("PostAuthenticateRequest"); } private void RecordEvent(string name) { List<string> eventList = Application["events"] as List<string>; if (eventList == null) { Application["events"] = eventList = new List<string>(); } eventList.Add(name); } } }
در اینجا متدی به نام RecordEvent را در کدهای ذکر شده مشاهده میکنید که نام یک رویداد را دریافت و جهت در دسترس قرار دادن در کل برنامه به خاصیت Application از کلاس HttpApplication نسبت داده و متد مذکور را از سه متد دیگر فراخوانی کردهایم. این متدها در زمان رخ دادن رویدادهای BeginRequest, AuthenticateRequest, PostAuthenticateRequest صدا زده خواهند شد.
حال جهت نمایش اطلاعات رویداد نیاز است تغییراتی مشابه ذیل در کنترلر Home ایجاد نماییم.
using System.Web.Mvc; namespace SimpleApp.Controllers { public class HomeController : Controller { public ActionResult Index() { return View(HttpContext.Application["events"]); } } }
ویوی مرتبط با اکشن متد index را مطابق کدهای ذیل بازنویسی میکنیم:
@model List<string> @{ ViewBag.Title = "Events List"; } <h5>Events</h5> <table> @foreach (string eventName in Model) { <tr> <td>@eventName</td> </tr> } </table>
در این مقاله سعی کردیم ابتدا چرخهی حیات یک Request را فرا گرفته و سپس از طریق فایل global و توسط متدهایی با پیشوند Application_ +نام رویداد (اصطلاحا متدهای ویژه نامیده میشوند) چرخه حیات یک درخواست را مدیریت کنیم.
ایجاد لینک با یک تصویر بوسیله Html Helper
- امکان رمزنگاری خودکار فیلدهای مخفی وجود دارد.
- بررسیهای سمت سرور هم جای خودشان را دارند و لازم هستند.