On 16th November 2016, Nat Friedman and James Montemagno introduced Visual Studio for Mac, the newest member of the Visual Studio family at Connect(); 2016 event. I thought let's give it a try so I installed the same and went through the project templates available in it. This blog is kind of a getting started guide to install Visual Studio For Mac.
تزریق وابستگی (DI) در ASP.NET Core
I’ve been building some ASP.NET Core apps as of late and had to dig into how Dependency Injection works there. After talking with Julie Lerman a bit on Twitter about it, I realized that there might be some confusing things about how it works in ASP.NET Core, so I’m hoping I can add some clarity in this post.
I just finished teaching the first week of a 9-week, super-intensive, hands-on boot camp that focuses on TypeScript/ASP.NET Web API/AngularJS. I decided to write a series of blog posts that describes each week of the camp. Who makes it. The challenges the students encounter. And, the material that we cover
ایا معمار نرم افزار هستید؟
....
Becoming a software architect isn't something that simply happens overnight or with a promotion. It's a role , not a rank . It's an evolutionary process where you'll gradually gain the experience and confidence that you need to undertake the role.
<script> let src = 'https://svelte.dev/tutorial/image.gif'; let name = 'Rick Astley'; </script> <img src={src} alt="{name} dancing">
- نکته اول : اگر در تگ img مقدار alt را وارد نکنیم و یا alt در این تگ وجود نداشته باشد، یک هشدار توسط کامپایلر svelte برای ما با عنوان <img> element should have an alt attribute> ایجاد میشود. زمان ساخت یک برنامه بسیار مهم است تا قوانین نوشتن یک کد html خوب را رعایت کنیم تا برای تمامی کاربران احتمالی برنامه قابل استفاده باشد. در همین مثال با ایجاد یک هشدار Svelte تلاش میکند که ما را از اشتباه در نوشتن کد html مطلع سازد.
- نکته دوم : اگر نام یک آبجکت تعریف شده و یک attribute، برابر باشد میتوانیم از نسخه کوتاه شده یا Shorthand attributes در svelte استفاده کنیم. به طور مثال در مثال بالا میتوانیم از کد زیر در خط 6 استفاده کنیم.
<img {src} alt="{name} dancing">
<script> export let siteName = "dotnettips"; </script> <p>this is a nested component for third tutorial on {siteName}</p>
<script> import Nested from "./Nested.svelte"; export let name; </script> <h1>Hello {name}!</h1> <Nested siteName="dotnettips.info" />
Hello world! this is a nested component for third tutorial on dotnettips.info
در مثال بالا ما یک کامپوننت جدید را ایجاد کرده و از طریق دستور import به App.svelte اضافه کردیم. نکتهای که در اینجا وجود دارد، نحوه مقدار دهی props در کامپوننتها است. اگر به خط 9 دقت کنیم، کامپوننت ما از طریق تگ جدیدی با نام (Nested) به بدنه html برنامه اضافه شده است که یک attribute به نام siteName دارد. siteName متغیر export شده در کامپوننت Nested.svelte است که در کامپوننتها به این صورت مقدار دهی میشود. قبلا نحوه مقدار دهی این خصیصهها را در فایلهای جاوا اسکریپت مشاهده کرده بودیم. نکته دیگری که باید به آن دقت داشت این است که خصیصه siteName مقدار پیش فرض dotnettips را در Nested.svelte به خود اختصاص داده بود. به همین جهت اگر ما siteName را هنگام استفاده از کامپوننت مقدار دهی نکنیم، از مقدار پیش فرض خود استفاده خواهد کرد. ولی اینجا ما با مقدار دهی آن، siteName را به dotnettips.info تغییر دادهایم.
نکته مهم : دقت داشته باشید کامپوننتهای شما همیشه باید با حروف بزرگ شروع شوند؛ به طور مثال در صورت نوشتن <nested/> محتوای کامپوننت نمایش داده نخواهد شد. svelte، از طریق زیر نظر گرفتن حروف کوچک و بزرگ در ابتدای تگها، بین تگهای html و کامپوننتها تمایز قائل میشود.
Spread props :
تا اینجا به صورت خلاصه با props یا خصیصهها آشنا شدهاید و دیدیم که با export کردن یک متغیر در یک کامپوننت، میتوانیم آن را هنگام استفاده مقدار دهی نماییم. برای اینکه تمرینی هم باشد با توجه به مطالبی که تاکنون گفته شده، پروژهی جدیدی را ایجاد کنید و محتوای App.svelte را مانند کد زیر تغییر دهید.
<script> import Info from './Info.svelte'; const pkg = { name: 'svelte', version: 3, speed: 'blazing', website: 'https://svelte.dev' }; </script> <Info name={pkg.name} version={pkg.version} speed={pkg.speed} website={pkg.website}/>
همانطور که در خط دوم کد میبینید، کامپوننتی به نام Info.svelte به این بخش اضافه شدهاست. این کامپوننت را با محتوای زیر ایجاد نمایید:
<script> export let name; export let version; export let speed; export let website; </script> <p> The <code>{name}</code> package is {speed} fast. Download version {version} from <a href="https://www.npmjs.com/package/{name}">npm</a> and <a href={website}>learn more here</a> </p>
اگر برنامه را اجرا کنید یک چنین خروجی را مشاهده خواهید کرد:
The svelte package is blazing fast. Download version 3 from npm and learn more here
<Info {...pkg}/>
<script> let count = 0; function handleClick() { count += 1; } </script> <p>Count : {count}</p> <button on:click={handleClick}> Clicked {count} {count === 1 ? 'time' : 'times'} </button>
<script> let count = 0; let doubled = count * 2; function handleClick() { count += 1; } </script> <button on:click={handleClick}> Clicked {count} {count === 1 ? 'time' : 'times'} </button> <p>{count} doubled is {doubled}</p>
$: doubled = count * 2;
$: console.log(`the count is ${count}`);
$: { console.log(`the count is ${count}`); alert(`I SAID THE COUNT IS ${count}`); }
$: if (count >= 10) { alert(`count is dangerously high!`); count = 9; }
<script> let numbers = [1, 2, 3, 4]; function addNumber() { let newNumber = numbers.length + 1; numbers.push(newNumber); } $: sum = numbers.reduce((t, n) => t + n, 0); </script> <p>{numbers.join(' + ')} = {sum}</p> <button on:click={addNumber}>Add a number</button>
function addNumber() { let newNumber = numbers.length + 1; numbers.push(newNumber); numbers = numbers; }
function addNumber() { let newNumber = numbers.length + 1; numbers = [...numbers, newNumber]; }
مروری بر Two way bindings :
<script> let name = ""; function updateName(event) { name = event.target.value; } </script> <h4>My Name Is {name}</h4> <input value={name} on:input={updateName} />
<script> let name = ""; </script> <h4>My Name Is {name}</h4> <input bind:value={name} />
{#if condition} <!-- you html codes ... --> {/if}
<script> let user = { loggedIn: false }; function toggle() { user.loggedIn = !user.loggedIn; } </script> {#if user.loggedIn} <button on:click={toggle}> Log out </button> {/if} {#if !user.loggedIn} <button on:click={toggle}> Log in </button> {/if}
{#if condition} <!-- you html code when condition is true --> {:else} <!-- you html code when condition is false --> {/if}
{#if user.loggedIn} <button on:click={toggle}> Log out </button> {:else} <button on:click={toggle}> Log in </button> {/if}
{#if condition} <!-- you html code when condition is true --> {:else if condition2} <!-- you html code when condition2 is true --> {:else} <!-- you html code when condition and condition2 are false --> {/if}
{#each list as item} <!-- you html code per each item in list --> {/each}
<script> let cats = [ { id: 'J---aiyznGQ', name: 'Keyboard Cat' }, { id: 'z_AbfPXTKms', name: 'Maru' }, { id: 'OUtn3pvWmpg', name: 'Henri The Existential Cat' } ]; </script> <h1>The Famous Cats of YouTube</h1> <ul> {#each cats as cat} <li><a target="_blank" href="https://www.youtube.com/watch?v={cat.id}"> {cat.name} </a></li> {/each} </ul>
<ul> {#each cats as {id,name}} <li><a target="_blank" href="https://www.youtube.com/watch?v={id}"> {name} </a></li> {/each} </ul>
<ul> {#each cats as { id, name }, i} <li><a target="_blank" href="https://www.youtube.com/watch?v={id}"> {i + 1}: {name} </a></li> {/each} </ul>
نکته : در این بخش من سعی کردم تا حدودی به ترتیب بخش آموزشی خود وبسایت Svelte، موارد را بیان کنم؛ ولی با توجه به اینکه شاید دوستان ترجیح بدهند روش آموزشی خود آن وبسایت که امکان تغییر و نوشتن کد را هم محیا کرده است، امتحان کنند لینک آن را به اشتراک میگذارم.
معرفی برنامهی Subtitle Tools
http://salarblog.wordpress.com/2010/04/18/correct-persian-english-text-to-display-in-left-to-right-direction/#comment-484
دانلود برنامه:
http://www.4shared.com/file/ET-QeiW7/PersianLeftToRightCorrection.html
var list = new List<int> {1,1,2,2,3,3,3,4,4,4};
var hash = new HashSet<int>(); var duplicates = list.Where(i => !hash.Add(i));
var movies = new List<Movie> { new Movie("Titanic", 1998, 4.5f), new Movie("The Fifth Element", 1997, 4.6f), new Movie("Terminator 2", 1991, 4.7f), new Movie("Avatar", 2009, 5), new Movie("Platoon", 1986, 4), new Movie("My Neighbor Totoro", 1988, 5) }; var distinctRatings = movies.DistinctBy(movie => movie.Rating); // Output: // Titanic,The Fifth Element,Terminator 2,Avatar,Platoon
معرفی کتابخانهی Redux Thunk
thunk، تابعی است که خروجی تابعی دیگر است؛ مانند مثال زیر:
function definitelyNotAThunk() { return function aThunk() { console.log('Hello, I am a thunk.'); } }
برای مثال فرض کنید که نیاز است یک فراخوانی Ajax ای صورت گیرد و پس از پایان آن، جهت به روز رسانی state، یک شیء اکشن، به سمت reducer متناظری dispatch شود. مشکل اینجا است که نمیتوان به Redux، یک callback حاصل از دریافت نتیجهی عملیات Ajax ای و یا یک Promise را ارسال کرد. تمام اینها یک اثر جانبی یا side effect هستند که با توابع خالص Redux ای سازگاری ندارند. برای مدیریت یک چنین مواردی، یک میانافزار را به نام redux-thunk ایجاد کردهاند که اجازهی dispatch تابعی را میدهد (همان thunk در اینجا) که قرار است action اصلی را در زمانی دیگر dispatch کند. به این ترتیب Redux اطلاعاتی را در مورد یک عمل async نخواهد داشت؛ میانافزاری در این بین آنرا دریافت میکند و زمانیکه آنرا dispatch میکنیم، آنگاه اکشن متناظر با آن، به redux منتقل میشود. به این ترتیب امکان منتظر ماندن تا زمان رسیدن پاسخ از شبکه، میسر میشود.
فرض کنید یک action creator متداول به صورت زیر ایجاد شدهاست:
export const getAllItems = () => ({ type: UPDATE_ALL_ITEMS, items, });
برای پاسخ به این سؤال، اینبار action creator فوق را بر اساس الگوی redux-thunk به صورت زیر بازنویسی میکنیم:
export const getAllItems = () => { return dispatch => { Api.getAll().then(items => { dispatch({ type: UPDATE_ALL_ITEMS, items, }); }); }; };
برپایی پیشنیازها
در اینجا برای افزودن کامپوننتی که اطلاعات خودش را از یک API خارجی تامین میکند، از همان برنامهی به همراه کامپوننت شمارشگر که در قسمت قبل آنرا تکمیل کردیم، استفاده میکنیم. فقط در آن کتابخانههای Axios و همچنین redux thunk را نیز نصب میکنید. به همین جهت در ریشهی پروژهی React این قسمت، دستور زیر را در خط فرمان صادر کنید:
> npm install --save axios redux-thunk
پس از نصب پیشنیازها و راه اندازی برنامهی backend، در ابتدا فایل src\config.json را جهت درج مشخصات آدرس REST Api، ایجاد میکنیم:
{ "apiUrl": "https://localhost:5001/api" }
افزودن میانافزار redux-thunk به برنامه
فرض کنید در قسمتی از صفحه، در کامپوننتی مجزا، دکمهای وجود دارد و با کلیک بر روی آن، قرار است اطلاعاتی از سرور دریافت شده و در کامپوننت مجزای دیگری نمایش داده شود:
چون نیاز به عملیات async وجود دارد، باید از میانافزار مخصوص thunk برای انجام آن استفاده کرد. برای این منظور به فایل src\index.js مراجعه کرده و میانافزار thunk را توسط تابع applyMiddleware، به متد createStore، معرفی میکنیم:
import { applyMiddleware, compose, createStore } from "redux"; import thunk from "redux-thunk"; //... const store = createStore( reducer, compose( applyMiddleware(thunk), window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__() ) ); //...
دریافت اطلاعات از یک API خارجی به کمک redux-thunk
پس از آشنایی با روش کلی برقراری اتصالات سیستم react-redux در قسمت قبل، پیاده سازی دریافت اطلاعات را بر اساس همان الگو، پیاده سازی میکنیم:
1) ایجاد نام نوع اکشن متناظر با دکمهی افزودن مقدار
به فایل src\constants\ActionTypes.js، نوع جدید دریافت مطالب را اضافه میکنیم:
export const GetPostsSuccess = "GetPostsSuccess"; export const GetPostsStarted = "GetPostsStarted"; export const GetPostsFailure = "GetPostsFailure";
2) ایجاد متد Action Creator
در فایل src\actions\index.js، متد ایجاد کنندهی شیء اکشن ارسالی به reducer متناظری را تعریف میکنیم تا بتوان بر اساس نوع آن در reducer دریافت اطلاعات، منطق نهایی را پیاده سازی کرد:
import axios from "axios"; import { apiUrl } from "../config.json"; import * as types from "../constants/ActionTypes"; export const fetchPosts = () => { return (dispatch, getState) => { dispatch(getPostsStarted()); axios.get(apiUrl + "/posts").then(response => { dispatch(getPostsSuccess(response.data)).catch(err => { dispatch(getPostsFailure(err)); }); }); }; }; export const fetchPostsAsync = () => { return async (dispatch, getState) => { dispatch(getPostsStarted()); try { const { data } = await axios.get(apiUrl + "/posts"); console.log(data); dispatch(getPostsSuccess(data)); } catch (error) { dispatch(getPostsFailure(error)); } }; }; const getPostsSuccess = posts => ({ type: types.GetPostsSuccess, payload: { posts } }); const getPostsStarted = () => ({ type: types.GetPostsStarted }); const getPostsFailure = error => ({ type: types.GetPostsFailure, payload: { error } });
- تابع fetchPosts، از همان روش قدیمی callback، برای مدیریت اطلاعات دریافتی از سرور استفاده میکند. زمانیکه اطلاعاتی دریافت شد، آنرا با فراخوانی dispatch و با قالبی که تابع getPostsSuccess ارائه میدهد، به reducer متناظر، ارسال میکند.
- تابع fetchPostsAsync، نمونهی به همراه async/await کار با کتابخانهی axios است. هر دو روش callback و یا async/await در اینجا پشتیبانی میشوند.
به صورت پیشفرض action creators کتابخانهی redux از اعمال async پشتیبانی نمیکنند. برای رفع این مشکل پس از ثبت میانافزار thunk، اینبار متدهای action creator، بجای بازگشت یک شیء، یک تابع را بازگشت میدهند که این تابع درونی در زمانی دیگر توسط میانافزار thunk و پیش از رسیدن به reducer، فراخوانی خواهد شد. این تابع درونی، دو پارامتر dispatch و getState را دریافت میکند. هر دوی اینها نیز متد هستند. برای مثال اگر نیاز به دریافت وضعیت فعلی state در اینجا وجود داشت، میتوان متد ()getState رسیده را فراخوانی کرد و حاصل آنرا بررسی نمود. برای مثال شاید تصمیم گرفته شود که بر اساس وضعیت فعلی state، نیازی نیست تا اطلاعاتی از سرور دریافت شود و بهتر است همان اطلاعات کش شدهی موجود در state را بازگشت دهیم. البته در این مثال فقط از متد dispatch ارسالی، برای بازگشت نتیجهی نهایی به reducer متناظر، استفاده شدهاست.
- در نهایت آرایهی اشیاء مطلب دریافتی از سرور، به عنوان مقدار خاصیت posts شیء منتسب به خاصیت payload شیء ارسالی به reducer، در متد getPostsSuccess تعریف شدهاست. یعنی reducer متناظر، اطلاعات را از طریق خاصیت action.payload.posts شیء رسیده، دریافت میکند.
- همچنین دو اکشن شروع به دریافت اطلاعات (getPostsStarted) و بروز خطا (getPostsFailure) نیز در ابتدا و در قسمت catch عملیات async، به سمت reducer متناظر، dispatch خواهند شد.
3) ایجاد تابع reducer مخصوص دریافت اطلاعات از سرور
اکنون در فایل جدید src\reducers\posts.js، بر اساس نوع شیء رسیده و مقدار action.payload.posts آن، کار تامین آرایهی posts موجود در state انجام میشود:
import * as types from "../constants/ActionTypes"; const initialState = { loading: false, posts: [], error: null }; export default function postsReducer(state = initialState, action) { switch (action.type) { case types.GetPostsStarted: return { loading: true, posts: [], error: null }; case types.GetPostsSuccess: return { loading: false, posts: action.payload.posts, error: null }; case types.GetPostsFailure: return { loading: false, posts: [], error: action.payload.error }; default: return state; } }
- در حالت آغاز کار و یا GetPostsStarted، با تنظیم خاصیت loading به true، سبب نمایش یک div «لطفا منتظر بمانید» خواهیم شد.
- در حالت دریافت نهایی اطلاعات از سرور، خاصیت loading به false تنظیم میشود تا div «لطفا منتظر بمانید» را مخفی کند. همچنین آرایهی posts را نیز از payload رسیده استخراج کرده و به سمت کامپوننتها ارسال میکند.
- در حالت بروز خطا و یا GetPostsFailure، خاصیت error شیء action.payload استخراج شده و جهت نمایش div متناظری، بازگشت داده میشود.
پس از تعریف این reducer باید آنرا در فایل src\reducers\index.js به کمک combineReducers، با سایر reducerهای موجود، ترکیب و یکی کرد تا در نهایت این rootReducer در فایل index.js اصلی برنامه، جهت ایجاد store اصلی redux، مورد استفاده قرار گیرد:
import { combineReducers } from "redux"; import counterReducer from "./counter"; import postsReducer from "./posts"; const rootReducer = combineReducers({ counterReducer, postsReducer }); export default rootReducer;
تشکیل کامپوننتهای دکمهی دریافت اطلاعات و نمایش لیست مطالب
UI این قسمت از سه کامپوننت تشکیل شدهاست که کدهای کامل آنها را در ادامه مشاهده میکنید:
الف) کامپوننت src\components\FetchPosts.jsx
import React from "react"; const FetchPosts = ({ fetchPostsAsync }) => { return ( <section className="card mt-5"> <div className="card-header text-center"> <button className="btn btn-primary" onClick={fetchPostsAsync}> Fetch Posts </button> </div> </section> ); }; export default FetchPosts;
همانطور که مشاهده میکنید، این کامپوننت هیچ اطلاعاتی از وجود کامپوننت دومی که قرار است لیست مطالب را نمایش دهد، ندارد. کارش تنها dispatch یک اکشن است.
بنابراین این کامپوننت از طریق props فقط یک اشارهگر به متد رویدادگردانی را دریافت میکند و اطلاعات دیگری را نیاز ندارد.
ب) کامپوننت src\components\Posts.jsx
import React from "react"; import Post from "./Post"; const Posts = ({ posts, loading, error }) => { return ( <> <section className="card mt-5"> <div className="card-header"> <h2>Posts</h2> </div> <div className="card-body"> {loading ? ( <div className="alert alert-info">Loading ...</div> ) : ( <div className="list-group list-group-flush"> {posts.map(post => ( <Post key={post.id} post={post} /> ))} </div> )} {error && <div className="alert alert-warning">{error.message}</div>} </div> </section> </> ); }; export default Posts;
در این کامپوننت اگر loading رسیده به true تنظیم شده باشد، یک div با عبارت loading نمایش داده میشود. در غیراینصورت، لیست مطالب را درج میکند. همچنین اگر خطایی نیز رخ داده باشد، آنرا نیز درون یک div در صفحه نمایش میدهد.
ج) کامپوننت src\components\Post.jsx
import React from "react"; const Post = ({ post }) => { return ( <article className="list-group-item"> <header> <h2>{post.title}</h2> </header> <p>{post.body}</p> </article> ); }; export default Post;
اتصال کامپوننتهای FetchPosts و Posts به مخزن redux
مرحلهی آخر کار، تامین state کامپوننتهای FetchPosts و Posts از طریق props است. به همین جهت باید دو دربرگیرنده را برای این دو کامپوننت ایجاد کنیم.
الف) ایجاد دربرگیرندهی کامپوننت FetchPosts
برای این منظور فایل جدید src\containers\FetchPosts.js را با محتوای زیر ایجاد میکنیم:
import { connect } from "react-redux"; import { fetchPostsAsync } from "../actions"; import FetchPosts from "../components/FetchPosts"; const mapDispatchToProps = { fetchPostsAsync }; export default connect(null, mapDispatchToProps)(FetchPosts);
- چون اطلاعات state ای قرار نیست به این کامپوننت ارسال شود، تابع mapStateToProps را در اینجا مشاهده نمیکنید و با نال مقدار دهی شدهاست.
ب) ایجاد دربرگیرندهی کامپوننت Posts
برای این منظور فایل جدید src\containers\Posts.js را با محتوای زیر ایجاد میکنیم:
import { connect } from "react-redux"; import Posts from "../components/Posts"; const mapStateToProps = state => { console.log("PostsContainer->mapStateToProps", state); return { ...state.postsReducer }; }; export default connect(mapStateToProps)(Posts);
- کامپوننت Posts رویدادی را سبب نخواهد شد. به همین جهت تابع mapDispatchToProps را در اینجا تعریف و ذکر نکردهایم.
استفاده از کامپوننتهای دربرگیرنده جهت نمایش نهایی کامپوننتهای تحت کنترل Redux
اکنون به فایل src\App.js مراجعه کرده و دو تامین کنندهی فوق را درج میکنیم:
import "./App.css"; import React from "react"; import CounterContainer from "./containers/Counter"; import FetchPostsContainer from "./containers/FetchPosts"; import PostsContainer from "./containers/Posts"; function App() { const prop1 = 123; return ( <main className="container"> <div className="row"> <div className="col"> <CounterContainer prop1={prop1} /> </div> <div className="col"> <FetchPostsContainer /> </div> <div className="col"> <PostsContainer /> </div> </div> </main> ); } export default App;
یک نکته: برای مثال در انتهای کامپوننت FetchPosts، سطر export default FetchPosts را داریم. اگر این سطر را حذف کنیم و بجای آن export default connect فوق را قرار دهیم، دیگر نیازی نخواهد بود تا FetchPostsContainer را از دربرگیرندهها، import کرد و سپس بجای درج المان </FetchPosts> نوشت </FetchPostsContainer>. میتوان همانند قبل از همان نام متداول </FetchPosts> استفاده کرد و import انجام شده نیز همانند سابق از همان فایل ماژول کامپوننت صورت میگیرد. یعنی میتوان پوشهی containers را حذف کرد و کدهای آن را دقیقا ذیل کلاس کامپوننت درج نمود.
کدهای کامل این قسمت را میتوانید از اینجا دریافت کنید: state-management-redux-mobx-part04-backend.zip و state-management-redux-mobx-part04-frontend.zip