Parler relied on several external services for security; but when those services were yanked away (due to Parler hosting neo-nazi and insurrectionist content), their code took the absence of such services as a reason to approve whatever action the user was trying to take. It’s the equivalent of your house security system letting everyone in if the phone-line goes down. There’s so much more to the Parler hack, from the lack of rate-limiting to the ability for people to pull down 60-70TBs of information from Parler’s AWS hosted storage, which — to add insult to injury, results in a massive egress bill from AWS to Parler, on top of AWS no longer hosting Parler.
ابزارهای پیش نیاز:
در اولین قدم، برنامهی متن باز Visual Studio Code را از اینجا دانلود و نصب کنید. برنامهی Visual Studio Code که در ادامهی فعالیتهای جدید متن باز مایکروسافت به بازار عرضه شده است، سریع، سبک و کاملا قابل توسعه و سفارشی سازی است و از اکثر زبانهای معروف پشتیبانی میکند.
در قدم بعدی، شما باید NET Core. را از اینجا (64 بیتی) دانلود و نصب کنید.
.NET Core چیست؟
NET Core. در واقع پیاده سازی بخشی از NET. اصلی است که به صورت متن باز در حال توسعه میباشد و بر روی لینوکس و مکینتاش هم قابل اجراست. موتور اجرای دات نت کامل CLR نام دارد و NET Core. نیز دارای موتور اجرایی CoreCLR است و شامل فریمورک CoreFX میباشد.
در حال حاضر شما میتوانید با استفاده از NET Core. برنامههای کنسولی و تحت وب با ASP.NET 5 بنویسید و احتمالا در آینده میتوان امیدوار بود که از ساختارهای پیچیدهتری مثل WPF نیز پشتیبانی کند.
پس از آنکه NET Core. را دانلود و نصب کردید، جهت شروع پروژه، یک پوشه را در یکی از درایوها ساخته (در این مثال E:\Projects\EF7-SQLite-NETCore) و Command prompt را در آنجا باز کنید. سپس دستورات زیر را به ترتیب اجرا کنید:
dotnet restore
dotnet run
- NuGet.Config (این فایل، تنظیمات مربوط به نیوگت را جهت کشف و دریافت وابستگیهای پروژه، شامل میشود)
- Program.cs (این فایل سی شارپ حاوی کد برنامه است)
- project.json (این فایل حاوی اطلاعات پلتفرم هدف و لیست وابستگیهای پروژه است)
دستور dotnet restore بر اساس لیست وابستگیها و پلتفرم هدف، وابستگیهای لازم را از مخزن نیوگت دریافت میکند. (در صورتی که در هنگام اجرای این دستور با خطای NullReferenceException مواجه شدید از دستور dnu restore استفاده کنید. این خطا در گیت هاب در حال بررسی است)
دستور dotnet run هم سورس برنامه را کامپایل و اجرا میکند. در صورتی که پیام Hello World را مشاهده کردید، یعنی برنامهی شما تحت NET Core. با موفقیت اجرا شده است.
توسعهی پروژه با Visual Studio Code
در ادامه، قصد داریم پروژهی HelloWorld را تحت Visual Studio Code باز کرده و تغییرات بعدی را در آنجا اعمال کنیم. پس از باز کردن Visual Studio Code از منوی File گزینهی Open Folder را انتخاب کنید و پوشهی حاوی پروژه (EF7-SQLite-NETCore) را انتخاب کنید. اکنون پروژهی شما تحت VS Code باز شده و قابل ویرایش است.
سپس از لیست فایلهای پروژه، فایل project.json را باز کرده و در بخش "dependencies" یک ردیف را برای EntityFramework.SQLite به صورت زیر اضافه کنید. به محض افزودن این خط در project.json و ذخیرهی آن، در صورتیکه قبلا این وابستگی دریافت نشده باشد، Visual Studio Code با نمایش یک هشدار در بالای برنامه به شما امکان دریافت اتوماتیک این وابستگی را میدهد. در نتیجه کافیست دکمهی Restore را زده و منتظر شوید تا وابستگی EntityFramework.SQLite از مخزن ناگت دانلود و برای پروژهی شما تنظیم شود.
"EntityFramework.SQLite": "7.0.0-rc1-final"
پس از کامل شدن این مرحله، در پروژههای بعدی تمام ارجاعات به وابستگیهای دریافت شده، از طریق مخزن موجود در سیستم خود شما، برطرف خواهد شد و نیاز به دانلود مجدد وابستگیها نیست.
اکنون همهی موارد، جهت توسعهی پروژه آماده است. ماوس خود را بر روی ریشهی پروژه در VS Code قرار داده و New Folder را انتخاب کنید و نام Models را برای آن تایپ کنید. این پوشه قرار است مدل کلاسهای پروژه را شامل شود. در اینجا ما یک مدل به نام Book داریم و نام کانتکست اصلی پروژه را هم LibraryContext گذاشتهایم.
بر روی پوشهی Models راست کلیک کرده و گزینهی New File را انتخاب کنید. سپس فایلهای Book.cs و LibraryContext.cs را ایجاد کرده و کدهای زیر را برای مدل و کانتکست، در درون این دو فایل قرار دهید.
Book.cs
namespace Models { public class Book { public int ID { get; set; } public string Title { get; set; } public string Author{get;set;} public int PublishYear { get; set; } } }
using Microsoft.Data.Entity; using Microsoft.Data.Sqlite; namespace Models { public class LibraryContext : DbContext { protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { var connectionStringBuilder = new SqliteConnectionStringBuilder { DataSource = "test.db" }; var connectionString = connectionStringBuilder.ToString(); var connection = new SqliteConnection(connectionString); optionsBuilder.UseSqlite(connection); } public DbSet<Book> Books { get; set; } } }
در قدم آخر هم کافیست که فایل Program.cs را تغییر دهید و مقادیری را در دیتابیس ذخیره و بازخوانی کنید.
using System; using Models; namespace ConsoleApplication { public class Program { public static void Main(string[] args) { Console.WriteLine("EF7 + Sqlite with the taste of .NET Core"); try { using (var context = new LibraryContext()) { context.Database.EnsureCreated(); var book1 = new Book() { Title = "Adaptive Code via C#: Agile coding with design patterns and SOLID principles ", Author = "Gary McLean Hall", PublishYear = 2014 }; var book2 = new Book() { Title = "CLR via C# (4th Edition)", Author = "Jefrey Ritcher", PublishYear = 2012 }; context.Books.Add(book1); context.Books.Add(book2); context.SaveChanges(); ReadData(context); } Console.WriteLine("Press any key to exit ..."); Console.ReadKey(); } catch (Exception ex) { Console.WriteLine($"An exception occured: {ex.Message}\n{ex.StackTrace}"); } } private static void ReadData(LibraryContext context) { Console.WriteLine("Books in database:"); foreach (var b in context.Books) { Console.WriteLine($"Book {b.ID}"); Console.WriteLine($"\tName: {b.Title}"); Console.WriteLine($"\tAuthor: {b.Author}"); Console.WriteLine($"\tPublish Year: {b.PublishYear}"); Console.WriteLine(); } } } }
جهت اجرای برنامه کافیست Command prompt را در آدرس پروژه باز کرده و دستور dotnet run را اجرا کنید. پروژهی شما کامپایل و اجرا میشود و خروجی مشابه زیر را مشاهده خواهید کرد. اگر برنامه را مجددا اجرا کنید، به جای دو کتاب اطلاعات چهار کتاب نمایش داده خواهد شد؛ چرا که در هر مرحله اطلاعات دو کتاب در دیتابیس درج میشود.
اگر به پوشهی bin که در پوشهی پروژه ایجاد شده است، نگاهی بیندازید، خبری از فایل باینری نیست. چرا که در لحظه، تولید و اجرا شده است. جهت build کردن پروژه و تولید فایل باینری کافیست دستور dotnet build را اجرا کنید، تا فایل باینری در پوشهی bin ایجاد شود.
جهت انتشار برنامه میتوانید دستور dotnet publish را اجرا کنید. این دستور نه تنها برنامه، که تمام وابستگیهای مورد نیاز آن را برای اجرای در یک پلتفرم خاص تولید میکند. برای مثال بعد از اجرای این دستور یک پوشهی win7-x64 حاوی 211 فایل در مجموع تولید شده است که تمامی وابستگیهای این پروژه را شامل میشود.
در واقع این پوشه تمام وابستگیهای مورد نیاز پروژه را همراه خود دارد و در نتیجه جهت اجرای این برنامه برخلاف برنامههای معمولی دات نت، دیگر نیازی به نصب هیچ وابستگی مجزایی نیست و حتی پروژههای نوشته شده تحت NET Core. را میتوانید در سیستمهای عاملهای دیگری مثل لینوکس و مکینتاش و یا Windows IoT بر روی سخت افزار Raspberry Pi 2 هم اجرا کنید.
جهت مطالعهی بیشتر:
امکان استفاده از گروپپالیسیها و...
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.
ایا معمار نرم افزار هستید؟
....
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.
Security Advisory Notices
CVE-2019-1077 Visual Studio Extension Auto Update Vulnerability
An elevation of privilege vulnerability exists when the Visual Studio Extension auto-update process improperly performs certain file operations. An attacker who successfully exploited this vulnerability could delete files in arbitrary locations. To exploit this vulnerability, an attacker would require unprivileged access to a vulnerable system. The security update addresses the vulnerability by securing locations the Visual Studio Extension auto-update performs file operations in.
CVE-2019-1075 ASP.NET Core Spoofing Vulnerability
A spoofing vulnerability exists in ASP.NET Core that could lead to an open redirect. An attacker who successfully exploited the vulnerability could redirect a targeted user to a malicious website. To exploit the vulnerability, an attacker could send a link that has a specially crafted URL and convince the user to click the link.
The security update addresses the vulnerability by correcting how ASP.NET Core parses URLs. Details can be found in the .NET Core release notes.
CVE-2019-1113 WorkflowDesigner XOML deserialization allows code execution
A XOML file referencing certain types could cause random code to be executed when the XOML file is opened in Visual Studio. There is now a restriction on what types are allowed to be used in XOML files. If a XOML file containing one of the newly unauthorized types is opened, a message is displayed explaining that the type is unauthorized.
For further information, please refer to https://support.microsoft.com/en-us/help/4512190/remote-code-execution-vulnerability-if-types-are-specified-in-xoml.
وضعیت خروجی متدهای بازنویسی شده تا پیش از C# 9.0
برای توضیح بهتر Covariant returns، نیاز است مثال زیر را بررسی کنیم:
public abstract class Product { public string Name { get; set; } public abstract ProductOrder Order(int quantity); } public class Book : Product { public string ISBN { get; set; } public override ProductOrder Order(int quantity) => new BookOrder { Quantity = quantity, Product = this }; } public class ProductOrder { public int Quantity { get; set; } } public class BookOrder : ProductOrder { public Book Product { get; set; } }
همانطور که مشاهده میکنید، در کلاس Book، تنها خروجی که برای متد Order بازنویسی شده میتوان درنظر گرفت، همانی است که در کلاس پایهی Product تعریف شدهاست و قابل تغییر نیست؛ یعنی همان ProductOrder.
همچنین در حین استفادهی از این کلاسها، تبدیل خروجی متد Order، به BookOrder ضروری است:
var book = new Book { Name = "My book", ISBN = "11-1-12-22-0" }; BookOrder orderBook = (BookOrder)book.Order(1);
امکان تغییر خروجی متدهای بازنویسی شده در C# 9.0
در C# 9.0 با مجاز اعلام شدن خروجیهای covariant، میتوان تغییرات زیر را به کدهای فوق اعمال کرد:
public class Book : Product { public string ISBN { get; set; } public override BookOrder Order(int quantity) => new BookOrder { Quantity = quantity, Product = this }; }
مزایای این ویژگی:
- داشتن یک خروجی مختص و متناسب با کلاس کتاب، مانند BookOrder؛ بجای ارائهی یک خروجی بسیار عمومی ProductOrder.
- نیاز به کار با Generics را برای اینگونه اختصاصی سازیها منتفی میکند.
- با این تغییر، دیگر نیازی به تبدیل نوع خروجی متد Order یک کتاب نیست و سطر سفارش دهی را میتوان به صورت زیر خلاصه کرد:
BookOrder orderBook = book.Order(1);
دریافت اطلاعات از سرور، توسط Axios
- ابتدا به پوشهی sample-22-backend ای که در قسمت قبل ایجاد کردیم، مراجعه کرده و فایل dotnet_run.bat آنرا اجرا کنید، تا endpointهای REST Api آن، قابل دسترسی شوند. برای مثال باید بتوان به مسیر https://localhost:5001/api/posts در مرورگر دسترسی یافت (و یا همانطور که عنوان شد، از آدرس https://jsonplaceholder.typicode.com/posts نیز میتوانید استفاده کنید؛ چون ساختار یکسانی دارند).
-سپس در برنامهی React ای که در قسمت قبل ایجاد کردیم، فایل app.js آنرا گشوده و ابتدا کتابخانهی Axios را import میکنیم:
import axios from "axios";
componentDidMount() { const promise = axios.get("https://localhost:5001/api/posts"); console.log(promise); }
تنظیمات CORS مخصوص React در برنامههای ASP.NET Core 3x
همانطور که مشاهده میکنید، پس از ذخیره سازی تغییرات، با اجرای برنامه، این Promise در حالت pending قرار گرفته و همچنین پس از پایان آن، حاوی نتیجهی عملیات نیز میباشد که در اینجا rejected است. علت شکست عملیات را در سطر بعدی آن ملاحظه میکنید که عنوان کردهاست «CORS policy» مناسبی در سمت سرور، برای این درخواست وجود ندارد؛ چرا؟ چون برنامهی React ما در مسیر http://localhost:3000/ اجرا میشود و برنامهی Web API در مسیر دیگری https://localhost:5001/ که شمارهی پورت ایندو یکی نیست. به همین جهت عنوان میکند که نیاز است در سمت سرور، هدرهای خاصی برای پردازش این نوع درخواستهای با Origin متفاوت وجود داشته باشد، تا مرورگر اجازهی دسترسی به آنرا بدهد. برای رفع این مشکل، برنامهی sample-22-backend را گشوده و تغییرات زیر را اعمال میکنیم:
ابتدا تنظیمات AddCors را با تعریف یک CORS policy جدید مخصوص آدرس http://localhost:3000، به متد ConfigureServices کلاس آغازین برنامه اضافه میکنیم:
public void ConfigureServices(IServiceCollection services) { services.AddCors(options => { options.AddPolicy("ReactCorsPolicy", builder => builder .AllowAnyMethod() .AllowAnyHeader() .WithOrigins("http://localhost:3000") .AllowCredentials() .Build()); }); services.AddSingleton<IPostsDataSource, PostsDataSource>(); services.AddControllers(); }
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseHttpsRedirection(); app.UseRouting(); //app.UseAuthentication(); //app.UseAuthorization(); app.UseCors("ReactCorsPolicy"); app.UseAuthorization(); app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); }
اینبار Promise بازگشت داده شده، در حالت resolved قرار گرفتهاست که به معنای موفقیت آمیز بودن عملیات async است. وجود [[PromiseStatus]] به معنای یک internal property است که توسط dot notation قابل دسترسی نیست. در اینجا [[PromiseValue]] نیز یک internal property غیرقابل دسترسی است که نتیجهی عملیات (response دریافتی از سرور) در آن قرار میگیرد. برای مثال در data آن، آرایهی مطالب دریافتی از سرور، قابل مشاهدهاست و یا status=200 به معنای موفقیت آمیز بودن پردازش درخواست، از سمت سرور است.
البته زمانیکه درخواست افزودن رکورد جدیدی را به سمت سرور ارسال میکنیم، میتوان دو درخواست را در برگهی network ابزارهای توسعه دهندگان مرورگر، مشاهده کرد:
در اولین درخواست، Request Method: OPTIONS را داریم که دقیقا مرتبط است با بررسی CORS توسط مرورگر.
دریافت اطلاعات شیء response از یک Promise و نمایش آن
همانطور که عنوان شد، [[PromiseValue]] نیز یک internal property غیرقابل دسترسی است. بنابراین اکنون این سؤال مطرح میشود که چگونه میتوان به اطلاعات آن دسترسی یافت؟
این شیء Promise، دارای متدی است به نام then است که نتیجهی عملیات async را بازگشت میدهد. البته این روش قدیمی کار کردن با Promiseها است و ما از آن در اینجا استفاده نخواهیم کرد. در جاوا اسکریپت مدرن، میتوان از واژهی کلیدی await برای دسترسی به شیء response دریافتی از سرور، استفاده کرد:
async componentDidMount() { const promise = axios.get("https://localhost:5001/api/posts"); console.log(promise); const response = await promise; console.log(response); }
البته قطعه کد نوشته شده، صرفا جهت توضیح مراحل مختلف عملیات، به این صورت چند مرحلهای نوشته شد، وگرنه میتوان واژهی کلیدی await را پیش از فراخوانی متدهای Axios نیز قرار داد:
async componentDidMount() { const response = await axios.get("https://localhost:5001/api/posts"); console.log(response); }
class App extends Component { state = { posts: [] }; async componentDidMount() { const { data: posts } = await axios.get("https://localhost:5001/api/posts"); this.setState({ posts }); // = { posts: posts } }
ایجاد یک مطلب جدید توسط Axios
در برنامهی React ای ایجاد شده، یک دکمهی Add نیز برای افزودن مطلبی جدید درنظر گرفته شدهاست. در یک برنامهی واقعیتر، معمولا فرمی وجود دارد و نتیجهی آن در حین submit، به سمت سرور ارسال میشود. در اینجا این سناریو را شبیه سازی خواهیم کرد:
const apiEndpoint = "https://localhost:5001/api/posts"; class App extends Component { state = { posts: [] }; async componentDidMount() { const { data: posts } = await axios.get(apiEndpoint); this.setState({ posts }); } handleAdd = async () => { const newPost = { title: "new Title ...", body: "new Body ...", userId: 1 }; const { data: post } = await axios.post(apiEndpoint, newPost); console.log(post); const posts = [post, ...this.state.posts]; this.setState({ posts }); };
- چون قرار است از آدرس https://localhost:5001/api/posts در قسمتهای مختلف برنامه استفاده کنیم، فعلا آنرا به صورت یک ثابت تعریف کرده و در متدهای get و post استفاده کردیم.
- در متد منتسب به خاصیت handleAdd، یک شیء جدید post را با ساختاری مشابه آن ایجاد کردهایم. این شیء جدید، دارای Id نیست؛ چون قرار است از سمت سرور پس از ثبت در بانک اطلاعاتی دریافت شود.
- سپس این شیء جدید را توسط متد post کتابخانهی Axios، به سمت سرور ارسال کردهایم. این متد نیز یک Promise را باز میگرداند. به همین جهت از واژهی کلیدی await برای دریافت نتیجهی واقعی آن استفاده شدهاست. همچنین هر زمانیکه await داریم، نیاز به ذکر واژهی کلیدی async نیز هست. اینبار این واژه باید پیش از قسمت تعریف پارامتر متد قرار گیرد و نه پیش از نام handleAdd؛ چون handleAdd در واقع یک خاصیت است که متدی به آن انتساب داده شدهاست.
- نتیجهی دریافتی از متد axios.post را اینبار به post، بجای posts تغییر نام دادهایم و همانطور که در تصویر زیر مشاهده میکنید، خاصیت id آن در سمت سرور مقدار دهی شدهاست:
- در آخر برای افزودن این رکورد، به مجموعهی رکوردهای موجود، از روش spread operator استفاده کردهایم تا ابتدا شیء post دریافتی از سمت سرور درج شود و سپس مابقی اعضای آرایهی posts موجود در state، در این آرایه گسترده شده و یک آرایهی جدید را تشکیل دهند. سپس این آرایهی جدید را جهت به روز رسانی state و در نتیجهی آن، به روز رسانی UI، به متد setState ارسال کردهایم، که نتیجهی آن درج این رکورد جدید، در ابتدای لیست است:
به روز رسانی اطلاعات در سمت سرور
در اینجا پیاده سازی متد put را مشاهده میکنید:
handleUpdate = async post => { post.title = "Updated"; const { data: updatedPost } = await axios.put( `${apiEndpoint}/${post.id}`, post ); console.log(updatedPost); const posts = [...this.state.posts]; const index = posts.indexOf(post); posts[index] = { ...post }; this.setState({ posts }); };
- اکنون امضای متد axios.put هرچند مانند متد post است، اما متد Update تعریف شدهی در سمت API سرور، یک چنین مسیری را نیاز دارد api/Posts/{id}. به همین جهت ذکر id مطلب، در URL نهایی نیز ضروری است.
- در اینجا نیز از واژههای await و async برای دریافت نتیجهی واقعی عملیات put و همچنین عملیات گذاری این متد به صورت async، استفاده شدهاست.
- در آخر، ابتدا آرایهی posts موجود در state را clone میکنیم. چون میخواهیم در آن، در ایندکسی که شیء post جاری قرار دارد، مقدار به روز رسانی شدهی آنرا قرار دهیم. سپس این آرایهی جدید را جهت به روز رسانی state و در نتیجهی آن، به روز رسانی UI، به متد setState ارسال کردهایم:
حذف اطلاعات در سمت سرور
برای حذف اطلاعات در سمت سرور، نیاز است یک HTTP Delete را به آن ارسال کنیم که اینکار را میتوان توسط متد axios.delete انجام داد. URL ای را که دریافت میکند، شبیه به URL ای است که برای حالت put ایجاد کردیم:
handleDelete = async post => { await axios.delete(`${apiEndpoint}/${post.id}`); const posts = this.state.posts.filter(item => item.id !== post.id); this.setState({ posts }); };
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: sample-22-backend-part-02.zip و sample-22-frontend-part-02.zip
ایجاد برنامهی backend ارائه دهندهی REST API
در اینجا یک برنامهی سادهی ASP.NET Core Web API را جهت تدارک سرویسهای backend، مورد استفاده قرار میدهیم. هرچند این مورد الزامی نبوده و اگر علاقمند بودید که مستقل از آن کار کنید، حتی میتوانید از سرویس آنلاین JSONPlaceholder نیز برای این منظور استفاده کنید که یک Fake Online REST API است. کار آن ارائهی یک سری endpoint است که به صورت عمومی از طریق وب قابل دسترسی هستند. میتوان به این endpintها درخواستهای HTTP خود را مانند GET/POST/DELETE/UPDATE ارسال کرد و از آن اطلاعاتی را دریافت نمود و یا تغییر داد. به هر کدام از این endpointها یک API گفته میشود که جهت آزمایش برنامهها بسیار مناسب هستند. برای نمونه در قسمت resources آن اگر به آدرس https://jsonplaceholder.typicode.com/posts مراجعه کنید، میتوان لیستی از مطالب را با فرمت JSON مشاهده کرد. کار آن ارائهی آرایهای از اشیاء جاوا اسکریپتی قابل استفادهی در برنامههای frontend است. بنابراین زمانیکه یک HTTP GET را به این endpoint ارسال میکنیم، آرایهای از اشیاء مطالب را دریافت خواهیم کرد. همین endpoint، امکان تغییر این اطلاعات را توسط برای مثال HTTP Delete نیز میسر کردهاست.
اگر علاقمندید بودید میتوانید از JSONPlaceholder استفاده کنید و یا در ادامه دقیقا ساختار همین endpoint ارائهی مطالب آنرا با ASP.NET Core Web API نیز پیاده سازی میکنیم (برای مطالعهی قسمت «ارتباط با سرور» اختیاری است و از هر REST API مشابهی که توسط nodejs یا PHP و غیره تولید شده باشد نیز میتوان استفاده کرد):
مدل مطالب
namespace sample_22_backend.Models { public class Post { public int Id { set; get; } public string Title { set; get; } public string Body { set; get; } public int UserId { set; get; } } }
منبع دادهی فرضی مطالب
برای ارائهی سادهتر برنامه، یک منبع دادهی درون حافظهای را به همراه یک سرویس، در اختیار کنترلر مطالب، قرار میدهیم:
using System; using System.Collections.Generic; using System.Linq; using sample_22_backend.Models; namespace sample_22_backend.Services { public interface IPostsDataSource { List<Post> GetAllPosts(); bool DeletePost(int id); Post AddPost(Post post); bool UpdatePost(int id, Post post); Post GetPost(int id); } /// <summary> /// هدف صرفا تهیه یک منبع داده آزمایشی ساده تشکیل شده در حافظه است /// </summary> public class PostsDataSource : IPostsDataSource { private readonly List<Post> _allPosts; public PostsDataSource() { _allPosts = createDataSource(); } public List<Post> GetAllPosts() { return _allPosts; } public Post GetPost(int id) { return _allPosts.Find(x => x.Id == id); } public bool DeletePost(int id) { var item = _allPosts.Find(x => x.Id == id); if (item == null) { return false; } _allPosts.Remove(item); return true; } public Post AddPost(Post post) { var id = 1; var lastItem = _allPosts.LastOrDefault(); if (lastItem != null) { id = lastItem.Id + 1; } post.Id = id; _allPosts.Add(post); return post; } public bool UpdatePost(int id, Post post) { var item = _allPosts .Select((pst, index) => new { Item = pst, Index = index }) .FirstOrDefault(x => x.Item.Id == id); if (item == null || id != post.Id) { return false; } _allPosts[item.Index] = post; return true; } private static List<Post> createDataSource() { var list = new List<Post>(); var rnd = new Random(); for (var i = 1; i < 10; i++) { list.Add(new Post { Id = i, UserId = rnd.Next(1, 1000), Title = $"Title {i} ...", Body = $"Body {i} ..." }); } return list; } } }
کنترلر Web API برنامهی backend
using System.Collections.Generic; using Microsoft.AspNetCore.Mvc; using sample_22_backend.Models; using sample_22_backend.Services; namespace sample_22_backend.Controllers { [ApiController] [Route("api/[controller]")] public class PostsController : ControllerBase { private readonly IPostsDataSource _postsDataSource; public PostsController(IPostsDataSource postsDataSource) { _postsDataSource = postsDataSource; } [HttpGet] public ActionResult<List<Post>> GetPosts() { return _postsDataSource.GetAllPosts(); } [HttpGet("{id}")] public ActionResult<Post> GetPost(int id) { var post = _postsDataSource.GetPost(id); if (post == null) { return NotFound(); } return Ok(post); } [HttpDelete("{id}")] public ActionResult DeletePost(int id) { var deleted = _postsDataSource.DeletePost(id); if (deleted) { return Ok(); } return NotFound(); } [HttpPost] public ActionResult<Post> CreatePost([FromBody]Post post) { post = _postsDataSource.AddPost(post); return CreatedAtRoute(nameof(GetPost), new { post.Id }, post); } [HttpPut("{id}")] public ActionResult<Post> UpdatePost(int id, [FromBody]Post post) { var updated = _postsDataSource.UpdatePost(id, post); if (updated) { return Ok(post); } return NotFound(); } } }
البته نمایش فرمت شدهی JSON در مرورگر کروم، نیاز به نصب این افزونه را دارد.
ایجاد ساختار ابتدایی برنامهی ارتباط با سرور
در اینجا برای بررسی کار با سرور، یک پروژهی جدید React را ایجاد میکنیم:
> create-react-app sample-22-frontend > cd sample-22-frontend > npm start
> npm install --save bootstrap
import "bootstrap/dist/css/bootstrap.css";
سپس فایل app.js را به شکل زیر تکمیل میکنیم:
import "./App.css"; import React, { Component } from "react"; class App extends Component { state = { posts: [] }; handleAdd = () => { console.log("Add"); }; handleUpdate = post => { console.log("Update", post); }; handleDelete = post => { console.log("Delete", post); }; render() { return ( <React.Fragment> <button className="btn btn-primary mt-1 mb-1" onClick={this.handleAdd}> Add </button> <table className="table"> <thead> <tr> <th>Title</th> <th>Update</th> <th>Delete</th> </tr> </thead> <tbody> {this.state.posts.map(post => ( <tr key={post.id}> <td>{post.title}</td> <td> <button className="btn btn-info btn-sm" onClick={() => this.handleUpdate(post)} > Update </button> </td> <td> <button className="btn btn-danger btn-sm" onClick={() => this.handleDelete(post)} > Delete </button> </td> </tr> ))} </tbody> </table> </React.Fragment> ); } } export default App;
نگاهی به انواع و اقسام HTTP Clientهای مهیا
در ادامه نیاز خواهیم داشت تا از طریق برنامههای React خود، درخواستهای HTTP را به سمت سرور (یا همان برنامهی backend) ارسال کنیم، تا بتوان اطلاعاتی را از آن دریافت کرد و یا تغییری را در اطلاعات موجود، ایجاد نمود. همانطور که پیشتر نیز در این سری عنوان شد، React برای این مورد نیز راهحل توکاری را به همراه ندارد و تنها کار آن، رندر کردن View و مدیریت DOM است. البته شاید این مورد یکی از مزایای کار با React نیز باشد! چون در این حالت میتوانید از کتابخانههایی که خودتان ترجیح میدهید، نسبت به کتابخانههایی که به شما ارائه/تحمیل (!) میشوند (مانند برنامههای Angular) آزادی انتخاب کاملی را داشته باشید. برای مثال هرچند Angular به همراه یک HTTP Module توکار است، اما تاکنون چندین بار بازنویسی از ابتدا شدهاست! ابتدا با یک کتابخانهی HTTP مقدماتی شروع کردند. بعدی آنرا منسوخ شده اعلام و با یک ماژول جدید جایگزین کردند. بعد در نگارشی دیگر، چون این کتابخانه وابستهاست به RxJS و خود RxJS نیز بازنویسی کامل شد، روش کار کردن با این HTTP Module نیز مجددا تغییر پیدا کرد! بنابراین اگر با Angular کار میکنید، باید کارها را آنگونه که Angular میپسندد، انجام دهید؛ اما در اینجا خیر و آزادی انتخاب کاملی برقرار است.
بنابراین اکنون این سؤال مطرح میشود که در React، برای برقراری ارتباط با سرور، چه باید کرد؟ در اینجا آزاد هستید برای مثال از Fetch API جدید مرورگرها و یا روش Ajax ای مبتنی بر XML قدیمیتر آنها، استفاده کنید (اطلاعات بیشتر) و یا حتی اگر علاقمند باشید میتوانید از محصور کنندههای آن مانند jQuery Ajax استفاده کنید. بنابراین اگر با jQuery Ajax راحت هستید، به سادگی میتوانید از آن در برنامههای React نیز استفاده کنید. اما ... ما در اینجا از یک کتابخانهی بسیار محبوب و قدرتمند HTTP Client، به نام Axios (اکسیوس/ یک واژهی یونانی به معنای «سودمند») استفاده خواهیم کرد که فقط تعداد بار دانلود هفتگی آن، 6 میلیون بار است!
نصب Axios در برنامهی React این قسمت
برای نصب کتابخانهی Axios، در ریشهی پروژهی React این قسمت، دستور زیر را در خط فرمان صادر کنید:
> npm install --save axios
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: sample-22-frontend-part-01.zip و sample-22-backend-part-01.zip
public class Person { public Person() { personId= this.GetType().Name + (new Random()).Next(1, int.MaxValue); } }
var student1=new Student(){Name="Iraj",Age=21}; var student1=new Student(){Name="Nima",Age=20}; var student1=new Student(){Name="Sara",Age=25}; var student1=new Student(){Name="Mina",Age=22}; var student1=new Student(){Name="Narges",Age=26}; var teacher1=new Student(){Name="Navaei",Age=45}; var teacher2=new Student(){Name="Imani",Age=50};
public class Person { public Person() { personId= this.GetType().Name + (new Random()).Next(1, int.MaxValue); Debug.Print(personId) } }
using System; using System.Threading; public static class RandomProvider { private static int seed = Environment.TickCount; private static ThreadLocal<Random> randomWrapper = new ThreadLocal<Random>(() => new Random(Interlocked.Increment(ref seed)) ); public static Random GetThreadRandom() { return randomWrapper.Value; } }
- از تاریخ سرور برای نمایش روز جاری استفاده میکند.
- به زبان جاوااسکریپت است و نیازی به سایر فریمورکها مانند jQuery ندارد.
- سبک و کم حجم است (3.5 کیلوبایت)
- روزها به صورت عمودی لیست شدهاند مانند برنامه هفتگی مدرسه که کاربران فارسیزبان با آن راحتترند.
طرز استفاده :
PersianDatePicker.Show(textBox, today)
today : تاریخ روز جاری است.
برای مثال :
<html> <head> <script type="text/javascript" src="Scripts/PersianDatePicker.min.js"></script> <link type="text/css" rel="stylesheet" href="Content/PersianDatePicker.min.css" /> </head> <body> <input type="text" onclick="PersianDatePicker.Show(this, '1392/03/22');" /> </body> </html>
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="WebForm1.aspx.cs" Inherits="WebApplication4.WebForm1" %> <!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head runat="server"> <title></title> <script src="Scripts/PersianDatePicker.min.js"></script> <link href="Content/PersianDatePicker.min.css" rel="stylesheet" /> </head> <body> <form id="form1" runat="server"> <div> <asp:Label runat="server" Text="Label" AssociatedControlID="TextBox1">تاریخ : </asp:Label> <asp:TextBox runat="server" ID="TextBox1"></asp:TextBox> </div> </form> </body> </html>
public partial class WebForm1 : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { var now = PersianDateTime.Now; var today = now.ToString(PersianDateTimeFormat.Date); TextBox1.Attributes["onclick"] = "PersianDatePicker.Show(this,'" + today + "');"; } }
در مثال بالا برای گرفتن تاریخ روز جاری از PersianDateTime استفاده شده است.
در ASP.NET MVC یا PHP یا هر زبان دیگری کافیست onclick کنترل متنی مورد نظر در سمت سرور مانند مثال بالا مقدار داده شود. یا میتوان image کوچکی کنار textbox مورد نظر قرار داد و onclick آن تنظیم شود.
برای استفاده از PersianDatePicker میتوانید آنرا از NuGet دریافت کنید :