Visual Studio 2022 Preview 1 was released recently. While normally I hold off on talking about preview releases and other items that are going to change rapidly like .NET 6, I think it is important to know what VS2022 is all about so you know what to expect in the coming months. So in this video, we are going to see VS2022 in action, we will see what the new features are, and we will talk about what this means for VS2019.
The previews of EF Core 5.0 require .NET Standard 2.1. This means:
- EF Core 5.0 runs on .NET Core 3.1; it does not require .NET 5.
- This may change in future previews depending on how the plan for .NET 5 evolves.
- EF Core 5.0 runs on other platforms that support .NET Standard 2.1.
- EF Core 5.0 will not run on .NET Standard 2.0 platforms, including .NET Framework.
Right click on the folder -> Properties -> Security tab -> Click at Edit button -> Enter `IIS AppPool\DefaultAppPool` user (IIS AppPool\<app_pool_name>) -> Click at Check names -> OK -> Then give it `read & execute` or other permissions.
برپایی پیشنیازها
در اینجا نیز از همان برنامهای که در قسمت 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
بررسی روش آپلود فایلها در ASP.NET Core
public async Task<IActionResult> FileUpload(IFormFile file) { if (file == null || file.Length == 0) { return BadRequest(); } using (var memoryStream = new MemoryStream()) { await file.CopyToAsync(memoryStream); var fileBytes = memoryStream.ToArray(); // ... save it } }
public async Task<IActionResult> FileUpload(IFormFile file) { if (file == null || file.Length == 0) { return BadRequest(); } using (var memoryStream = new MemoryStream()) { await file.CopyToAsync(memoryStream); using (var img = Image.FromStream(memoryStream)) { // TODO: ResizeImage(img, 100, 100); } } }
معرفی برنامه SQL Monitor
اگر از آخرین نگارش Ubuntu استفاده میکنید، با توجه به همکاری مایکروسافت و شرکت پشتیبان آن، نصب داتنت به سادگی اجرای دستور زیر است:
$ sudo apt update && sudo apt install -y dotnet-sdk-8.0 $ dotnet --version
و اگر میخواهید بدانید که چه نگارشی از داتنت، بههمراه مخازن استاندارد Ubuntu است، دستور زیر را صادر کنید:
$ apt search dotnet-sdk*
که یک نمونه خروجی آن به صورت زیر است:
$ apt search dotnet-sdk* Sorting... Done Full Text Search... Done dotnet-sdk-8.0/noble-updates,noble-security 8.0.107-0ubuntu1~24.04.1 amd64 .NET 8.0 Software Development Kit dotnet-sdk-8.0-source-built-artifacts/noble-updates,noble-security 8.0.107-0ubuntu1~24.04.1 amd64 Internal package for building the .NET 8.0 Software Development Kit dotnet-sdk-dbg-8.0/noble-updates,noble-security 8.0.107-0ubuntu1~24.04.1 amd64 .NET SDK debug symbols.
نصب اسکریپتی آخرین نگارش داتنت بر روی تمام توزیعهای لینوکسی
مایکروسافت، اسکریپتی را برای دریافت و نصب خودکار آخرین نگارش داتنت، تهیه کردهاست که کار با آن بسیار سادهاست و با تمام توزیعهای لینوکسی و نه فقط Ubuntu سازگار است.
پیش از هرکاری ابتدا مخزنهای بستهها و برنامههای مرتبط را یکبار بهروز کرده و سیستم را ریاستارت میکنیم:
sudo apt update -q && sudo apt upgrade -y && reboot
سپس دستور زیر را صادر میکنیم تا اسکریپت نصاب مخصوص داتنت خود مایکروسافت را دریافت کنیم :
wget https://dot.net/v1/dotnet-install.sh -O dotnet-install.sh chmod +x ./dotnet-install.sh
توسط این دو دستور، فایل dotnet-install.sh دریافت شده و همچنین دسترسی اجرایی بودن آن نیز تنظیم میشود.
پس از آن، برای نصب آخرین نگارش داتنت SDK موجود، تنها کافی است دستور زیر را صادر کنیم:
./dotnet-install.sh --version latest
و یا اگر فقط میخواهید runtime آنرا نصب کنید، پارامترهای نصب، به صورت زیر تغییر میکنند:
./dotnet-install.sh --version latest --runtime aspnetcore
همچنین اگر نگارشهای پایینتر مدنظر شما هستند، میتوانید کانال نصب را هم مشخص کنید:
./dotnet-install.sh --channel 7.0
که یک نمونه خروجی اجرای دستور dotnet-install.sh --version latest/. آن به صورت زیر است:
$ ./dotnet-install.sh --version latest dotnet-install: Attempting to download using aka.ms link https://dotnetcli.azureedge.net/dotnet/Sdk/8.0.303/dotnet-sdk-8.0.303-linux-x64.tar.gz dotnet-install: Remote file https://dotnetcli.azureedge.net/dotnet/Sdk/8.0.303/dotnet-sdk-8.0.303-linux-x64.tar.gz size is 223236164 bytes. dotnet-install: Extracting archive from https://dotnetcli.azureedge.net/dotnet/Sdk/8.0.303/dotnet-sdk-8.0.303-linux-x64.tar.gz dotnet-install: Downloaded file size is 223236164 bytes. dotnet-install: The remote and local file sizes are equal. dotnet-install: Installed version is 8.0.303 dotnet-install: Adding to current process PATH: `/home/vahid/.dotnet`. Note: This change will be visible only when sourcing script. dotnet-install: Note that the script does not resolve dependencies during installation. dotnet-install: To check the list of dependencies, go to https://learn.microsoft.com/dotnet/core/install, select your operating system and check the "Dependencies" section. dotnet-install: Installation finished successfully.
همانطور که مشاهده میکنید، داتنت 8.0.303، نصب شدهاست.
پس از پایان نصب، دو دستور زیر را هم باید اجرا کنید تا بتوان در خط فرمان به NET CLI. دسترسی یافت:
$ export DOTNET_ROOT=$HOME/.dotnet $ export PATH=$PATH:$DOTNET_ROOT:$DOTNET_ROOT/tools
پس از این تنظیمات، امکان اجرای دو دستور آزمایشی زیر میسر میشوند:
$ dotnet --version $ dotnet --list-sdks
نصب داتنت بر روی نگارشهای قدیمیتر Ubuntu
برای اینکه بتوان به روش متداولی (بدون دریافت اسکریپت نصاب فوق) به آخرین تغییرات انجام شده و آخرین بهروز رسانیها و همچنین تمام نگارشهای داتنت دسترسی داشت، میتوان از مخازن بستههای خود مایکروسافت استفاده کنیم که باید آنها را به سیستم عامل اضافه کرد. برای اینکار باید مراحل زیر طی شوند:
ابتدا تمام وابستگیهای احتمالی موجود را حذف میکنیم:
$ sudo apt remove 'dotnet*' 'aspnet*' 'netstandard*'
سپس فایل زیر را ایجاد کرده:
sudo nano touch /etc/apt/preferences
و آنرا با محتوای زیر تکمیل میکنیم؛ تا فقط از مخازن خود مایکروسافت استفاده شود و سایر مخازن مرتبط در این حالت، اولویت کمتری داشته باشند:
Package: dotnet* aspnet* netstandard* Pin: origin "archive.ubuntu.com" Pin-Priority: -10 Package: dotnet* aspnet* netstandard* Pin: origin "security.ubuntu.com" Pin-Priority: -10
پس از آن، دستورات زیر، کار افزودن مخازن بستههای مایکروسافت را انجام میدهند:
# Get OS version info which adds the $ID and $VERSION_ID variables source /etc/os-release # Download Microsoft signing key and repository wget https://packages.microsoft.com/config/$ID/$VERSION_ID/packages-microsoft-prod.deb -O packages-microsoft-prod.deb # Install Microsoft signing key and repository sudo dpkg -i packages-microsoft-prod.deb # Clean up rm packages-microsoft-prod.deb # Update packages sudo apt update sudo apt upgrade -y
بعد از این بهروز رسانیها، دستور متداول زیر، کار نصب آخرین نگارش NET SDK. را انجام میدهد:
sudo apt-get install -y dotnet-sdk-8.0
و همچنین هربار هم که سیستم را با دستورات sudo apt update -q && sudo apt upgrade -y به روز کنیم، در صورت وجود بهروز رسانی داتنتی جدیدی، آنرا به صورت خودکار دریافت و نصب میکند.
امکان استفاده از گروپپالیسیها و...
Ubuntu 21.04 desktops can now join an AD domain at installation for central configuration. In turn, AD administrators can now manage Ubuntu workstations, which simplifies compliance with company policies.
The new Ubuntu also adds the ability to configure system settings from an AD domain controller. Using Ubuntu's AD Group Policy Client or other Group Policy Clients, system administrators can specify security policies on all connected clients, such as password policies and user access control, and desktop environment settings, such as login screen, background, and favorite programs.