The Visual Studio team is excited about the Unity 2018.1 release: It’s the start of a new release cycle packed with great new features like the Scriptable Render Pipeline and the C# Job System. You can read the full blog post by Unity for all the details on what’s new in the 2018.1 release.
ایجاد اپلیکیشن آنگولار با typescript
انجام SEO بر روی برنامه های Angular
ترکیب کامپوننتها
در ادامه، همان برنامهی تا قسمت 5 را که کار نمایش یک counter را انجام میدهد، تکمیل میکنیم. در این برنامه اگر به فایل index.js دقت کنید، کار رندر تک کامپوننت Counter را انجام میدهیم:
ReactDOM.render(<Counter />, document.getElementById("root"));
برای نمایش این مفهوم، کامپوننت جدید src\components\counters.jsx را ایجاد میکنیم. قصد داریم در این کامپوننت، لیستی از کامپوننتهای Counter را رندر کنیم. سپس در index.js، بجای رندر کامپوننت Counter، کامپوننت جدید Counters را رندر میکنیم. به این ترتیب درخت کامپوننتهای برنامه، در سطح بالایی خودش از کامپوننت Counters شروع میشود و سپس فرزندان آنرا کامپوننتهای Counter تشکیل میدهند. به همین جهت فایل index.js را به صورت زیر ویرایش میکنیم تا به کامپوننت Counters اشاره کند:
import Counters from "./components/counters"; ReactDOM.render(<Counters />, document.getElementById("root"));
import React, { Component } from "react"; import Counter from "./counter"; class Counters extends Component { state = {}; render() { return ( <div> <Counter /> <Counter /> <Counter /> <Counter /> </div> ); } } export default Counters;
در مرحلهی بعد، بجای رندر و درج دستی این کامپوننتها، آرایهای از اشیاء counter را ایجاد کرده و سپس آنها را توسط متد Array.map رندر میکنیم:
import React, { Component } from "react"; import Counter from "./counter"; class Counters extends Component { state = { counters: [ { id: 1, value: 0 }, { id: 2, value: 0 }, { id: 3, value: 0 }, { id: 4, value: 0 } ] }; render() { return ( <div> {this.state.counters.map(counter => ( <Counter key={counter.id} /> ))} </div> ); } } export default Counters;
ارسال دادهها به کامپوننتها
مشکل! مقدار value هر شیء شمارشگر تعریف شده، به کامپوننتهای مرتبط رندر شده اعمال نشدهاست. برای مثال اگر value اولین شیء را به 4 تغییر دهیم، هنوز هم این کامپوننت با همان مقدار صفر شروع به کار میکند. برای رفع این مشکل، به همان روشی که ویژگی key کامپوننت Counter را مقدار دهی کردیم، میتوان ویژگیهای سفارشی دیگری را تعریف و مقدار دهی کرد:
render() { return ( <div> {this.state.counters.map(counter => ( <Counter key={counter.id} value={counter.value} selected={true} /> ))} </div> );
class Counter extends Component { state = { count: 0 }; render() { console.log("props", this.props); //...
خاصیت this.props، یک شیء سادهی جاوا اسکریپتی است و شامل تمام ویژگیهایی میباشد که ما در کامپوننت Counters برای هر کدام از کامپوننتهای Counter رندر شدهی توسط آن، تعریف کردیم. برای نمونه دو ویژگی جدید value و selected را که به تعاریف المانهای Counter در کامپوننت Counters اضافه کردیم، در اینجا به همراه مقادیر منتسب به آنها، قابل مشاهده هستند. البته در این خروجی، key را ملاحظه نمیکنید؛ چون هدف اصلی آن، معرفی یکتای المانها در DOM مجازی React است.
بنابراین اکنون میتوان به value تنظیم شدهی در کامپوننت Counters به صورت this.props.value در کامپوننت Counter دسترسی یافت و سپس از آن جهت مقدار دهی اولیهی counter استفاده کرد.
class Counter extends Component { state = { count: this.props.value };
یک نکته: در اینجا selected={true} را داریم. اگر مقدار آنرا حذف کنیم، یعنی selected تنها درج شود، مقدار آن، همان true دریافت خواهد شد.
تعریف فرزند برای المانهای کامپوننتها
ویژگیهای اضافه شدهی به تعاریف المانهای کامپوننتها، توسط خاصیت this.props، به هر کدام از آن کامپوننتها منتقل میشوند. این خاصیت props، یک خاصیت ویژه را به نام children، نیز دارا است و از آن برای دسترسی به المانهای تعریف شدهی بین تگهای یک المان اصلی استفاده میشود:
render() { return ( <div> {this.state.counters.map(counter => ( <Counter key={counter.id} value={counter.value} selected={true}> <h4>Counter #{counter.id}</h4> </Counter> ))} </div> ); }
یک نمونه مثال واقعی این قابلیت، امکان تعریف محتوای دیالوگ باکسها، توسط استفاده کنندهی از آن است.
روش دیباگ برنامههای React
افزونهی مفید React developer tools را میتوانید برای مرورگرهای کروم و فایرفاکس، دریافت و نصب کنید. برای نمونه پس از نصب آن در مرورگر کروم، یک برگهی جدید به لیست برگههای کنسول توسعه دهندگان آن اضافه میشود:
همانطور که مشاهده کنید، درخت کامپوننتهای برنامه را در برگهی جدید Components، میتوان مشاهده کرد. در اینجا با انتخاب هر کدام از فرزندان این درخت، مشخصات آن نیز مانند props و state، در کنار صفحه ظاهر میشوند. همچنین در بالای همین قسمت، 4 آیکن مشاهدهی سورس، مشاهدهی DOM و یا لاگ کردن جزئیات شیء کامپوننت انتخابی در کنسول هم درج شدهاند:
که برای نمونه چنین خروجی را لاگ میکند:
بررسی تفاوتهای خواص props و state
در کامپوننت Counter، از props برای مقدار دهی اولیهی state استفاده میکنیم:
class Counter extends Component { state = { count: this.props.value };
- props حاوی اطلاعاتی است که به یک کامپوننت ارسال میکنیم؛ اما state حاوی اطلاعاتی است که مختص به آن کامپوننت بوده و private است. یعنی سایر کامپوننتها نمیتوانند به state کامپوننت دیگری دسترسی پیدا کنند. برای مثال در کامپوننت Counters، تمام attributes سفارشی تنظیم شدهی بر روی تعاریف المانهای کامپوننت Counter، جزئی از اطلاعات props خواهند بود. در اینجا نمیتوان به state کامپوننت مدنظری دسترسی یافت و آنرا مقدار دهی کرد. به همین ترتیب state کامپوننت Counters نیز در سایر کامپوننتها قابل دسترسی نیست.
- همچنین باید درنظر داشت که props، در مقایسه با state، فقط خواندنی است. به عبارتی مقدار ورودی به یک کامپوننت را داخل آن کامپوننت نمیتوان تغییر داد. برای مثال سعی کنید در داخل متد رویدادگردان کلیک موجود در کامپوننت Counter، مقدار this.props.value را به صفر تنظیم کنید. در این حالت با کلیک بر روی دکمهی Increment، بلافاصله خطای readonly بودن خواص شیء منتسب به props را دریافت میکنیم. در اینجا اگر نیاز است این مقدار را داخل کامپوننت تغییر دهیم، باید ابتدا این مقدار را دریافت کرده و سپس آنرا داخل state قرار دهیم. پس از آن امکان ویرایش اطلاعات منتسب به state، داخل یک کامپوننت وجود خواهد داشت.
صدور و مدیریت رخدادها
در ادامه میخواهیم در کنار هر دکمهی Increment کامپوننت شمارشگر، یک دکمهی Delete هم قرار دهیم:
مشکل! اگر کد مدیریتی handleDelete را در کامپوننت Counter قرار دهیم، چگونه باید به لیست آرایهی اشیاء counters والد آن، یعنی کامپوننت Counters که سبب رندر شدن کامپوننتهای شمارشگر شده (state = { counters: [ ] })، دسترسی یافت و شیءای را از آن حذف کرد؟ در React، کامپوننتی که state ای را تعریف میکند، باید کامپوننتی باشد که قرار است آنرا تغییر دهد و اطلاعات state هر کامپوننت، صرفا متعلق به آن کامپوننت بوده و جزو اطلاعات خصوصی آن است. بنابراین مدیریت حذف و یا افزودن کامپوننتها در لیست نمایش داده شده، باید جزو وظایف کامپوننت Counters باشد و نه Counter.
برای حل این مشکل، کامپوننت Counter تعریف شده (کامپوننت فرزند) باید سبب بروز رخداد onDelete شود تا کامپوننت Counters (کامپوننت والد)، آنرا توسط متد handleDelete مدیریت کند. بنابراین ابتدا به کامپوننت Counters (کامپوننت والد) مراجعه کرده و متد رویدادگردان handleDelete را به آن اضافه میکنیم:
handleDelete = () => { console.log("handleDelete called."); };
<Counter key={counter.id} value={counter.value} selected={true} onDelete={this.handleDelete} />
<button onClick={this.props.onDelete} className="btn btn-danger btn-sm m-2" > Delete </button>
اکنون اگر برنامه را ذخیره کرده و پس از بارگذاری مجدد برنامه در مرورگر بر روی دکمهی Delete کلیک کنیم، پیام «handleDelete called» در کنسول توسعه دهندگان مرورگر لاگ میشود. به این ترتیب کامپوننت فرزند سبب بروز رخدادی شده و والد آن، این رخداد را مدیریت میکند.
به روز رسانی state
تا اینجا دکمهی Delete فرزند، به متد handleDelete والد متصل شدهاست. مرحلهی بعد، پیاده سازی واقعی حذف یک المان از DOM مجازی و به روز رسانی state است. برای اینکار ابتدا به رخدادگردان onClick، در کامپوننت شمارشگر، مراجعه کرده و id دریافتی را به سمت والد ارسال میکنیم:
onClick={() => this.props.onDelete(this.props.id)}
<Counter key={counter.id} value={counter.value} selected={true} onDelete={this.handleDelete} id={counter.id} />
handleDelete = counterId => { console.log("handleDelete called.", counterId); const counters = this.state.counters.filter( counter => counter.id !== counterId ); this.setState({ counters }); // = this.setState({ counters: counters }); };
البته پیاده سازی ما تا به اینجا بدون مشکل کار میکند، اما به ازای هر خاصیت counter، یک ویژگی جدید را به تعریف المان مرتبط اضافه کردهایم که در طول زمان بیش از اندازه طولانی خواهد شد. برای رفع این مشکل، خود شیء counter را به صورت یک ویژگی جدید به کامپوننت مرتبط با آن ارسال میکنیم. به این ترتیب اگر در آینده خاصیتی را به این شیء اضافه کردیم، دیگر نیازی نیست تا آنرا به صورت دستی و مجزا تعریف کنیم. به همین جهت ابتدا تعریف المان Counter را به صورت زیر خلاصه میکنیم که در آن ویژگی جدید counter، حاوی کل شیء counter است:
<Counter key={counter.id} counter={counter} onDelete={this.handleDelete} />
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: sample-07.zip
The team said that “most of their effort to improve the .NET Core Docker experience in the last year has been focused on .NET Core 3.0.” “This is the first release in which we’ve made substantive runtime changes to make CoreCLR much more efficient, honor Docker resource limits better by default, and offer more configuration for you to tweak”.
پیشنیاز: ایجاد یک برنامهی خالی React و ASP.NET Core
یک پوشهی خالی را ایجاد کرده و در آن دستور dotnet new react را صادر کنید، تا قالب خاص پروژههای React یکی سازی شدهی با پروژههای ASP.NET Core، یک پروژهی جدید را ایجاد کند.
همانطور که در تصویر فوق نیز مشاهده میکنید، این پروژه از دو برنامه تشکیل شدهاست:
الف) برنامهی SPA که در پوشهی ClientApp قرار گرفتهاست و شامل کدهای کامل یک برنامهی React است.
ب) برنامهی سمت سرور ASP.NET Core که یک برنامهی متداول وب، به همراه فایل Startup.cs و سایر فایلهای مورد نیاز آن است.
در ادامه نکات ویژهی ساختار این پروژه را بررسی خواهیم کرد.
تجربهی توسعهی برنامهها توسط این قالب ویژه
اکنون اگر این پروژهی وب را برای مثال با فشردن دکمهی F5 و یا اجرای دستور dotnet run، اجرا کنیم، چه اتفاقی رخ میدهد؟
- به صورت خلاصه برنامهی ASP.NET Core شروع به کار کرده و سبب ارائه همزمان برنامهی SPA نیز خواهد شد.
- پورتی که برنامهی وب بر روی آن قرار دارد، با پورتی که برنامهی React بر روی روی آن ارائه میشود، یکی است. یعنی نیازی به تنظیمات CORS را ندارد.
- در این حالت اگر در برنامهی React تغییری را ایجاد کنیم (در هر قسمتی از آن)، hot reloading آن هنوز هم برقرار است و سبب بارگذاری مجدد برنامهی SPA در مرورگر خواهد شد و برای اینکار نیازی به توقف و راه اندازی مجدد برنامهی ASP.NET Core نیست.
اما این تجربهی روان کاربری و توسعه، چگونه حاصل شدهاست؟
بررسی ساختار فایل Startup.cs یک پروژهی مبتنی بر dotnet new react
برای درک نحوهی عملکرد این قالب ویژه، نیاز است از فایل Startup.cs آن شروع کرد.
// ... using Microsoft.AspNetCore.SpaServices.ReactDevelopmentServer; namespace dotnet_template_sample { public class Startup { // ... public void ConfigureServices(IServiceCollection services) { services.AddControllersWithViews(); // In production, the React files will be served from this directory services.AddSpaStaticFiles(configuration => { configuration.RootPath = "ClientApp/build"; }); }
<ItemGroup> <PackageReference Include="Microsoft.AspNetCore.SpaServices.Extensions" Version="3.1.2" /> </ItemGroup>
در متد ConfigureServices، ثبت سرویسهای مرتبط با فایلهای استاتیک پروژهی SPA، توسط متد AddSpaStaticFiles صورت گرفتهاست. در اینجا RootPath آن، به پوشهی ClientApp/build اشاره میکند. البته این پوشه هنوز در این ساختار، قابل مشاهده نیست؛ اما زمانیکه پروژهی ASP.NET Core را برای ارائهی نهایی، publish کردیم، به صورت خودکار ایجاد شده و حاوی فایلهای قابل ارائهی برنامهی React نیز خواهد بود.
قسمت مهم دیگر کلاس آغازین برنامه، متد Configure آن است:
// ... using Microsoft.AspNetCore.SpaServices.ReactDevelopmentServer; namespace dotnet_template_sample { public class Startup { // ... public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { // ... app.UseStaticFiles(); app.UseSpaStaticFiles(); app.UseRouting(); app.UseEndpoints(endpoints => { endpoints.MapControllerRoute( name: "default", pattern: "{controller}/{action=Index}/{id?}"); }); app.UseSpa(spa => { spa.Options.SourcePath = "ClientApp"; if (env.IsDevelopment()) { spa.UseReactDevelopmentServer(npmScript: "start"); } }); } } }
- متد UseSpaStaticFiles، سبب ثبت میانافزاری میشود که امکان دسترسی به فایلهای استاتیک پوشهی ClientApp حاوی برنامهی React را میسر میکند؛ مسیر این پوشه را در متد ConfigureServices تنظیم کردیم.
- متد UseSpa، سبب ثبت میانافزاری میشود که دو کار مهم را انجام میدهد:
1- کار اصلی آن، ثبت مسیریابی معروف catch all است تا مسیریابیهایی را که توسط کنترلرهای برنامهی ASP.NET Core مدیریت نمیشوند، به سمت برنامهی React هدایت کند. برای مثال مسیر https://localhost:5001/api/users به یک کنترلر API برنامهی سمت سرور ختم میشود، اما سایر مسیرها مانند https://localhost:5001/login قرار است صفحهی login برنامهی سمت کلاینت SPA را نمایش دهند و متناظر با اکشن متد خاصی در کنترلرهای برنامهی وب ما نیستند. در این حالت، کار این مسیریابی catch all، نمایش صفحهی پیشفرض برنامهی SPA است.
2- بررسی میکند که آیا شرایط IsDevelopment برقرار است؟ آیا در حال توسعهی برنامه هستیم؟ اگر بله، میانافزار دیگری را به نام UseReactDevelopmentServer، اجرا و ثبت میکند.
برای درک عملکرد میانافزار ReactDevelopmentServer نیاز است به سورس آن مراجعه کرد. این میانافزار بر اساس پارامتر start ای که دریافت میکند، سبب اجرای npm run start خواهد شد. به این ترتیب دیگر نیازی به اجرای جداگانهی این دستور نخواهد بود و همچنین این اجرا، به همراه تنظیمات proxy مخصوصی نیز هست تا پورت اجرایی برنامهی React و برنامهی ASP.NET Core یکی شده و دیگر نیازی به تنظیمات CORS مخصوص برنامههای React نباشد. بنابراین hot reloading ای که از آن صحبت شد، توسط ASP.NET Core مدیریت نمیشود. در پشت صحنه همان npm run start اصلی برنامههای React، در حال اجرای وب سرور آزمایشی React است که از hot reloading پشتیبانی میکند.
یک مشکل: با این تنظیم، هربار که برنامهی ASP.NET Core اجرا میشود (به علت تغییرات در کدها و فایلهای پروژه)، سبب اجرای مجدد و پشت صحنهی react development server نیز خواهد شد که ... آغاز برنامه را در حالت توسعه، کند میکند. برای رفع این مشکل میتوان این وب سرور توسعهی برنامههای React را به صورت جداگانهای اجرا کرد و فقط تنظیمات پروکسی آنرا در اینجا ذکر نمود:
// replace spa.UseReactDevelopmentServer(npmScript: "start"); // with spa.UseProxyToSpaDevelopmentServer("http://localhost:3000");
تغییرات ویژهی فایل csproj برنامه
اگر به فایل csproj برنامه دقت کنیم، دو تغییر جدید نیز در آن قابل مشاهده هستند:
الف) نصب خودکار وابستگیهای برنامهی client
<Target Name="DebugEnsureNodeEnv" BeforeTargets="Build" Condition=" '$(Configuration)' == 'Debug' And !Exists('$(SpaRoot)node_modules') "> <!-- Ensure Node.js is installed --> <Exec Command="node --version" ContinueOnError="true"> <Output TaskParameter="ExitCode" PropertyName="ErrorCode" /> </Exec> <Error Condition="'$(ErrorCode)' != '0'" Text="Node.js is required to build and run this project. To continue, please install Node.js from https://nodejs.org/, and then restart your command prompt or IDE." /> <Message Importance="high" Text="Restoring dependencies using 'npm'. This may take several minutes..." /> <Exec WorkingDirectory="$(SpaRoot)" Command="npm install" /> </Target>
ب) یکی کردن تجربهی publish برنامهی ASP.NET Core با publish پروژههای React
<Target Name="PublishRunWebpack" AfterTargets="ComputeFilesToPublish"> <!-- As part of publishing, ensure the JS resources are freshly built in production mode --> <Exec WorkingDirectory="$(SpaRoot)" Command="npm install" /> <Exec WorkingDirectory="$(SpaRoot)" Command="npm run build" /> <!-- Include the newly-built files in the publish output --> <ItemGroup> <DistFiles Include="$(SpaRoot)build\**" /> <ResolvedFileToPublish Include="@(DistFiles->'%(FullPath)')" Exclude="@(ResolvedFileToPublish)"> <RelativePath>%(DistFiles.Identity)</RelativePath> <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory> <ExcludeFromSingleFile>true</ExcludeFromSingleFile> </ResolvedFileToPublish> </ItemGroup> </Target>
- ابتدا npm install را جهت اطمینان از به روز بودن وابستگیهای برنامه مجددا اجرا میکند.
- سپس npm run build را برای تولید فایلهای قابل ارائهی برنامهی React اجرا میکند.
- در آخر تمام فایلهای پوشهی ClientApp/build تولیدی را به بستهی نهایی توزیعی برنامهی ASP.NET Core، اضافه میکند.
کتاب درک رمزنگاری اطلاعات
Cryptography has crept into everything, from web browsers and email programs to cell phones, bank cards, cars and even into medical implants. Thus, an increasing number of people have to understand how crypto schemes work and how they can be used in practice. We wanted to create a book that teaches modern applied cryptography to readers with a technical background but without an education in pure mathematics.