کتابخانه scrollgress
برای شروع ابتدا یک اینترفیس تعریف میکنیم:
public interface IFilesService { DirectoryInfo GetDirectoryInfo(string directoryPath); void DeleteFile(string fileName); void WritePersonInFile(string fileName,string name, string lastName, byte age); }
کلاس اصلی:
class FilesServices:IFilesService { public DirectoryInfo GetDirectoryInfo(string directoryPath) { return new DirectoryInfo(directoryPath); } public void DeleteFile(string fileName) { File.Delete(fileName); Console.WriteLine("the file has been deleted"); } public void WritePersonInFile(string fileName, string name, string lastName, byte age) { var text = $"my name is {name} {lastName} with {age} years old from dotnettips.info"; File.WriteAllText(fileName,text); } }
class FilesServicesProxy:IFilesService { private readonly IFilesService _filesService; public FilesServicesProxy() { _filesService=new FilesServices(); } public DirectoryInfo GetDirectoryInfo(string directoryPath) { var existing = Directory.Exists(directoryPath); if (!existing) Directory.CreateDirectory(directoryPath); return _filesService.GetDirectoryInfo(directoryPath); } public void DeleteFile(string fileName) { if(!File.Exists(fileName)) Console.WriteLine("Please enter a valid path"); else _filesService.DeleteFile(fileName); } public void WritePersonInFile(string fileName, string name, string lastName, byte age) { if (!Directory.Exists(fileName.Remove(fileName.LastIndexOf("\\")))) { Console.WriteLine("File Path is not valid"); return; } if (name.Trim().Length == 0) { Console.WriteLine("first name must enter"); return; } if (lastName.Trim().Length == 0) { Console.WriteLine("last name must enter"); return; } if (age<18) { Console.WriteLine("your age is illegal"); return; } if (name.Trim().Length < 3) { Console.WriteLine("first name must be more than 2 letters"); return; } if (lastName.Trim().Length <5) { Console.WriteLine("last name must be more than 4 letters"); return; } _filesService.WritePersonInFile(fileName,name,lastName,age); Console.WriteLine("the file has been written"); } }
در نهایت در بدنه اصلی با تست چندین حالت مختلف، همه متدها را داریم:
static void Main(string[] args) { IFilesService filesService=new FilesServicesProxy(); filesService.WritePersonInFile("c:\\myfakepath\\a.txt","ali","yeganeh",26); var directory = filesService.GetDirectoryInfo("d:\\myrightpath\\"); var fileName = Path.Combine(directory.FullName, "dotnettips.txt"); filesService.WritePersonInFile(fileName, "al", "yeganeh", 26); filesService.WritePersonInFile(fileName, "ali", "yeganeh", 12); filesService.WritePersonInFile(fileName, "ali", "yeganeh", 26); filesService.DeleteFile("c:\\myfakefile.txt"); filesService.DeleteFile(fileName); }
File Path is not valid first name must be more than 2 letters your age is illegal the file has been written Please enter a valid path the file has been deleted
خلاصه اخبار کنفرانس 21 ژانویه مایکروسافت در مورد ویندوز 10
var scaffolder = new MigrationScaffolder(configuration);
The Entity Framework provider type 'System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' for the 'System.Data.SqlClient' ADO.NET provider could not be loaded. Make sure the provider assembly is available to the running application. See http://go.microsoft.com/fwlink/?LinkId=260882 for more information.
ضمنا آیا اسمبلی مربوط به EF6 را نمیتوان بجز نیوگت از روش دیگری بدست آورد.؟
همچنین EF Power Tools مربوط به EF6 را از کجا میتوان تهیه کرد؟
بررسی زبان Go برای توسعه دهندگان #C
A Tour of Go (golang) for the C# Developer
Learning other programming languages enhances our work in our primary language. From the perspective of a C# developer, the Go language (golang) has many interesting ideas. Go is opinionated on some things (such as where curly braces go and what items are capitalized). Declaring an unused variable causes a compile failure; the use of "blank identifiers" (or "discards" in C#) are common. Concurrency is baked right in to the language through goroutines and channels. Programming by exception is discouraged; it's actually called a "panic" in Go. Instead, errors are treated as states to be handled like any other data state. We'll explore these features (and others) by building an application that uses concurrent operations to get data from a service. These ideas make us think about the way we program and how we can improve our day-to-day work (in C# or elsewhere).
0:00 Welcome to Go
2:40 Step 1: Basics
12:20 Step 2: Calling a web service
23:35 Step 3: Parsing JSON
36:26 Step 4: "for" loops
41:00 Step 5: Interfaces and methods
50:05 Step 6: Time and Args
55:10 Step 7: Concurrency
1:07:10 Step 8: Errors
1:14:40 Step 9: Concurrency and errors
1:24:35 Where to go next
برپایی پیشنیازها
در اینجا نیز از همان برنامهای که در قسمت 30، برای بررسی مثالهای React hooks ایجاد کردیم، استفاده خواهیم کرد. فقط در آن، کتابخانهی Axios را نیز نصب میکنید. به همین جهت در ریشهی پروژهی React این قسمت، دستور زیر را در خط فرمان صادر کنید:
> npm install --save axios
در ادامه میخواهیم در برنامهی React خود، لیست مطالب برنامهی backend را از سرور دریافت کرده و نمایش دهیم. همچنین یک search box را به همراه دکمههای search و clear نیز به آن اضافه کنیم.
دریافت اطلاعات اولیه از سرور، درون useEffect Hook
پس از نصب پیشنیازها و راه اندازی برنامهی backend، در ابتدا فایل src\config.json را جهت درج مشخصات آدرس REST Api آن، ایجاد میکنیم:
{ "apiUrl": "https://localhost:5001/api" }
import React from "react"; import {apiUrl} from "../../config.json"; export default function App() { return <></>; }
import App from "./components/part03/Search";
import axios from "axios"; import React, { useEffect, useState } from "react"; import { apiUrl } from "../../config.json"; export default function App() { useEffect(() => { axios .get(apiUrl + "/posts/search?query=") .then(response => console.log(response.data)); }); return <></>; }
اکنون میخواهیم این اطلاعات دریافتی را در برنامهی خود نیز نمایش دهیم. به همین جهت نیاز است تا response.data را درون state کامپوننت جاری قرار داده و در حین رندر کامپوننت، با تشکیل حلقهای بر روی آن، اطلاعات نهایی را نمایش دهیم. بنابراین نیاز به useState Hook خواهیم داشت که ابتدا آنرا import کرده و سپس آنرا تعریف و در قسمت then، فراخوانی میکنیم:
import axios from "axios"; import React, { useEffect, useState } from "react"; import { apiUrl } from "../../config.json"; export default function App() { const [results, setResults] = useState([]); useEffect(() => { axios.get(apiUrl + "/posts/search?query=").then(response => { console.log(response.data); setResults(response.data); }); });
همانطور که مشاهده میکنید، یک حلقهی بی پایان در اینجا رخ دادهاست! برای پایان آن، مجبور خواهیم شد ابتدا کنسول اجرایی برنامهی React را به صورت دستی خاتمه داده و سپس مرورگر را نیز refresh کنیم تا این حلقه، خاتمه پیدا کند.
علت این مشکل را در قسمت 30 بررسی کردیم؛ effect method تابع useEffect (همان متد در برگیرندهی قطعه کدهای axios.get در اینجا)، پس از هربار رندر کامپوننت، یکبار دیگر نیز اجرا میشود. یعنی این متد، هر دو حالت componentDidMount و componentDidUpdate کامپوننتهای کلاسی را با هم پوشش میدهد و چون در اینجا setState را با فراخوانی متد setResults داریم، یعنی درخواست رندر مجدد کامپوننت انجام شدهاست و پس از آن، مجددا effect method فراخوانی میشود و ... این حلقه هیچگاه خاتمه نخواهد یافت. به همین جهت مرورگر و برنامه، هر دو با هم هنگ میکنند!
در این برنامه فعلا میخواهیم که فقط در حالت componentDidMount، کار درخواست اطلاعات از backend صورت گیرد. به همین جهت پارامتر دوم متد useEffect را با یک آرایهی خالی مقدار دهی میکنیم:
useEffect(() => { // ... }, []);
//... export default function App() { // ... return ( <> <table className="table"> <thead> <tr> <th>Title</th> </tr> </thead> <tbody> {results.map(post => ( <tr key={post.id}> <td>{post.title}</td> </tr> ))} </tbody> </table> </> ); }
استفاده ازAsync/Await برای دریافت اطلاعات، درون یک useEffect Hook
اکنون میخواهیم درون effect method یک useEffect Hook، روش قدیمی استفادهی از callbackها و متد then را برای دریافت اطلاعات، با روش جدیدتر async/await که در قسمت 23 آنرا بیشتر بررسی کردیم، جایگزین کنیم.
useEffect(async () => { const { data } = await axios.get(apiUrl + "/posts/search?query="); console.log(data); setResults(data); }, []);
Warning: An effect function must not return anything besides a function, which is used for clean-up. It looks like you wrote useEffect(async () => ...) or returned a Promise.
برای رفع این مشکل، روش توصیه شده، ایجاد یک تابع مجزای async و سپس فراخوانی آن درون effect function است:
useEffect(() => { getResults(); }, []); const getResults = async () => { const { data } = await axios.get(apiUrl + "/posts/search?query="); console.log(data); setResults(data); };
پیاده سازی componentDidUpdate با یک useEffect Hook، جهت انجام جستجوهای پویا
تا اینجا با اضافه کردن پارامتر دومی به متد useEffect، رویداد componentDidUpdate آنرا از کار انداختیم، تا برنامه با هربار فراخوانی setState و اجرای مجدد effect function، در یک حلقهی بینهایت وارد نشود. اکنون این سؤال مطرح میشود که اگر یک textbox را برای جستجوی در عناوین نمایش داده شده، در بالای جدول آن قرار دهیم، نیاز است با هربار تغییر ورودی آن، کار فراخوانی مجدد effect function صورت گیرد، تا بتوان نتایج جدیدتری را از سرور دریافت و به کاربر نشان داد؛ این مشکل را چگونه باید حل کرد؟
برای دریافت عبارت وارد شدهی توسط کاربر و جستجو بر اساس آن، ابتدا متغیر state و متد تنظیم آنرا با استفاده از useState Hook و یک مقدار اولیهی دلخواه تنظیم میکنیم:
export default function App() { // ... const [query, setQuery] = useState("Title");
<input type="text" name="query" className="form-control my-3" placeholder="Search..." onChange={event => setQuery(event.target.value)} value={query} />
اکنون که متغیر query دارای مقدار شدهاست، میتوان از آن در متد axios.get، به نحو زیر و با ارسال یک کوئری استرینگ به سمت سرور، استفاده کرد:
const { data } = await axios.get( `${apiUrl}/posts/search?query=${encodeURIComponent(query)}` );
تا اینجا اگر برنامه را ذخیره کرده و اجرا کنید، با تایپ در textbox جستجو، تغییری در نتایج حاصل نمیشود؛ چون effect function تعریف شده که سبب اجرای مجدد axios.get میشود، طوری تنظیم شدهاست که فقط یکبار، آنهم پس از رندر اولیهی کامپوننت، اجرا شود. برای رفع این مشکل، با مقدار دهی آرایهای که به عنوان پارامتر دوم متد useEffect تعریف شده، میتوان اجرای مجدد effect function آنرا وابستهی به تغییرات متغیر query در state کامپوننت کرد:
useEffect(() => { getResults(); }, [query]);
دریافت اطلاعات جستجو، تنها با ارسال اطلاعات یک فرم به سمت سرور
تا اینجا کاربر با هر حرفی که درون textbox جستجو وارد میکند، یک کوئری، به سمت سرور ارسال خواهد شد. برای کاهش آن میتوان یک دکمهی جستجو را در کنار این textbox قرار داد تا تنها پس از کلیک بر روی آن، این جستجو صورت گیرد.
برای پیاده سازی این قابلیت، ابتدا وابستگی به query را از متد useEffect حذف میکنیم، تا دیگر با تغییر اطلاعات textbox، متد callback آن اجرا نشود (پارامتر دوم آنرا مجددا به یک آرایهی خالی تنظیم میکنیم). سپس یک دکمه را که از نوع button است و رویداد onClick آن به getResults اشاره میکند، در بالای جدول نتایج مطالب، قرار میدهیم:
<button className="btn btn-primary" type="button" onClick={getResults} > Search </button>
<form onSubmit={handleSearch}> <div className="input-group my-3"> <label htmlFor="query" className="form-control-label sr-only"></label> <input type="text" id="query" name="query" className="form-control" placeholder="Search ..." onChange={event => setQuery(event.target.value)} value={query} /> <div className="input-group-append"> <button className="btn btn-primary" type="submit"> Search </button> </div> </div> </form>
const handleSearch = event => { event.preventDefault(); getResults(); };
افزودن قابلیت پاک کردن textbox جستجو و معرفی useRef Hook
در ادامه میخواهیم یک دکمهی جدید را در کنار دکمهی Search، اضافه کنیم تا با کلیک کاربر بر روی آن، نه فقط محتوای وارد شدهی در textbox پاک شود، بلکه focus نیز به آن منتقل گردد. برای پاک کردن textbox، فقط کافی است متد setQuery را با یک رشتهی خالی ارسالی به آن فراخوانی کنیم. اما برای انتقال focus به textbox، نیاز به داشتن ارجاع مستقیمی به آن المان وجود دارد که با مفهوم آن در قسمت 18 آشنا شدیم: «برای دسترسی به یک المان DOM در React، باید یک reference را به آن نسبت داد. برای این منظور یک خاصیت جدید را در سطح کلاس کامپوننت ایجاد کرده و آنرا با React.RefObject مقدار دهی اولیه کرده و سپس ویژگی ref المان مدنظر را به این RefObject تنظیم میکنیم». برای انجام یک چنین کاری در اینجا، Hook ویژهای به نام useRef معرفی شدهاست. بنابراین برای پیاده سازی این نیازمندیها، ابتدا دکمهی Clear را در کنار دکمهی Search قرار میدهیم:
<button type="button" onClick={handleClearSearch} className="btn btn-info" > Clear </button>
import React, { useEffect, useRef, useState } from "react"; // ... export default function App() { // ... const searchInputRef = useRef(); const handleClearSearch = () => { setQuery(""); searchInputRef.current.focus(); };
البته این searchInputRef برای اینکه دقیقا به textbox تعریف شده اشاره کند، باید آنرا به ویژگی ref المان، انتساب داد:
<input type="text" id="query" name="query" className="form-control" placeholder="Search ..." onChange={event => setQuery(event.target.value)} value={query} ref={searchInputRef} />
نمایش «لطفا منتظر بمانید» در حین دریافت اطلاعات از سرور
البته در اینجا با هر بار کلیک بر روی دکمهی جستجو، نتیجهی نهایی به سرعت نمایش داده میشود؛ اما اگر سرعت اتصال کاربر کمتر باشد، با یک وقفه این امر رخ میدهد. به همین جهت بهتر است یک پیام «لطفا منتظر بمانید» را در این حین به او نمایش دهیم. به همین جهت در ابتدا state مرتبطی را به کامپوننت اضافه میکنیم:
const [loading, setLoading] = useState(false);
const getResults = async () => { setLoading(true); const { data } = await axios.get( `${apiUrl}/posts/search?query=${encodeURIComponent(query)}` ); console.log(data); setResults(data); setLoading(false); };
{loading ? ( <div className="alert alert-info">Loading results...</div> ) : ( <table className="table"> <thead> <tr> <th>Title</th> </tr> </thead> <tbody> {results.map(post => ( <tr key={post.id}> <td>{post.title}</td> </tr> ))} </tbody> </table> )}
برای آزمایش آن میتوان سرعت اتصال را در برگهی شبکهی ابزارهای توسعه دهندگان مرورگر، تغییر داد:
مدیریت خطاها در حین اعمال async
آخرین امکانی را که به این مطلب اضافه خواهیم کرد، مدیریت خطاهای اعمال async است که با try/catch صورت میگیرد:
// ... export default function App() { // ... const [error, setError] = useState(null); // ... const getResults = async () => { setLoading(true); try { const { data } = await axios.get( `${apiUrl}/posts/search?query=${encodeURIComponent(query)}` ); console.log(data); setResults(data); } catch (err) { setError(err); } setLoading(false); };
{error && <div className="alert alert-warning">{error.message}</div>}
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: sample-30-part-03-frontend.zip و sample-30-part-03-backend.zip
پروژه C# to JavaScript Compiler
Bootstrap 4.5.0 منتشر شد
- New interaction utilities. Quickly set
user-select
with the new utilities and Sass map. - New Reboot style for pointer cursors. We now include a
role="button"
selector in Reboot to setcursor: pointer
on non-<button>
element buttons. - Examples are now downloadable. We’ve added a script to zip up and offer all our Examples as their own download from the docs.
- Saved ~5% from the compressed minified JS builds.