بررسی جامع مسیریابی در ASP.NET 5
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.
In this video, you will get to experience bringing SQL and Spark together as a unified data platform running on Kubernetes and learn how:
• Data virtualization integrates data from disparate sources, locations and formats, without replicating or moving the data, to create a single "virtual" data layer
• Data Lake - SQL 2019 provides SQL and Spark query capabilities over a scalable storage, across relational and big data
• Data Mart provides an ability to scale out storage for super-fast performance over big data or data from other external sources
اصول کلی طراحی یک اعتبارسنج ساده
در قسمت قبل، تمام اطلاعات فرم لاگین را درون شیء account خاصیت state قرار دادیم. در اینجا نیز شبیه به چنین شیءای را برای ذخیره سازی خطاهای اعتبارسنجی فیلدهای فرم، تعریف میکنیم:
class LoginForm extends Component { state = { account: { username: "", password: "" }, errors: { username: "Username is required" } };
البته در ابتدای کار، خاصیت errors را با یک شیء خالی ({}) مقدار دهی میکنیم و سپس در متد مدیریت ارسال فرم به سرور:
validate = () => { return { username: "Username is required." }; }; handleSubmit = e => { e.preventDefault(); const errors = this.validate(); console.log("Validation errors", errors); this.setState({ errors }); if (errors) { return; } // call the server console.log("Submitted!"); };
- اگر خطایی وجود داشت، به مرحلهی بعد که ارسال فرم به سمت سرور میباشد، نخواهیم رسید و کار را با یک return، خاتمه میدهیم.
- علت فراخوانی متد setState در اینجا، درخواست رندر مجدد فرم، با توجه به خطاهای اعتبارسنجی ممکنی است که به خاصیت errors، اضافه یا به روز رسانی کردهایم.
- نمونهای از خروجی متد validate را نیز در اینجا مشاهده میکنید که تشکیل شدهاست از یک شیء، که هر خاصیت آن، به نام یک فیلد موجود در فرم، اشاره میکند.
پیاده سازی یک اعتبارسنج ساده
در اینجا یک نمونه پیاده سازی ساده و ابتدایی منطق اعتبارسنجی فیلدهای فرم را ملاحظه میکنید:
validate = () => { const { account } = this.state; const errors = {}; if (account.username.trim() === "") { errors.username = "Username is required."; } if (account.password.trim() === "") { errors.password = "Password is required."; } return Object.keys(errors).length === 0 ? null : errors; };
- سپس یک شیء خالی error را تعریف کردهایم.
- در ادامه با توجه به اینکه مقادیر المانهای فرم در state وجود دارند، خالی بودن آنها را بررسی میکنیم. اگر خالی بودند، یک خاصیت جدید را با همان نام المان مورد بررسی، به شیء errors اضافه کرده و پیام خطایی را درج میکنیم.
- در نهایت این شیء errors و یا نال را (در صورت عدم وجود خطایی) بازگشت میدهیم.
برای آزمایش آن، پس از اجرای برنامه، یکبار بدون وارد کردن اطلاعاتی، بر روی دکمهی Login کلیک کنید؛ یکبار هم با وارد کردن اطلاعاتی در فیلدهای مختلف. در این بین کنسول توسعه دهندگان مرورگر را نیز جهت مشاهدهی شیءهای error لاگ شده، بررسی نمائید.
نمایش خطاهای اعتبارسنجی فیلدهای فرم
در قسمت قبل، کامپوننت جدید src\components\common\input.jsx را جهت کاهش کدهای تکراری تعاریف المانهای ورودی، ایجاد کردیم. در اینجا نیز میتوان کار نمایش خطاهای اعتبارسنجی را قرار داد:
import React from "react"; const Input = ({ name, label, value, error, onChange }) => { return ( <div className="form-group"> <label htmlFor={name}>{label}</label> <input value={value} onChange={onChange} id={name} name={name} type="text" className="form-control" /> {error && <div className="alert alert-danger">{error}</div>} </div> ); }; export default Input;
- سپس با توجه به نکتهی «رندر شرطی عناصر در کامپوننتهای React» در قسمت 5، اگر error مقداری داشته باشد و به true تفسیر شود، آنگاه به صورت خودکار، div ای که دارای کلاسهای بوت استرپی اخطار است به همراه متن خطا، رندر خواهد شد؛ در غیراینصورت هیچ div ای به صفحه اضافه نمیشود.
- اکنون متد رندر کامپوننت فرم لاگین را به صورت زیر تکمیل میکنیم:
render() { const { account, errors } = this.state; return ( <form onSubmit={this.handleSubmit}> <Input name="username" label="Username" value={account.username} onChange={this.handleChange} error={errors.username} /> <Input name="password" label="Password" value={account.password} onChange={this.handleChange} error={errors.password} /> <button className="btn btn-primary">Login</button> </form> ); }
تا اینجا اگر تغییرات را ذخیره کرده و برنامه را اجرا کنیم، با کلیک بر روی دکمهی Login، خطاهای اعتبارسنجی به صورت زیر ظاهر میشوند:
در این حالت اگر هر دو فیلد را تکمیل کرده و بر روی دکمهی لاگین کلیک کنیم، به خطای زیر در کنسول توسعه دهندگان مرورگر میرسیم:
loginForm.jsx:55 Uncaught TypeError: Cannot read property 'username' of null at LoginForm.render (loginForm.jsx:55)
this.setState({ errors: errors || {} });
اعتبارسنجی فیلدهای یک فرم در حین ورود اطلاعات در آنها
تا اینجا نحوهی اعتبارسنجی فیلدهای ورودی را در حین submit بررسی کردیم. شبیه به همین روش را به حالت onChange و متد handleChange فرم لاگین که در قسمت قبل تکمیل کردیم نیز میتوان اعمال کرد:
handleChange = ({ currentTarget: input }) => { const errors = { ...this.state.errors }; //cloning an object const errorMessage = this.validateProperty(input); if (errorMessage) { errors[input.name] = errorMessage; } else { delete errors[input.name]; } const account = { ...this.state.account }; //cloning an object account[input.name] = input.value; this.setState({ account, errors }); };
- سپس اینبار فقط نیاز داریم اعتبار اطلاعات ورودی یک فیلد را بررسی کنیم و متد validate فعلی، فیلدهای کل فرم را با هم تعیین اعتبار میکند. به همین جهت متد جدید validateProperty را به صورت زیر تعریف میکنیم. اگر این متد خروجی داشت، خاصیت متناظر با آنرا در شیء errors به روز رسانی میکنیم؛ در غیراینصورت این خاصیت را از شیء errors حذف میکنیم تا پیام اشتباهی را نمایش ندهد. در نهایت توسط متد setState، مقدار خاصیت errors را با شیء errors جاری به روز رسانی میکنیم:
validateProperty = ({ name, value }) => { if (name === "username") { if (value.trim() === "") { return "Username is required."; } // ... } if (name === "password") { if (value.trim() === "") { return "Password is required."; } // ... } };
پس از ذخیره سازی این تغییرات، برای آزمایش آن، یکبار حرف a را بجای username وارد کنید و سپس آنرا حذف کنید. بلافاصله پیام خطای مرتبطی نمایش داده خواهد شد و اگر مجددا عبارتی را وارد کنیم، این پیام محو میشود که معادل قسمت delete در کدهای فوق است.
معرفی Joi
تا اینجا، هدف نمایش ساختار یک اعتبارسنج ساده بود. این روش مقیاس پذیر نیست و در ادامه آنرا با یک کتابخانهی اعتبارسنجی بسیار پیشرفته به نام Joi، جایگزین خواهیم کرد که نمونه مثالهای آنرا در اینجا میتوانید مشاهده کنید:
const Joi = require('@hapi/joi'); const schema = Joi.object({ username: Joi.string().alphanum().min(3).max(30).required(), password: Joi.string().pattern(/^[a-zA-Z0-9]{3,30}$/), email: Joi.string().email({ minDomainSegments: 2, tlds: { allow: ['com', 'net'] } }) }); schema.validate({ username: 'abc', birth_year: 1994 });
برای نصب آن، پس از باز کردن پوشهی اصلی برنامه توسط VSCode، دکمههای ctrl+` را فشرده (ctrl+back-tick) و دستورات زیر را در ترمینال ظاهر شده وارد کنید:
> npm install @hapi/joi --save > npm i --save-dev @types/hapi__joi
سپس به کامپوننت فرم لاگین مراجعه کرده و در ابتدای آن، Joi را import میکنیم:
import Joi from "@hapi/joi";
schema = { username: Joi.string() .required() .label("Username"), password: Joi.string() .required() .label("Password") };
سپس ابتدای متد validate قبلی را به صورت زیر بازنویسی میکنیم:
validate = () => { const { account } = this.state; const validationResult = Joi.object(this.schema).validate(account, { abortEarly: false }); console.log("validationResult", validationResult);
اکنون اگر برنامه را اجرا کرده و بدون ورود اطلاعاتی، بر روی دکمهی لاگین کلیک کنیم، خروجی زیر در کنسول توسعه دهندگان مرورگر ظاهر میشود:
همانطور که مشاهده میکنید، خروجی Joi، یک شیء است که اگر دارای خاصیت error بود، یعنی خطای اعتبارسنجی رخدادهاست. سپس باید خاصیت آرایهای details این شیء error را جهت یافتن خواص مشکل دار بررسی کرد. هر خاصیت در اینجا با path مشخص میشود. بنابراین قدم بعدی، تبدیل این ساختار، به ساختار شیء errors موجود در state کامپوننت جاری است تا مابقی برنامه بتواند بدون تغییری از آن استفاده کند.
نگاشت شیء دریافتی از Joi، به شیء errors موجود در state کامپوننت لاگین
خاصیت error شیء دریافتی از متد validate کتابخانهی Joi، تنها زمانی ظاهر میشود که خطایی وجود داشته باشد. همچنین خاصیت details آن نیز آرایهای از اشیاء با خواص message و path است. این path نیز یک آرایه است که اولین المان آن، نام خاصیت در حال بررسی است. اکنون میخواهیم این آرایه را تبدیل به یک شیء قابل درک برای برنامه کنیم:
validate = () => { const { account } = this.state; const validationResult = Joi.object(this.schema).validate(account, { abortEarly: false }); console.log("validationResult", validationResult); if (!validationResult.error) { return null; } const errors = {}; for (let item of validationResult.error.details) { errors[item.path[0]] = item.message; } return errors; };
اکنون اگر تغییرات را ذخیره کرده و برنامه را اجرا کنیم، همانند قبل میتوان خطاهای اعتبارسنجی در حین submit را مشاهده کرد:
بازنویسی متد validateProperty توسط Joi
تا اینجا متد validate ساده و ابتدایی خود را با استفاده از امکانات کتابخانهی Joi، بازنویسی کردیم. اکنون نوبت بازنویسی متد اعتبارسنجی در حین تایپ اطلاعات است:
validateProperty = ({ name, value }) => { const userInputObject = { [name]: value }; const propertySchema = Joi.object({ [name]: this.schema[name] }); const { error } = propertySchema.validate(userInputObject, { abortEarly: true }); return error ? error.details[0].message : null; };
تا اینجا بجای this.state.account که به کل فرم اشاره میکند، شیء اختصاصیتر userInputObject را ایجاد کردهایم که معادل اطلاعات فیلد ورودی کاربر است. یک چنین نکتهای را در مورد schema نیز باید رعایت کرد. در اینجا نمیتوان اعتبارسنجی را با this.schema شروع کرد؛ چون این شیء نیز به اطلاعات کل فرم اشاره میکند و نه تک فیلدی که هم اکنون در حال کار با آن هستیم. بنابراین نیاز است یک Joi.object جدید را بر اساس name رسیده، از this.schema کلی، استخراج و تولید کرد؛ مانند propertySchema که مشاهده میکنید.
اکنون میتوان متد validate این شیء اسکیمای جدید را فراخوانی کرد و خاصیت error شیء حاصل از آنرا توسط Object Destructuring، استخراج نمود. در اینجا abortEarly به true تنظیم شدهاست (البته حالت پیشفرض آن است و نیازی به ذکر صریح آن نیست). علت اینجا است که نمایش خطاهای بیش از اندازه به کاربر، برای او گیج کننده خواهند بود؛ به همین جهت هربار، اولین خطای حاصل را به او نمایش میدهیم.
غیرفعالسازی دکمهی submit در صورت شکست اعتبارسنجیهای فرم
در ادامه میخواهیم دکمهی submit فرم لاگین، تا زمانیکه اعتبارسنجی آن با موفقیت به پایان برسد، غیرفعال باشد:
<button disabled={this.validate()} className="btn btn-primary"> Login </button>
فراخوانی setStateهای تعریف شدهی در متدهای رویدادگردان این فرم هستند که سبب رندر مجدد کامپوننت شده و در این بین در متد رندر، کار بررسی مجدد مقدار نهایی متد validate صورت میگیرد تا بر اساس آن، فعال یا غیرفعال بودن دکمهی Login، مشخص شود.
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: sample-19.zip
آیندهی Microsoft
- Microsoft will change how it reports financially.
- Microsoft will become a “consumer-focused enterprise company.”
- Windows will converge into a single code base capable of running on any platform.
- Application development will converge into allowing developers to maintain a single code base for an app that will run on any device capable of running Windows.
- Release frequency will dramatically increase.
- The “modern” user interface is here to stay.
At The Bit, we understand how overwhelming it can be to navigate through all the different tutorials online and try to teach yourself programming skills alone.
That’s why we’ve created The Bit — we’ll match you to a peer with similar skills and interests to take the tutorial of your choosing.
Find a tutorial you want to take below? Upload it to our platform to get the support and accountability you need to start building cool shit.
ایجاد یک پروژهی ابتدایی Blazor WASM
پروژهای را که در این مطلب تکمیل خواهیم کرد، از نوع Blazor WASM هاست شدهاست. بنابراین در پوشهی فرضی BlazorAsyncValidation، دستور «dotnet new blazorwasm --hosted» را صادر میکنیم تا ساختار ابتدایی پروژه که به همراه یک کلاینت Blazor WASM و یک سرور ASP.NET Core Web API است، تشکیل شود. از قسمت Web API، برای پیاده سازی اعتبارسنجی سمت سرور استفاده خواهیم کرد.
مدل ثبت نام برنامه
مدل ثبت نام برنامه تنها از یک خاصیت نام تشکیل شده و در پروژهی Shared قرار میگیرد تا هم در کلاینت و هم در سرور قابل استفاده باشد:
using System.ComponentModel.DataAnnotations; namespace BlazorAsyncValidation.Shared; public class UserDto { [Required] public string Name { set; get; } = default!; }
کنترلر API ثبت نام برنامه
کنترلر زیر که در پوشهی BlazorAsyncValidation\Server\Controllers قرار میگیرد، منطق بررسی تکراری نبودن نام دریافتی از برنامهی کلاینت را شبیه به منطق remote validation استاندارد MVC، پیاده سازی میکند که در نهایت یک true و یا false را باز میگرداند.
در اینجا خروجی بازگشت داده کاملا در اختیار شما است و نیازی نیست تا حتما ارتباطی با MVC داشته باشد؛ چون مدیریت سمت کلاینت بررسی آنرا خودمان انجام خواهیم داد و نه یک کتابخانهی از پیش نوشته شده و مشخص.
using BlazorAsyncValidation.Shared; using Microsoft.AspNetCore.Mvc; namespace BlazorAsyncValidation.Server.Controllers; [ApiController, Route("api/[controller]/[action]")] public class RegisterController : ControllerBase { [HttpPost] public IActionResult IsUserNameUnique([FromBody] UserDto userModel) { if (string.Equals(userModel?.Name, "Vahid", StringComparison.OrdinalIgnoreCase)) { return Ok(false); } return Ok(true); } }
غنی سازی فرم استاندارد Blazor جهت انجام Remote validation
اگر بخواهیم از EditForm استاندارد Blazor در حالت متداول آن و بدون هیچ تغییری استفاده کنیم، مانند مثال زیر که InputText متصل به خاصیت Name مربوط به Dto برنامه را نمایش میدهد:
@page "/" <PageTitle>Index</PageTitle> <h2>Register</h2> <EditForm EditContext="@EditContext" OnValidSubmit="DoSubmitAsync"> <DataAnnotationsValidator/> <div class="row mb-2"> <label class="col-form-label col-lg-2">Name:</label> <div class="col-lg-10"> <InputText @bind-Value="Model.Name" class="form-control"/> <ValidationMessage For="@(() => Model.Name)"/> </div> </div> <button class="btn btn-secondary" type="submit">Submit</button> </EditForm>
public partial class Index { private const string UserValidationUrl = "/api/Register/IsUserNameUnique"; private ValidationMessageStore? _messageStore; [Inject] private HttpClient HttpClient { set; get; } = default!; private EditContext? EditContext { set; get; } private UserDto Model { get; } = new();
ValidationMessageStore به همراه متد Add است و اگر به آن نام فیلد مدنظر را به همراه پیامی، اضافه کنیم، این اطلاعات را به صورت خطای اعتبارسنجی توسط کامپوننت ValidationMessage نمایش میدهد.
محل مقدار دهی اولیهی این اشیاء نیز در روال رویدادگردان OnInitialized به صورت زیر است:
protected override void OnInitialized() { EditContext = new EditContext(Model); _messageStore = new ValidationMessageStore(EditContext); EditContext.OnFieldChanged += (sender, eventArgs) => { var fieldIdentifier = eventArgs.FieldIdentifier; _messageStore?.Clear(fieldIdentifier); _ = InvokeAsync(async () => { var errors = await OnValidateFieldAsync(fieldIdentifier.FieldName); if (errors?.Any() != true) { return; } foreach (var error in errors) { _messageStore?.Add(fieldIdentifier, error); } EditContext.NotifyValidationStateChanged(); }); StateHasChanged(); }; EditContext.OnValidationStateChanged += (sender, eventArgs) => StateHasChanged(); EditContext.OnValidationRequested += (sender, eventArgs) => _messageStore?.Clear(); }
برای مثال اگر فیلدی تغییر کند، رویداد OnFieldChanged صادر میشود. در همینجا است که کار فراخوانی متد OnValidateFieldAsync که در ادامه معرفی میشود را انجام میدهیم تا کار اعتبارسنجی Async سمت سرور را انجام دهد. اگر نتیجهای به همراه آن بود، توسط messageStore به صورت یک خطای اعتبارسنجی نمایش داده خواهد شد و همچنین EditContext نیز با فراخوانی متد NotifyValidationStateChanged، وادار به بهروز رسانی وضعیت اعتبارسنجی خود میگردد.
متد سفارشی OnValidateFieldAsync که کار اعتبارسنجی سمت سرور را انجام میدهد، به صورت زیر تعریف شدهاست:
private async Task<IList<string>?> OnValidateFieldAsync(string fieldName) { switch (fieldName) { case nameof(UserDto.Name): var response = await HttpClient.PostAsJsonAsync( UserValidationUrl, new UserDto { Name = Model.Name }); var responseContent = await response.Content.ReadAsStringAsync(); if (string.Equals(responseContent, "false", StringComparison.OrdinalIgnoreCase)) { return new List<string> { $"`{Model.Name}` is in use. Please choose another name." }; } // TIP: It's better to use the `DntDebounceInputText` component for this case to reduce the network round-trips. break; } return null; }
یک نکته: InputText استاندارد در حالت معمول آن، پس از تغییر focus به یک کنترل دیگر، سبب بروز رویداد OnFieldChanged میشود و نه در حالت فشرده شدن کلیدها. به همین جهت اگر برنامه پیوستی را میخواهید آزمایش کنید، نیاز است فقط focus را تغییر دهید و یا یک کنترل سفارشی را برای اینکار توسعه دهید.
کدهای کامل این مطلب را از اینجا میتوانید دریافت کنید: BlazorAsyncValidation.zip
بررسی چند ریسمانی در #C
As anyone in the .NET community who hasn't been living under a rock will know, there's a lot of exciting things happening with .NET at the moment with the announcement of the open source, cross platform, .NET Core. However, partly due to the very open nature of its evolution, there's been a whole host of names associated with its development - vNext, ASP.NET 5, ASP.NET Core, .NET generations etc.
In this post I'm going to try and clarify some of the naming and terminology surrounding the evolution of the .NET framework. I'll discuss some of the challenges the latest iteration is attempting to deal with and how the latest developments aim to address these.
This is really for those that have seen some of the big announcements but aren't sure about the intricacies of this new framework and how it relates to the existing ecosystem, which was my situation before I really started digging into it all properly!
Hopefully by the end of this article you'll have a clearer grasp of the latest in .NET!