public class Order { public int Id { get; set; } public Customer Customer { get; set; } } public class Customer { public int Id { get; set; } } ...... modelBuilder.Entity<Order>().HasRequired(ff => ff.Customer);
CoffeeScript #5
Classes
کلاس نه تنها در جاوااسکریپت بلکه در سایر زبانها از جمله CoffeeScript نیز، بسیار مفید است.
در پشت صحنه، CoffeeScript برای ایجاد کلاس از prototype استفاده میکند. برای ساختن کلاس در CoffeeScript از کلمه کلیدی class باید استفاده کنید.
class Animal
var Animal; Animal = (function() { function Animal() {} return Animal; })();
animal = new Animal
class Animal constructor: (name) -> @name = name
var Animal; Animal = (function() { function Animal(name) { this.name = name; } return Animal; })();
class Animal constructor: (@name) ->
animal = new Animal "Cat" alert "Animal is a #{animal.name}"
Instance properties
اضافه کردن property به یک کلاس بسیار ساده و راحت است، syntax این کار دقیقا مانند اضافه کردن property به یک object است. فقط نکته ای که باید رعایت شود میزان تو رفتگی property نوشته شده است که به طور صحیح در داخل بدنه کلاس قرار بگیرد.class Animal price: 5 sell: (customer) -> animal = new Animal animal.sell(new Customer)
var Animal, animal; Animal = (function() { function Animal() {} Animal.prototype.price = 5; Animal.prototype.sell = function(customer) {}; return Animal; })(); animal = new Animal; animal.sell(new Customer);
Static properties
برای تعریف property به صورت static باید کلمه کلیدی this را به ابتدای آن اضافه کنید.class Animal this.find = (name) -> Animal.find("Dog")
class Animal @find: (name) -> Animal.find("Dog")
var Animal; Animal = (function() { function Animal() {} Animal.find = function(name) {}; return Animal; })(); Animal.find("Dog");
2- استفاده از {}less.
Dotless یک پیاده سازی از کتابخانه جاوا اسکریپتی LESS برای دات نت میباشد. پکیج نیوگت DotLess را نیز میتوانید از اینجا دریافت کنید. بعد از اضافه شدن فایلهای آن، یک ارجاع به dotless.core به پروژه تان اضافه خواهد شد. همچنین در فایل Web.Config در قسمت HttpHandler خط زیر اضافه خواهد شد:
<add type="dotless.Core.LessCssHttpHandler,dotless.Core" validate="false" path="*.LESS" verb="*" />
خط فوق یعنی به محض مواجه شدن با فایل LESS، پردازشگر فایلهای LESS وارد عمل میشود. همچنین خط زیر نیز جهت پیکربندی به قسمت configSections در فایل Web.Config اضافه میشود:
<section name="dotless" type="dotless.Core.configuration.DotlessConfigurationSectionHandler,dotless.Core" />
همچنین اگر مایل بودید میتوانید تنظیمات مربوط به فشرده سازی و caching را نیز فعال کنید:
<dotless minifyCss="false" cache="true" />
3- استفاده از افزونهی Web Essentials
Web Essentials برای کامپایل فایلهای LESS از کامپایلر node استفاده میکند. کار با این افزونه خیلی ساده است. کافی است پسوند فایل CSS موجود در پروژه تان را درون ویژوال استودیو، به less. تغییر دهید. با دوبار کلیک بر روی فایل، ویرایشگر فایلهای LESS برای شما نمایش داده میشود، همزمان نیز فایل یک فایل CSS و یک نسخه از فایل CSS را به صورت فشرده، برایتان تولید میکند. خب، هر بار که فایل LESS را تغییر دهید، Web Essentials به صورت خودکار فایلهای css. و min.css. را برایتان روز رسانی میکند.
خوب با کلیک بر روی فایل less، ویرایشگر فایلهای less نمایش داده میشود که با تغییر فایل css میتوانید پیش نمایش آنرا در سمت راست مشاهده کنید:
تعریف متغیر
با استفاده از syntax زیر میتوانید متغیرهای خود را تعریف کنید:
@variable-name: variableValue;
یکی از قابلیتهای جالب در حین مقداردهی متغیرها به خصوص زمانیکه مقدار یک کد رنگی باشد، نمایش کادر انتخاب رنگ است، این کادر بلافاصله بعد از نوشتن علامت # در ابتدای مقدار متغیر نمایش داده میشود:
به طور مثال با تعریف متغیر فوق هر جایی میتوانیم برای تعیین رنگ از آن استفاده کنیم:
@primary-color: #ff6a00; body { background-color: @primary-color; }
استفاده از توابع
LESS شامل تعداد زیادی توابع از پیش نوشته شده است که میتوانید به راحتی از آنها استفاده کنید، توابعی از جمله کار با رنگ ها، اعمال ریاضی و غیره. استفاده از آنها خیلی ساده است. به طور مثال در کد زیر از تابع percentage جهت تبدیل 0.5 به 50% استفاده کرده ایم:
.myClass { width: percentage(0.5); }
استخراج یک فایل
یکی دیگر از قابلیتهای Web Essentials استخراج(Extract) یک فایل میباشد به طور مثال فایل LESS شما شامل متغیرهای زیر است:
@primary-color: #7BA857; @primary-color-light: #B6DE8F; @primary-color-lighter: #D3EFC3; @primary-color-lightest: #EFFAE6; @secondary-color: #AE855C; @text-color-light: #666666; @text-color-dark: #0444;
به راحتی میتوانید تعاریف فوق را درون یک فایل LESS دیگر با نام colors.less قرار دهید:
تغییر تنظیمات پیش فرض Web Essentials
افزونه Web Essentials دارای یک قسمت جهت تغییر تنظیمات پیش فرض برای کار با LESS میباشد که با مراجعه به منوی Tools در ویژوال استودیو و سپس Options میتوانید آنها را تغییر دهید:
Auto-compile dependent files on save: توسط این گزینه میتوانیم تعیین کنیم که فایلهای که import کرده ایم تنها در صورتی که تغییر کرده و ذخیره شده باشند، در فایل CSS جاری کامپایل شوند.
Compile files on build: توسط این گزینه میتوانیم تعیین کنیم که فایلهای less در زمان Build پروژه کامپایل شوند.
Compile files on save: توسط این گزینه میتوانیم تعیین کنیم که فایلهای less در زمان ذخیره کردن پروژه کامپایل شوند.
Create source map files: اگر این گزینه True باشد فایل map. نیز تولید خواهد شد.
Custom output directory: اگر میخواهید خروجی در پوشهی موردنظر شما نمایش داده شود میتوانید آدرس آن را تعیین کنید.
Don't save raw compilation output: با فعال بودن این گزینه فایل CSS عادی ایجاد نخواهد شد.
Process source maps: توسط این گزینه میتوانید قابلیتهای ویرایشگر فایلهای source map را فعال یا غیرفعال کنید.
Strict Math: با فعال بودن این گزینه LESS تمام اعمال ریاضی درون فایل CSS را پردازش خواهد کرد.
Show preview pane: از این گزینه نیز جهت نمایش یا عدم نمایش preview window استفاده میشود.
تفاوت بین یک کلاس استاتیک، متدی استاتیک و یا متغیر عضو استاتیک چیست؟ چه زمانی باید از آنها استفاده کرد و لزوم بودن آنها چیست؟
برای پاسخ دادن به این سؤالات باید از نحوهی تقسیم بندی حافظه شروع کرد.
RAM برای هر نوع پروسهای که در آن بارگذاری میشود به سه قسمت تقسیم میگردد: Stack ، Heap و Static (استاتیک در دات نت در حقیقت قسمتی از Heap است که به آن High Frequency Heap نیز گفته میشود).
این قسمت استاتیک حافظه، محل نگهداری متدها و متغیرهای استاتیک است. آن متدها و یا متغیرهایی که نیاز به وهلهای از کلاس برای ایجاد ندارند، به صورت استاتیک ایجاد میگردند. در سی شارپ از واژه کلیدی static برای معرفی آنها کمک گرفته میشود. برای مثال:
class MyClass
{
public static int a;
public static void DoSomething();
}
MyClass.DoSomething(); // and not -> new MyClass().DoSomething();
به مثال زیر دقت نمائید:
class MyClass
{
// non-static instance member variable
private int a;
//static member variable
private static int b;
//static method
public static void DoSomething()
{
//this will result in compilation error as “a” has no memory
a = a + 1;
//this works fine since “b” is static
b = b + 1;
}
}
بر این اساس کامپایلر نیز از کامپایل شدن این کد جلوگیری کرده و خطای لازم را گوشزد خواهد کرد.
اکنون تعریف یک کلاس به صورت استاتیک چه اثری را خواهد داشت؟
با تعریف یک کلاس به صورت استاتیک مشخص خواهیم کرد که این کلاس تنها حاوی متدها و متغیرهای استاتیک میباشد. امکان ایجاد یک وهله از آنها وجود نداشته و نیازی نیز به این امر ندارند. این کلاسها امکان داشتن instance variables را نداشته و به صورت پیش فرض از نوع sealed به حساب خواهند آمد و امکان ارث بری از آنها نیز وجود ندارد. علت این امر هم این است که یک کلاس static هیچ نوع رفتاری را تعریف نمیکند.
پس با این تفاسیر چرا نیاز به یک کلاس static ممکن است وجود داشته باشد؟
همانطور که عنوان شد یک کلاس استاتیک هیچ نوع رفتاری را تعریف نمیکند بنابراین بهترین مکان است برای تعریف متدهای کمکی که به سایر اعضای کلاسهای ما وابستگی نداشته، عمومی بوده، مستقل و متکی به خود هستند. عموما متدهای کمکی در یک برنامه به صورت مکرر فراخوانی شده و نیاز است تا به سرعت در دسترس قرار داشته باشند و حداقل یک مرحله ایجاد وهله کلاس در اینجا برای راندمان بیشتر حذف گردد.
برای مثال متدی را در نظر بگیرید که بجز اعداد، سایر حروف یک رشته را حذف میکند. این متد عمومی است، وابستگی به سایر اعضای یک کلاس یا کلاسهای دیگر ندارد. بنابراین در گروه متدهای کمکی قرار میگیرد. اگر از افزونهی ReSharper استفاده نمائید، این نوع متدها را به صورت خودکار تشخیص داده و راهنمایی لازم را جهت تبدیل آنها به متدهای استاتیک ارائه خواهد داد.
با کلاسهای استاتیک نیز همانند سایر کلاسهای یک برنامه توسط JIT compiler رفتار میشود، اما با یک تفاوت. کلاسهای استاتیک فقط یکبار هنگام اولین دسترسی به آنها ساخته شده و در قسمت High Frequency Heap حافظه قرار میگیرند. این قسمت از حافظه تا پایان کار برنامه از دست garbage collector در امان است (بر خلاف garbage-collected heap یا object heap که جهت instance classes مورد استفاده قرار میگیرد)
نکته:
در برنامههای ASP.Net از بکارگیری متغیرهای عمومی استاتیک برحذر باشید (از static fields و نه static methods). این متغیرها بین تمامی کاربران همزمان یک برنامه به اشتراک گذاشته شده و همچنین باید مباحث قفلگذاری و امثال آنرا در محیطهای چند ریسمانی هنگام کار با آنها رعایت کرد (thread safe نیستند).
خیر. ممکنه برخی از این خطاها سلامت سیستم رو به خطر نندازه و اصلا ارتباطی هم به برنامه نداشته باشه . به طور مثال زمانی که کاربر یک صفحه ای و یا فایلی رو درخواست میده که وجود نداره.
توسط رویداد OnException میتونیم به خطاهای بوجود اومده در سطح یک Controller دسترسی داشته باشیم و اون رو مدیریت کنیم.
protected override void OnException(ExceptionContext filterContext) { // Bail if we can't do anything if (filterContext == null) return; // log var ex = filterContext.Exception ?? new Exception("No further information exists."); // save log ex.Message filterContext.ExceptionHandled = true; filterContext.Result = View("ViewName"); base.OnException(filterContext); }
ما تمام خطا هایی که در سطح یک کنترل اتفاق میافته رو با دوباره نویسی(override) متد OnException مدیریت میکنیم. اما اگه بخواهیم محدودهی این مدیریت رو بیشتر کنیم و کاری کنیم که روی تمام نرم افزار اعمال بشه باید چه کار کنیم؟
رویداد Application_Error در Global.asax محل مناسبی برای انجام این کار هست اما باید چند مسئله رو در نظر بگیریم:
- Server.Transfer : این متد یک فایل (میتونه یک صفحه باشه)رو به خروجی میفرسته بدون اینکه آدرس تغییر کنه(کاربر به صفحه دیگه ای منتقل بشه) نکته ای که وجود داره اینه که برای انتقال نیاز به وجود یک فایل فیزیکی بر روی سیستم داره تا اون فایل رو به خروجی منتقل کنه ، در پروژههای MVC فایل فیزیکی (به شکلی که در ASP.NET وجود داره) وجود نداره و بوسیله routeها ، controllerها و view ها یک صفحه(خروجی) تولید میشه بنابراین ما نمیتونیم در ASP.NET MVC از این متد استفاده کنیم و باید به دنبال راه حلی برای فرستادن پاسخ مناسب باشیم.
- Response.Redirect : این متد کاربر رو به یک صفحهی دیگه منتقل میکنه و StatusCode رو برابر با 301 مقدار دهی میکنه که معنای انتقال صفحه هست و برای مشخص کردن "خطای داخلی سرور" (Internal Server Error) با کد 500 و پیدا نشدن فایل با کد 404 مناسب نیست .این کد(StatusCode) برای موتورهای جستجو اهمیت زیادی داره لیست کامل این کدها و توضیحاتشون رو میتونید در این آدرس مطالعه کنید.
- Response.Clear : این متد باید فراخوانی بشه تا خروجی تولید شده تا این لحظه رو پاک کنیم.
- Server.ClearError : این متد باید فراخوانی بشه تا از ایجاد صفحه زرد رنگ خطا جلوگیری کنه.
- بدست آوردن آخرین خطای ایجاد شده.
- بدست آوردن کد خطای ایجاد شده.
- ثبت خطا برای بررسی (ممکن هست شما برخی خطاها مثل پیدا نشدن فایل رو ثبت نکنید).
- پاک کردن خروجی ایجاد شده تا این لحظه.
- پاک کردن خطای ایجاد شده در سرور.
- فرستادن یک خروجی مناسب بدون تغییر مسیر.
protected void Application_Error(object sender, EventArgs e) { var error = Server.GetLastError(); var code = (error is HttpException) ? (error as HttpException).GetHttpCode() : 500; if (code != 404) { // save log error.Message } Response.Clear(); Server.ClearError(); string path = Request.Path; Context.RewritePath(string.Format("~/Errors/Http{0}", code), false); IHttpHandler httpHandler = new MvcHttpHandler(); httpHandler.ProcessRequest(Context); Context.RewritePath(path, false); }
public class ErrorsController : Controller { [HttpGet] public ActionResult Http404() { Response.StatusCode = 404; return View(); } [HttpGet] public ActionResult Http500() { Response.StatusCode = 500; return View(); } }
public class Page { public int Id { get; set; } public string Path { get; set; } public string Content { get; set; } }
public class CustomVirtualPathProvider : VirtualPathProvider { public override bool FileExists(string virtualPath) { return base.FileExists(virtualPath) || FileExistsInDatabase(virtualPath); } public override VirtualFile GetFile(string virtualPath) { return base.FileExists(virtualPath) ? base.GetFile(virtualPath) : new CustomVirtualFile(virtualPath, GetFileFromDatabase(virtualPath)); } private bool FileExistsInDatabase(string virtualPath) { virtualPath = virtualPath.Replace("~", ""); return new DatabaseContext().Pages.Any(v => v.Path == virtualPath); } private byte[] GetFileFromDatabase(string virtualPath) { virtualPath = virtualPath.Replace("~", ""); return Encoding.UTF8.GetBytes(new DatabaseContext().Pages.First(v => v.Path == virtualPath).Content); } }
public class CustomVirtualFile : VirtualFile { private readonly byte[] _content; public CustomVirtualFile(string virtualPath, byte[] content) : base(virtualPath) { _content = content; } public override Stream Open() { return new MemoryStream(_content); } }
حال نوبت ثبت کلاس CustomVirtualPathProvider جهت استفادهی خودکار از آن میباشد. برای این کار در تابع Application_Start موجود در فایل Global.asax.cs دستور زیر را اضافه مینماییم:
protected void Application_Start() { HostingEnvironment.RegisterVirtualPathProvider(new CustomVirtualPathProvider()); //... }
import 'dart:html'; import 'dart:math' show Random; import 'dart:convert' show JSON;
class PirateName { ... PirateName.fromJSON(String jsonString) { Map storedName = JSON.decode(jsonString); _firstName = storedName['f']; _appellation = storedName['a']; } }
class PirateName { ... String get jsonString => JSON.encode({"f": _firstName, "a": _appellation}); }
final String TREASURE_KEY = 'pirateName'; void main() { ... }
void setBadgeName(PirateName newName) { if (newName == null) { return; } querySelector('#badgeName').text = newName.pirateName; window.localStorage[TREASURE_KEY] = newName.jsonString; }
void setBadgeName(PirateName newName) { ... } PirateName getBadgeNameFromStorage() { String storedName = window.localStorage[TREASURE_KEY]; if (storedName != null) { return new PirateName.fromJSON(storedName); } else { return null; } }
void main() { ... setBadgeName(getBadgeNameFromStorage()); }
{ "names": [ "Anne", "Bette", "Cate", "Dawn", "Elise", "Faye", "Ginger", "Harriot", "Izzy", "Jane", "Kaye", "Liz", "Maria", "Nell", "Olive", "Pat", "Queenie", "Rae", "Sal", "Tam", "Uma", "Violet", "Wilma", "Xana", "Yvonne", "Zelda", "Abe", "Billy", "Caleb", "Davie", "Eb", "Frank", "Gabe", "House", "Icarus", "Jack", "Kurt", "Larry", "Mike", "Nolan", "Oliver", "Pat", "Quib", "Roy", "Sal", "Tom", "Ube", "Val", "Walt", "Xavier", "Yvan", "Zeb"], "appellations": [ "Awesome", "Captain", "Even", "Fighter", "Great", "Hearty", "Jackal", "King", "Lord", "Mighty", "Noble", "Old", "Powerful", "Quick", "Red", "Stalwart", "Tank", "Ultimate", "Vicious", "Wily", "aXe", "Young", "Brave", "Eager", "Kind", "Sandy", "Xeric", "Yellow", "Zesty"]}
... <div> <input type="text" id="inputName" maxlength="15" disabled> </div> <div> <button id="generateButton" disabled>Aye! Gimme a name!</button> </div> ...
import 'dart:html'; import 'dart:math' show Random; import 'dart:convert' show JSON; import 'dart:async' show Future;
class PirateName { ... static List<String> names = []; static List<String> appellations = []; ... }
class PirateName { ... static Future readyThePirates() { var path = 'piratenames.json'; return HttpRequest.getString(path) .then(_parsePirateNamesFromJSON); } static _parsePirateNamesFromJSON(String jsonString) { Map pirateNames = JSON.decode(jsonString); names = pirateNames['names']; appellations = pirateNames['appellations']; } }
SpanElement badgeNameElement; void main() { ... }
void main() { InputElement inputField = querySelector('#inputName'); inputField.onInput.listen(updateBadge); genButton = querySelector('#generateButton'); genButton.onClick.listen(generateBadge); badgeNameElement = querySelector('#badgeName'); ... }
void main() { ... PirateName.readyThePirates() .then((_) { //on success inputField.disabled = false; //enable genButton.disabled = false; //enable setBadgeName(getBadgeNameFromStorage()); }) .catchError((arrr) { print('Error initializing pirate names: $arrr'); badgeNameElement.text = 'Arrr! No names.'; }); }
> npx create-react-app redux-template --template redux > cd redux-template > yarn start
import { configureStore } from '@reduxjs/toolkit'; import counterReducer from './features/counter/counterSlice'; export default configureStore({ reducer: { counter: counterReducer, }, });
Counter Counter.module.css counterSlice.js
import React, { useState } from 'react'; import { useSelector, useDispatch } from 'react-redux'; import { decrement, increment, incrementByAmount, selectCount, } from './counterSlice'; import styles from './Counter.module.css'; export function Counter() { const count = useSelector(selectCount); const dispatch = useDispatch(); const [incrementAmount, setIncrementAmount] = useState(2); return ( <div> <div className={styles.row}> <button className={styles.button} aria-label="Increment value" onClick={() => dispatch(increment())} > + </button> <span className={styles.value}>{count}</span> <button className={styles.button} aria-label="Decrement value" onClick={() => dispatch(decrement())} > - </button> </div> <div className={styles.row}> <input className={styles.textbox} value={incrementAmount} onChange={e => setIncrementAmount(e.target.value)} /> <button className={styles.button} onClick={() => dispatch( incrementByAmount({ amount: Number(incrementAmount) || 0 }) ) } > Add Amount </button> </div> </div> ); }
import { createSlice } from '@reduxjs/toolkit'; export const slice = createSlice({ name: 'counter', initialState: { value: 0, }, reducers: { increment: state => { // Redux Toolkit allows us to 'mutate' the state. It doesn't actually // mutate the state because it uses the immer library, which detects // changes to a "draft state" and produces a brand new immutable state // based off those changes state.value += 1; }, decrement: state => { state.value -= 1; }, incrementByAmount: (state, action) => { state.value += action.payload.amount; }, }, }); export const selectCount = state => state.counter.value; export const { increment, decrement, incrementByAmount } = slice.actions; export default slice.reducer;
- name: برای هر بخش از state، میتوانیم یک نام را تعیین کنیم و این همان عنوانی خواهد بود که میتوانید توسط Redux DevTools مشاهده کنید.
- initialValue: در اینجا میتوانیم مقادیر اولیهای را برای این بخش از state، تعیین کنیم که در مثال فوق، value به مقدار صفر تنظیم شدهاست.
- reducers: این قسمت محل تعریف actionهایی هستند که قرار است state را تغییر دهند. نکته جالب توجه این است که state در هر کدام از متدهای فوق، به ظاهر mutate شده است؛ اما همانطور که به صورت کامنت نیز نوشتهاست، در پشت صحنه از کتابخانهای با عنوان immer استفاده میکند که در عمل بجای تغییر state اصلی، یک کپی از state جدید را جایگزین state قبلی خواهد کرد.
return { ...state, models: state.models.map(c => c.model === action.payload.model ? { ...c, on: action.payload.toggle } : c ) };
state.models.forEach(item => { if (item.model === action.payload.model) { item.on = action.payload.toggle; } });