اشتراکها
نظرات مطالب
Url Routing در ASP.Net WebForms
در حالت کلی در وب فرمها، برای مسیردهی سازگار با Routing باید از ResolveUrl استفاده کنید.
برای اسکریپتها:
برای شیوهنامهها:
متد ResolveUrl به صورت خودکار مسیر صحیح را تولید میکند.
اما ... این روش کار کردن صحیح نیست. چون به زودی به تعداد زیادی فایل اسکریپت و CSS لینک داده شده در صفحه میرسید. برای یکی کردن آنها یا از ScriptManager استفاده کنید (برای اسکریپتها) و یا از روشهای bundling & minification که با فایلهای CSS و JS سازگار است. مسیریابیها را هم به صورت خودکار تصحیح میکند.
برای اسکریپتها:
<script type='text/javascript' src='<%= ResolveUrl("~/Scripts/test.js") %>'></script>
<link rel="stylesheet" href="<%= ResolveUrl("~/myStylysheet.css")%>" type="text/css" />
اما ... این روش کار کردن صحیح نیست. چون به زودی به تعداد زیادی فایل اسکریپت و CSS لینک داده شده در صفحه میرسید. برای یکی کردن آنها یا از ScriptManager استفاده کنید (برای اسکریپتها) و یا از روشهای bundling & minification که با فایلهای CSS و JS سازگار است. مسیریابیها را هم به صورت خودکار تصحیح میکند.
باسلام؛ من توابع کتابخانههای جاوا اسکریپتی که سراسری هستند و در تمام کامپوننتهای برنامه قابل دسترسی خواهند بود به صفحه index.html اضافه کردم .مشکل اینجاست که یکی از کتابخانههای جاوا اسکرپیتی، یکی از متدهاش نیاز به یک تگ html داخل MainLayout.razor دارد که احیانا چون این کتابخانه قبل ساخت کامل کدهای اhtm صدا زده میشود که تگ html رو پیدا نمیکند و به خطا میخورد. چه روشی برای اینکه کتابخانه با تاخیر اجرا شود یا بعد از لود کامل کدهای html؟
مقدارش قبلا تنظیم شدهاست. data ایی که به اون ارسال شده حاوی postId هست ({} یعنی یک شیء حاوی اطلاعات و خواص جاوا اسکریپتی). در اینجا فقط به صورت پویا یک خاصیت جدید به آن اضافه میشود. جاوا اسکریپت یک زبان dynamic هست. مثل واژهی کلیدی dynamic در #C که بعدش میتونید خواصی را به دلخواه به این نوع اشیاء اضافه کنید. همان توضیحات مطلبی که لینک دادم.
آیا راه حلی برای فراخوانی متدهای غیر استاتیک #C از طریق کدهای جاوااسکریپتی در یک فایل js وجود دارد؟ برای مثال API گوگل مپ جزییات یک آدرس درخواست شده را به یک فایل js برمیگرداند، از چه روشی میتوان این مقدار را به کامپوننت برای پردازش و ذخیره ارسال کرد؟
کامپوننتی که کار پردازش نتیجه API را انجام میدهد باید خروجی خود را بصورت یک EventCallBack به والد خود برای ذخیره ارسال کند، از این جهت متدها استاتیک نیستند. آیا از روش غیر استاتیک که معرفی کردید برای این سناریو میشود استفاده کرد یا روش دیگری وجود دارد؟
ممنون.
validateProperty یک خاصیت را تعیین اعتبار میکند، اما برای تطابق پسوردها، نیاز به تعیین اعتبار دو خاصیت را با هم دارد. به همین جهت برای مثال میتوان پارامتر چهارمی را به نام correspond، در اینجا اضافه کرد تا فیلد متناظر با آنرا هم مشخص کند:
renderInput(name, label, type = "text", correspond) { const { data, errors } = this.state; return ( <Input name={name} type={type} label={label} value={data[name]} onChange={this.handleChange} error={errors[name]} correspond={correspond} /> ); }
<form onSubmit={this.handleSubmit}> {this.renderInput("username", "Username")} {this.renderInput("password", "Password", "password")} {this.renderInput( "confirmPassword", "Confirm Password", "password", "password" )} {this.renderInput("name", "Name")} {this.renderButton("Register")} </form>
state = { data: { username: "", password: "", name: "", confirmPassword: "" }, errors: {}, }; schema = { username: Joi.string() .required() .email({ minDomainSegments: 2, tlds: { allow: ["com", "net", "info"] } }) .label("Username"), password: Joi.string().required().min(5).label("Password"), name: Joi.string().required().label("Name"), confirmPassword: Joi.any().valid(Joi.ref("password")).required().messages({ "any.only": "با رمز عبور مطابقت ندارد", }), };
validateProperty = ({ name, value, attributes }) => { const userInputObject = { [name]: value }; const schemaMap = { [name]: this.schema[name] }; if (attributes.correspond) { const correspondFieldName = attributes.correspond.value; userInputObject[correspondFieldName] = this.state.data[ correspondFieldName ]; schemaMap[correspondFieldName] = this.schema[correspondFieldName]; } const propertySchema = Joi.object(schemaMap); const { error } = propertySchema.validate(userInputObject, { abortEarly: true, }); return error ? error.details[0].message : null; };
Blazor، چارچوبی است ارائه شده توسط مایکروسافت که به ما اجازه میدهد برنامههای تعاملی وب را با CSharp و بدون JavaScript بنویسیم. Blazor از اساس، Component based بوده و در برنامههایی که Backend نیز با CSharp نوشته شده باشد ( مثلا با ASP.NET Core) امکان به اشتراک گذاری کد بین کلاینت و سرور را نیز فراهم میکند. معماری Blazor معماریای مدرن است که در دل خود از امکانات CSharp نیز به خوبی بهره برده است تا بتوان پروژه را بهصورتی که قابلیت نگهداری بالایی داشته باشد، توسعه داد. Blazor در ذات معماری خود امکان نوشتن برنامههای Native موبایل را نیز میدهد و برای اثبات این مهم چندین دمو برای Proof of concept ارائه شده است؛ اما تا به امروز امکانی که به صورت Production ready باشد، ارائه نشده است.
Blazor به دو شکل کار میکند. Blazor Server و Blazor Client
در Blazor Client با استفاده از پشتیبانی نزدیک به 90% ای مرورگرها از Web Assembly که اجازه اجرای کدهای غیر JavaScript ای را در مرورگر میدهد، ابتدا DLLها به همراه HTML-CSS و عکسها و ... دانلود شده و برنامه به صورت Single Page App کار میکند. در این مدل شما میتوانید از تکنیک Pre rendering یا SSR نیز استفاده کنید تا تجربه کاربری بهتری را نیز ارائه دهید و یا در بحث SEO بهتر عمل کنید.
Blazor در سمت کلاینت از NET Standard 2.1 پشتیبانی میکند که عملا به شما اجازه میدهد بازهی گستردهای از Nuget Packageها را استفاده کنید.
البته Blazor Client به صورت Preview ارائه شده است و "فعلا" دو مشکل را دارد:
۱- امکان دیباگ خوبی ندارد.
۲- در عمل باید فایلهای اجرایی برنامه به صورت wasm یا web assembly دانلود شوند که در این حالت سرعتی بسیار بالا و بیش از جاوااسکریپت خواهند داشت؛ اما تا این لحظه این فایلها به صورت DLL دانلود میشوند و در سمت مرورگر "تفسیر" میشوند که باعث میشود سرعت گاه تا 70 برابر کمتر شود.
البته این دو مشکل در زمان ارائه نسخه NET 5. حل خواهند شد و در پروژه نسخه نهایی خود این دو مشکل را نخواهد داشت.
Blazor مدل اجرای دومی نیز دارد که به آن Blazor Server نیز میگوییم. در این مدل کدها تماما سمت سرور اجرا میشوند و تعاملات UI به صورت Web Socket به کلاینت منتقل و یا از آن دریافت میشوند که در این مدل، امکان Debug بدون کوچکترین مشکلی کار میکند و مشکلی از بابت کندی دانلود و اجرای DLLها ندارد. فقط چون کدها تماما سمت سرور اجرا میشوند، در زمانیکه ارتباط شبکهای، مناسب نباشد، میتوانیم در سناریوهای مختلف، شاهد لگ و کندی باشیم.
آیندهی Blazor در مدل اول آن تعریف شدهاست و در گذر زمان برای مدل Blazor Server، نمیتوان آیندهی زیادی را متصور بود. اما نکتهی مهم و جالب اینجاست که کد پروژه در هر دو مدل یکی است و فقط Configuration این دو از هم متفاوت است. پس اگر علاقمند به شروع به استفاده از Blazor هستید، یک راه این است که با مدل Blazor Server شروع و بعدا با تغییر Configuration، به Blazor Client سوئیچ کنید. البته در این میان باید به نکاتی دقت کنید:
۱- Blazor Server از NET Core 3.1. پشتیبانی میکند و Blazor Client از NET Standard 2.1. در مواقعی خاص ممکن است یک کلاس یا یک متد در NET Core 3.1. باشد و در NET Standard 2.1. نباشد.
۲- در Blazor Server شما حتی میتوانید با Entity Framework Core مثلا به Sql Server وصل شوید؛ ولی طبیعتا Browser امکان اتصال مستقیم به Sql Server را در مدل Client به شما نخواهد داد.
این موارد باعث میشود که اگر پروژه را با Blazor Server شروع کنید، شاید در آن کارهایی را انجام دهید که در BlazorServer کار میکنند، ولی در BlazorClient کار نمیکنند و این باعث شود بعدا که نسخه نهایی و کامل BlazorClient آماده شد، شاید نتوانید به آن، به صورت ساده و آسانی سوئیچ کنید.
ایده آل این است که پروژهای داشته باشید که به سادگی عوض کردن یک کلمه، بین این دو مدل سوئیچ کنید و بر صحت عملکرد پروژه در هر دو حالت نظارت داشته باشید. برای رسیدن به این مهم، پروژهای را ساختهام به نام BlazorDualMode که عمدهی بررسیها را به صورت اتوماتیک انجام میدهد و عملا تضمینی بر مهاجرت آسان شما از BlazorServer به BlazorClient در آینده خواهد بود
اگر در نت جستجو کنید، پروژههایی با این اسم را خواهید دید؛ اما این پروژه دو مزیت مهم را دارد که الباقی فاقد آن هستند:
۱- مدل Blazor Client آن دارای تکنیک Pre rendering یا SSR است.
۲- پروژه Api آن، از پروژهی Blazor آن جداست. اگر پروژهی Api را پروژهای بدانیم که به همراه Model، Data و ... امکان دسترسی به DbContext و دیتابیس را دارد، جدا بودن پروژهی Blazor باعث میشود که حتی در مدل Server آن نیز مجبور باشیم دیتا را از Api به صورت Rest Api call بگیریم و به واسطه پروژه Shared مابین Api و Blazor نهایت Dtoها و سایر کدهای مشترک را ببینیم.
البته در پروژه DualMode فقط پروژه Api درست شدهاست و ما کاری با جزئیات آن، اینکه مثلا CQRS میخواهیم یا نه، آیا میخواهیم لایهای کار کنیم و ... نداریم و فقط روی موارد مرتبط با Blazor متمرکز شدهایم.
در پروژه یک فایل داریم به نام Directory.build.props. زمانیکه چنین فایلی در فولدری قرار بگیرد، تمامی موارد نوشته شده در آن به صورت ضمنی در تمامی فایلهای csproj زیر مجموعه آن اعمال میشود.
در این فایل داریم:
<Project> <PropertyGroup> <BlazorMode>Client</BlazorMode> <DefineConstants Condition=" '$(BlazorMode)' == 'Client' ">$(DefineConstants);BlazorClient</DefineConstants> <DefineConstants Condition=" '$(BlazorMode)' == 'Server' ">$(DefineConstants);BlazorServer</DefineConstants> <LangVersion>8.0</LangVersion> </PropertyGroup> </Project>
در ادامه Define Constant کردهایم که اجازه میدهد کدهای CSharp دخیل در Configuration پروژه Blazor را شرطی کنیم. برای مثال بنویسیم:
#if BlazorClient ... #elif BlazorServer ... #endif
نسخه CSharp تمامی پروژهها نیز 8.0 قرار داده شده است.
پروژهی BlazorDualMode.Api آن پروژهای است که Api Controllerها در آن قرار میگیرند و همچنین در حالت Blazor Client، پروژه را برای مرورگرها ارائه یا Serve میکند. به واقع در این مدل، درخواستی که به هیچ Api Controller ای نرسد، به Blazor تحویل میشود. Blazor نیز ابتدا به دنبال Component ای میگردد که برای Route مربوطه نوشته شده باشد و آن را اجرا میکند و سپس Response آماده، به کلاینت ارسال میشود. در ادامه، مرورگر فایلهای DLL را دانلود و برنامه به صورت Single Page App به کار خود ادامه میدهد.
پروژه BlazorDualMode.Shared پروژهای است که کد مشترک بین Api و Blazor در آن قرار میگیرد. برای مثال میتوانید Dtoها را در این قسمت قرار دهید.
پروژه BlazorDualMode.Web پروژهای است که در آن Componentهای Blazor قرار دارند. در حالت Server این پروژه نیز قابلیت اجرا مییابد و باید با امکانات Visual Studio یا IDE دلخواه خود پروژه Web و Api را به صورت همزمان اجرا کنید تا به درستی کار کند.
فایلهای Program.cs، Startup.cs و همچنین خود csprojها و در نهایت فایل Host.cshtml، نهایت تفاوت این دو حالت بوده و کدهای بیزینسی پروژه و حتی Componentها و Api Controllerها در هر دو حالت یکی هستند. Configuration با شرط if server یا if client هندل شدهاند و درک جزئیات تنظیمات مربوطه نیاز به تسلط بر روی خود Blazor را دارد که از موضوع این پست خارج است؛ ولی در صورت داشتن هر گونه سوالی، میتوانید از قسمت پرسش و پاسخ همین سایت استفاده کنید.
در قسمت قبل، روش تعریف قواعد اعتبارسنجی را با استفاده از کتابخانهی Fluent Validation بررسی کردیم. در این قسمت میخواهیم این قواعد را به صورت خودکار به یک برنامهی ASP.NET Core معرفی کرده و سپس از آنها استفاده کنیم.
روش اول: استفادهی دستی از اعتبارسنج کتابخانهی Fluent Validation
روشهای زیادی برای استفادهی از قواعد تعریف شدهی توسط کتابخانهی Fluent Validation وجود دارند. اولین روش، فراخوانی دستی اعتبارسنج، در مکانهای مورد نیاز است. برای اینکار در ابتدا نیاز است با اجرای دستور «dotnet add package FluentValidation.AspNetCore»، این کتابخانه را در پروژهی وب خود نیز نصب کنیم تا بتوانیم از کلاسها و متدهای آن استفاده نمائیم. پس از آن، روش دستی کار با کلاس RegisterModelValidator که در قسمت قبل آنرا تعریف کردیم، به صورت زیر است:
سادهترین روش کار با RegisterModelValidator تعریف شده، ایجاد یک وهلهی جدید از آن و سپس فراخوانی متد Validate آن شیء است. در این حالت میتوان کنترل کاملی را بر روی قالب پیام نهایی بازگشت داده شده داشت. برای مثال در اینجا اولین خطای بازگشت داده شده، به اطلاع کاربر رسیدهاست. حتی میتوان کل شیء Errors را نیز بازگشت داد.
یک نکته: متد الحاقی AddToModelState که در فضای نام FluentValidation.AspNetCore قرار دارد، امکان تبدیل نتیجهی اعتبارسنجی حاصل را به ModelState استاندارد ASP.NET Core نیز میسر میکند:
روش دوم: تزریق اعتبارسنج تعریف شده در سازندهی کنترلر
بجای وهله سازی دستی RegisterModelValidator و ایجاد وابستگی مستقیمی به آن، میتوان از روش تزریق وابستگیهای آن نیز استفاده کرد. در این حالت اعتبارسنج RegisterModelValidator با طول عمر Transient به سیستم تزریق وابستگیها معرفی شده:
و پس از آن با تزریق <IValidator<RegisterModel به سازندهی کنترلر مدنظر، میتوان به امکانات آن همانند روش اول، دسترسی یافت:
به این ترتیب new RegisterModelValidator را با وهلهای از <IValidator<RegisterModel، تعویض کردیم. کار با این روش بسیار انعطاف پذیر بوده و همچنین قابلیت آزمون پذیری بالایی را نیز دارد.
روش سوم: خودکار سازی اجرای یک تک اعتبارسنج تعریف شده
اگر متد الحاقی AddFluentValidation را به صورت زیر به سیستم معرفی کنیم:
سبب اجرای خودکار تمام IValidatorهای اضافه شدهی به سیستم، پیش از اجرای اکشن متد مرتبط با آنها میشود. برای مثال اگر اکشن متدی دارای پارامتری از نوع RegisterModel بود، چون IValidator مخصوص به آن به سیستم تزریق وابستگیها معرفی شدهاست، متد الحاقی AddFluentValidation، کار وهله سازی خودکار این IValidator و سپس فراخوانی متد Validate آنرا به صورت خودکار انجام میدهد. به این ترتیب، قطعه کدهایی را که تاکنون نوشتیم، به صورت زیر خلاصه خواهند شد که در آنها اثری از بکارگیری کتابخانهی FluentValidation مشاهده نمیشود:
زمانیکه model به سمت اکشن متد فوق ارسال میشود، زیرساخت model-binding موجود در ASP.NET Core، اینبار کار اعتبارسنجی آنرا توسط RegisterModelValidator به صورت خودکار انجام داده و نتیجهی آنرا به ModelState اضافه میکند که برای مثال در اینجا سبب رندر مجدد فرم شده که تمام مباحث tag-helperهای استانداردی مانند asp-validation-summary و asp-validation-for پس از آن به صورت متداولی و همانند قبل، قابل استفاده خواهند بود.
نکته 1: تنظیمات فوق برایASP.NET Web Pages و PageModels نیز یکی است. فقط با این تفاوت که اعتبارسنجها را فقط میتوان به مدلهایی که به صورت خواص یک page model تعریف شدهاند، اعمال کرد و نه به کل page model.
نکته 2: اگر کنترلر شما به ویژگی [ApiController] مزین شده باشد:
در این حالت دیگر نیازی به ذکر if (!ModelState.IsValid) نیست و خطای حاصل از شکست اعتبارسنجی، به صورت خودکار توسط FluentValidation تشکیل شده و بازگشت داده میشود (پیش از رسیدن به بدنهی اکشن متد فوق) و برای نمونه یک چنین شکل و خروجی خودکاری را پیدا میکند:
اگر علاقمند به سفارشی سازی این خروجی خودکار هستید، باید به این صورت با تنظیم ApiBehaviorOptions و مقدار دهی نحوهی تشکیل ModelState نهایی، عمل کرد:
روش چهارم: خودکار سازی ثبت و اجرای تمام اعتبارسنجهای تعریف شده
و در آخر بجای معرفی دستی تک تک اعتبارسنجهای تعریف شده به سیستم تزریق وابستگیها، میتوان تمام آنها را با فراخوانی متد RegisterValidatorsFromAssemblyContaining، به صورت خودکار از یک اسمبلی خاص استخراج نمود و با طول عمر Transient، به سیستم معرفی کرد. در این حالت متد ConfigureServices به صورت زیر خلاصه میشود:
در اینجا امکان استفادهی از متد fv.RegisterValidatorsFromAssembly نیز برای معرفی اسمبلی خاصی مانند ()Assembly.GetExecutingAssembly نیز وجود دارد.
سازگاری اجرای خودکار FluentValidation با اعتبارسنجهای استاندارد ASP.NET Core
به صورت پیشفرض، زمانیکه FluentValidation اجرا میشود، اگر اعتبارسنج دیگری نیز در سیستم تعریف شده باشد، اجرا خواهد شد. به این معنا که برای مثال میتوان FluentValidation و DataAnnotations attributes و IValidatableObjectها را با هم ترکیب کرد.
اگر میخواهید این قابلیت را غیرفعال کنید و فقط سبب اجرای خودکار FluentValidationها شوید، نیاز است تنظیم زیر را انجام دهید:
روش اول: استفادهی دستی از اعتبارسنج کتابخانهی Fluent Validation
روشهای زیادی برای استفادهی از قواعد تعریف شدهی توسط کتابخانهی Fluent Validation وجود دارند. اولین روش، فراخوانی دستی اعتبارسنج، در مکانهای مورد نیاز است. برای اینکار در ابتدا نیاز است با اجرای دستور «dotnet add package FluentValidation.AspNetCore»، این کتابخانه را در پروژهی وب خود نیز نصب کنیم تا بتوانیم از کلاسها و متدهای آن استفاده نمائیم. پس از آن، روش دستی کار با کلاس RegisterModelValidator که در قسمت قبل آنرا تعریف کردیم، به صورت زیر است:
using FluentValidationSample.Models; using Microsoft.AspNetCore.Mvc; namespace FluentValidationSample.Web.Controllers { public class HomeController : Controller { public IActionResult Index() { return View(); } [HttpPost] public IActionResult RegisterValidateManually(RegisterModel model) { var validator = new RegisterModelValidator(); var validationResult = validator.Validate(model); if (!validationResult.IsValid) { return BadRequest(validationResult.Errors[0].ErrorMessage); } // TODO: Save the model return Ok(); } } }
یک نکته: متد الحاقی AddToModelState که در فضای نام FluentValidation.AspNetCore قرار دارد، امکان تبدیل نتیجهی اعتبارسنجی حاصل را به ModelState استاندارد ASP.NET Core نیز میسر میکند:
public IActionResult RegisterValidateManually(RegisterModel model) { var validator = new RegisterModelValidator(); var validationResult = validator.Validate(model); if (!validationResult.IsValid) { validationResult.AddToModelState(ModelState, null); return BadRequest(ModelState); } // TODO: Save the model return Ok(); }
روش دوم: تزریق اعتبارسنج تعریف شده در سازندهی کنترلر
بجای وهله سازی دستی RegisterModelValidator و ایجاد وابستگی مستقیمی به آن، میتوان از روش تزریق وابستگیهای آن نیز استفاده کرد. در این حالت اعتبارسنج RegisterModelValidator با طول عمر Transient به سیستم تزریق وابستگیها معرفی شده:
namespace FluentValidationSample.Web { public class Startup { public void ConfigureServices(IServiceCollection services) { services.AddTransient<IValidator<RegisterModel>, RegisterModelValidator>(); services.AddControllersWithViews(); }
namespace FluentValidationSample.Web.Controllers { public class HomeController : Controller { private readonly IValidator<RegisterModel> _registerModelValidator; public HomeController(IValidator<RegisterModel> registerModelValidator) { _registerModelValidator = registerModelValidator; } [HttpPost] public IActionResult RegisterValidatorInjection(RegisterModel model) { var validationResult = _registerModelValidator.Validate(model); if (!validationResult.IsValid) { return BadRequest(validationResult.Errors[0].ErrorMessage); } // TODO: Save the model return Ok(); } } }
روش سوم: خودکار سازی اجرای یک تک اعتبارسنج تعریف شده
اگر متد الحاقی AddFluentValidation را به صورت زیر به سیستم معرفی کنیم:
namespace FluentValidationSample.Web { public class Startup { public void ConfigureServices(IServiceCollection services) { services.AddTransient<IValidator<RegisterModel>, RegisterModelValidator>(); services.AddControllersWithViews().AddFluentValidation(); }
namespace FluentValidationSample.Web.Controllers { public class HomeController : Controller { [HttpPost] public IActionResult RegisterValidatorAutomatically(RegisterModel model) { if (!ModelState.IsValid) { // re-render the view when validation failed. return View(model); } // TODO: Save the model return Ok(); } } }
نکته 1: تنظیمات فوق برایASP.NET Web Pages و PageModels نیز یکی است. فقط با این تفاوت که اعتبارسنجها را فقط میتوان به مدلهایی که به صورت خواص یک page model تعریف شدهاند، اعمال کرد و نه به کل page model.
نکته 2: اگر کنترلر شما به ویژگی [ApiController] مزین شده باشد:
namespace FluentValidationSample.Web.Controllers { [Route("[controller]")] [ApiController] public class HomeController : Controller { [HttpPost] public IActionResult RegisterValidatorAutomatically(RegisterModel model) { // TODO: Save the model return Ok(); } } }
{ "type": "https://tools.ietf.org/html/rfc7231#section-6.5.1", "title": "One or more validation errors occurred.", "status": 400, "traceId": "|84df05e2-41e0d4841bb61293.", "errors": { "FirstName": [ "'First Name' must not be empty." ] } }
public void ConfigureServices(IServiceCollection services) { // ... // override modelstate services.Configure<ApiBehaviorOptions>(options => { options.InvalidModelStateResponseFactory = context => { var errors = context.ModelState.Values.SelectMany(x => x.Errors.Select(p => p.ErrorMessage)).ToList(); return new BadRequestObjectResult(new { Code = "00009", Message = "Validation errors", Errors = errors }); }; }); }
روش چهارم: خودکار سازی ثبت و اجرای تمام اعتبارسنجهای تعریف شده
و در آخر بجای معرفی دستی تک تک اعتبارسنجهای تعریف شده به سیستم تزریق وابستگیها، میتوان تمام آنها را با فراخوانی متد RegisterValidatorsFromAssemblyContaining، به صورت خودکار از یک اسمبلی خاص استخراج نمود و با طول عمر Transient، به سیستم معرفی کرد. در این حالت متد ConfigureServices به صورت زیر خلاصه میشود:
namespace FluentValidationSample.Web { public class Startup { public void ConfigureServices(IServiceCollection services) { services.AddControllersWithViews().AddFluentValidation( fv => fv.RegisterValidatorsFromAssemblyContaining<RegisterModelValidator>() ); }
سازگاری اجرای خودکار FluentValidation با اعتبارسنجهای استاندارد ASP.NET Core
به صورت پیشفرض، زمانیکه FluentValidation اجرا میشود، اگر اعتبارسنج دیگری نیز در سیستم تعریف شده باشد، اجرا خواهد شد. به این معنا که برای مثال میتوان FluentValidation و DataAnnotations attributes و IValidatableObjectها را با هم ترکیب کرد.
اگر میخواهید این قابلیت را غیرفعال کنید و فقط سبب اجرای خودکار FluentValidationها شوید، نیاز است تنظیم زیر را انجام دهید:
public void ConfigureServices(IServiceCollection services) { services.AddControllersWithViews().AddFluentValidation( fv => { fv.RegisterValidatorsFromAssemblyContaining<RegisterModelValidator>(); fv.RunDefaultMvcValidationAfterFluentValidationExecutes = false; } ); }
Custom Elements، دارای یک چرخه حیات میباشند. در طی این چرخه حیات، میتوان تعدادی متد خاص را به المان سفارشی خود اضافه کرد که به صورت خودکار توسط مرورگر فراخوانی میشوند. به این متدها Life-cycle Callbacks یا Custom Element Reactions نیز میگویند. برای درک بهتر چرخه حیات مذکور، به تکه کد زیر توجه نمائید:
در این صورت، امکان استفاده از المان سفارشی، قبل از معرفی و ثبت آن توسط متد customElements.define نیز وجود خواهد داشت. یعنی اگر در DOM شما تعدادی المان سفارشی وجود داشته باشند که به هر دلیل نیاز است پس از گذشت یک بازه زمانی کوتاهی معرفی و ثبت شوند (مثال: lazy load اسکریپتهای متناظر با المانهای سفارشی در Angular)، این المانها معتبر هستند. فرآیند فراخوانی متد define و استفاده از کلاس معرفی شده برای ارتقاء المان سفارشی موجود در DOM، اصطلاحا Element Upgrades نامیده میشود. همچنین با استفاده از متد customElements.whenDefined که یک Promise را بازگشت میدهد، میتوان از معرفی و ثبت شدن المان خاصی آگاه شد:
یا حتی امکان استفاده از سلکتور «:defined» نیز به شکل زیر وجود دارد:
در اینجا ابتدا تمام المانهای تعریف نشده، کوئری شدهاند و با استفاده از متد map و اجرای متد whenDefined، به لیستی از Promiseها رسیدهایم و در نهایت با استفاده از Promise.all منتظر اتمام مرحله upgrade المانهای مذکور هستیم.
متد get، ارجاعی به سازنده کلاس x-component را بازگشت خواهد داد.
customElements.define("x-component", class extends HTMLElement { constructor() { super(); console.log('constructed!'); } connectedCallback() { console.log('connected!'); } disconnectedCallback() { console.log('disconnected!'); } adoptedCallback() { console.log('adopted!'); } attributeChangedCallback(name, oldValue, newValue) { console.log('attirbuteChanged!', name, oldValue, newValue); } static get observedAttributes() { return ['checked','demo','label']; } });
Element Upgrades
به صورت پیشفرض، المانهای موجود در DOM که مبتنیبر استانداردهای HTML تعریف نشدهاند، توسط مرورگر به عنوان HTMLUnknownElement تجزیه و تحلیل خواهند شد. ولی این موضوع برای المانهای سفارشی که نام معتبری دارند (وجود «-»)، صدق نمیکند. برای مثال دو خط کد زیر را در کنسول مربوط به Developer tools مرورگر خود اجرا کنید:
// "tabs" is not a valid custom element name document.createElement('tabs') instanceof HTMLUnknownElement === true //true // "x-tabs" is a valid custom element name document.createElement('x-tabs') instanceof HTMLElement === true //true
customElements.whenDefined('x-component').then(() => { console.log('x-component defined'); });
<share-buttons> <social-button type="twitter"><a href="...">Twitter</a></social-button> <social-button type="fb"><a href="...">Facebook</a></social-button> <social-button type="plus"><a href="...">G+</a></social-button> </share-buttons> // Fetch all the children of <share-buttons> that are not defined yet. let undefinedButtons = buttons.querySelectorAll(':not(:defined)'); let promises = [...undefinedButtons].map(socialButton => { return customElements.whenDefined(socialButton.localName); )); // Wait for all the social-buttons to be upgraded. Promise.all(promises).then(() => { // All social-button children are ready. });
سازنده مرتبط با کلاس المان سفارشی x-component، در هنگام وهلهسازی یا فرآیند upgrades فراخوانی میشود و میتواند برای مقداردهی اولیه خواص وهله جاری، تنظیم رخدادگردانها (Event Listeners) و یا ایجاد و اتصال ShadowDOM با استفاده از متد attachShadow، محل مناسبی باشد. طبق مستندات مرتبط، فراخوانی ()super بدون ارسال هیچ آرگومانی باید در اولین خط این سازنده انجام شود.
برای وهلهسازی المان سفارشی نیز میتوان از متد customeElements.get به شکل زیر استفاده کرد:
customeElements.define('x-component',...) let XComponent = customElements.get('x-component'); document.body.appendChild(new XComponent())
در تکه کد بالا، لیست المانهای img موجود در داخل iframe کوئری شده و سپس با پیمایش بر روی لیست بدست آمده و در زمان فراخوانی متد adoptNode که کار تغییر ownerDocument مرتبط با یک المان را انجام میدهد، متد adoptedCallback ما نیز اجرا خواهد شد.
مشخص است که امکان تعریف انواع و اقسام متدها با پارامترها و خروجیهای مختلفی و حتی نسخههای همزمان یا ناهمزمان آنها وجود دارد.
انتظار چنین خروجی داریم:
این موضوع، تحت عنوان «Reflecting Properties to Attributes» مطرح میباشد. در این صورت علاوه بر اینکه با یک نگاه به DOM، از مقادیر خصوصیات یک المان آگاه خواهیم بود، امکان استفاده از این صفات به عنوان سلکتورهایی در زمان استایلدهی نیز وجود دارد. حال از این مکانیزم برای اعمال یکسری صفات دسترسیپذیری مانند صفات ARIA به المان سفارشی خود نیز میتوان استفاده کرد. برای مثال:
یا حتی به شکل زیر:
در اینجا از همان getter که طبق پیادهسازی ما، در پشت صحنه از همان مقدار صفت disabled استفاده میکند، برای تنظیم یکسری صفات دیگر استفاده کردهایم. به عنوان مثال اگر المان ما غیرفعال شده بود، صفت tabindex آن را با «-1» مقداردهی میکنیم تا از توالی پیمایش مبتنیبر Tab خارج شود.
در این صورت اگر لود اسکریپت، معرفی و ثبت این المان سفارشی به صورت Lazy انجام شود، امکان آن وجود دارد که فریمورک، عملیات binding را قبل از مرحله upgrades انجام دهد. خوب... این موضوع چه مشکلی را ایجاد میکند؟ در این صورت چون مرحله upgrades تمام نشده است، پیادهسازی بدنه متد setter متناظر با خصوصیات المان سفارشی، توسط پراپرتی جدیدی که توسط فریمورک برروی وهله موجود تعریف میشود، بیاستفاده خواهد ماند. برای مثال، اگر سعی کنیم قبل از مرحله upgrades خصوصیت disabled المان x-component را مقداردهی کنیم، عملیات مکانیزم همگامسازی مدنظر ما اجرا نخواهد شد:
با این خروجی مواجه خواهیم شد که هیچ اثری از صفت disabled دیده نمیشود:
connectedCallback
اولین متد بعد از فراخوانی سازنده، connectedCallback نام دارد و زمانی رخ میدهد که وهلهای از یک المان سفارشی به Light DOM افزوده شدهاست و یا توسط Parser مرورگر، در DOM شناسایی شود. این متد، محل پیشنهاد شده برای اجرای کدهای زمان راهاندازی مانند دریافت منابع از سرور و یا رندر کردن محتوایی خاص، میباشد.
نکته: این متد ممکن است بیش از یکبار نیز فراخوانی شود! هنگامیکه یک المان موجود در DOM از طریق کد از DOM جداشده و سپس اضافه شود:
const el = document.createElement('x-component'); document.body.appendChild(el); // connectedCallback() called el.remove(); // disconnectedCallback() document.body.appendChild(el); // connectedCallback() called again
disconnectedCallback
این متد نیز هر وقت المانی از DOM حذف شود، اجرا خواهد شد و مانند متد connectedCallback ممکن است چندین بار فراخوانی شود. همچنین محل مناسبی برای آزادسازی منابع استفاده شده در پیادهسازی المان سفارشی، میباشد. مانند: استفاده از متد clearInterval برای پاکسازی یک تایمر که با متد setInterval ایجاد شدهاست.
نکته: هیچ تعهدی به اجرای متد disconnectedCallback در تمام حالاتی که یک المان از DOM حذف میشود، وجود ندارد. به عنوان مثال هنگامیکه یک برگهی مرورگر بسته شود، این متد فراخوانی نخواهد شد.
attributeChangedCallback
این متد هنگامی فراخوانی خواهد شد که خصوصیات مشخص شده از طریق observedAttributes به عنوان یک static getter، اضافه، حذف، ویرایش و یا جایگزین شوند. همچنین در مرحله Upgrades برای مقادیر اولیه خصوصیات که توسط استفاده کننده از المان سفارشی، مشخص شدهاست نیز فراخوانی میشود.
adoptedCallback
متدهای قبلی بیشترین استفاده را دارند؛ این متد خاص نیز زمانیکه یک المان سفارشی به یک DOM دیگری منتقل میشود، اجرا خواهد شد. برای مثال:
const iframe = document.querySelector('iframe'); const iframeImages = iframe.contentDocument.querySelectorAll('img'); const newParent = document.getElementById('images'); iframeImages.forEach(function(imgEl) { newParent.appendChild(document.adoptNode(imgEl)); });
Methods
اگر المانهای بومی و استاندارد موجود را بررسی کنید، همه آنها دارای یکسری متد، پراپرتی و صفات مشخصی هستند. در اینجا نیز تعریف رفتاری برای یک المان سفارشی و کپسوله، نکته خاصی ندارد و به شکل زیر قابل تعریف و استفاده میباشد:
class XComponent extends HTMLElement { constructor() { super(); } doSomething(){ console.log('doSomething'); } }
let component = document.querySelector('x-component'); component.doSomething();
Attributes & Properties
در HTML خیلی رایج است که مقادیر پراپرتیهای یک المان در قالب یکسری صفات، نمودی در DOM هم داشته باشند. برای مثال:
div.id = 'id-value'; div.hidden = true;
<div id="id-value" hidden>
class XComponent extends HTMLElement { constructor() { super(); } connectedCallback() { this._render(); } get disabled() { return this.hasAttribute('disabled'); } set disabled(val) { // Reflect the value of `disabled` as an attribute. if (val) { this.setAttribute('disabled', ''); } else { this.removeAttribute('disabled'); } this._render(); } _render() { //... } }
ایده کار خیلی ساده است؛ پراپرتیهای یک المانسفارشی را از طریق متدهای getter و setter تعریف کرده و در بدنه پیادهسازی آنها، صفات HTML ای المان جاری را تغییر داده و یا از مقادیر آنها استفاده کنیم.
اینبار با مقداردهی پراپرتی disabled برروی وهلهای از المان سفارشی ما، این مقادیر نمودی در DOM هم خواهند داشت. با استفاده از متدهای setAttribute یا removeAttribute کار همگامسازی پراپرتیها با صفات را انجام دادهایم.همچین با استفاده از متد attributeChangedCallback نیز میتوان برای اعمال صفات ARIA که اشاره شد، به شکل زیر استفاده کرد:
attributeChangedCallback(name, oldValue, newValue) { switch (name) { case 'checked': // Note the attributeChangedCallback is only handling the *side effects* // of setting the attribute. this.setAttribute('aria-disabled', !!newValue); break; ... }
attributeChangedCallback(name, oldValue, newValue) { // When the component is disabled, update keyboard/screen reader behavior. if (this.disabled) { this.setAttribute('tabindex', '-1'); this.setAttribute('aria-disabled', 'true'); } else { this.setAttribute('tabindex', '0'); this.setAttribute('aria-disabled', 'false'); } // TODO: also react to the other attribute changing. }
نکته: پیشنهاد میشود از مکانیزم همگامسازی پراپرتیها و صفات، برای انواع دادهای اولیه (رشته، عدد و ...) استفاده شود و برای دریافت مقادیری مانند objects و یا arrays، از متدها یا پراپرتیها استفاده کنید. در غیر این صورت نیاز خواهد بود که این مقادیر را سریالایز و در داخل المان سفارشی، عملیات معکوس آن را انجام دهید که میتواند هزینهی زیادی داشته باشد. عملیات سریالایز نیز خود باعث از دست دادن ارجاعات به آن مقادیر خواهد شد. به صورت کلی هیچکدام از المانهای بومی موجود، چنین اطلاعاتی را دریافت نمیکند. برای مثال:
constructor() { super(); this._data = []; } get data() { return _data; } set data(value) { if (this_data === value) return; this._data = value; this._render(); }
Lazy Properties
همانطور که اشاره شد حتی قبل از مرحله upgrades مربوط به المانهای سفارشی استفاده شده در سند HTML برنامه شما، به عنوان المانهای معتبری هستند که امکان کوئری کردن و مقداردهی اولیه خصوصیات آنها از طریق کد نیز ممکن است. این موضوع زمانیکه از فریمورکی مثل Angular استفاده میشود، المان موردنظر به صورت خودکار توسط فریمورک لود و به صفحه اضافه شده و در انتهای عملیات، binding پراپرتیهای آن به خصوصیات موجود در کامپوننت Angular ای انجام خواهد شد. به مثال زیر توجه کنید:
<x-component [disabled]="model.disabled"></x-component>
let el = document.querySelector('x-component'); el.disabled = true; customElements.define("x-component", class extends HTMLElement { constructor() { super(); } get disabled() { return this.hasAttribute('disabled'); } set disabled(val) { // Reflect the value of `disabled` as an attribute. if (val) { this.setAttribute('disabled', ''); } else { this.removeAttribute('disabled'); } this._render(); } });
یکی از روشهای پیشنهاد شده برای حل این مشکل، مقداردهی مجدد پراپرتیها بعد از مرحله upgrades و پس از اینکه متد setter تعریف شدهاست، میباشد:
let el = document.querySelector('x-component'); el.disabled = true; customElements.define("x-component", class extends HTMLElement { constructor() { super(); } connectedCallback() { this._upgradeProp('disabled'); } get disabled() { return this.hasAttribute('disabled'); } set disabled(val) { // Reflect the value of `disabled` as an attribute. if (val) { this.setAttribute('disabled', ''); } else { this.removeAttribute('disabled'); } this._render(); } _upgradeProp(prop) { if (this.hasOwnProperty(prop)) { let value = this[prop]; delete this[prop]; //delete instance property this[prop] = value; // set prototype property } } });
ایده کار به این صورت است که مقدار پراپرتی مورد نظر را که قبل از مرحله upgrades برروی وهله جاری (instance property) تنظیم شدهاست، در متغییری نگهداری کرده و آن پراپرتی را حذف و سپس پراپرتی تعریف شده در کلاس (prototype property) را برای وهله جاری مقداردهی کنیم.
نکته: به یاد داشته باشید که قبل از اینکه یکسری صفات خاص را مقدار دهی کنید، بررسی شود که استفاده کننده از المان سفارشی، مقداری را تنظیم نکرده باشد. برای مثال اگر قصد دارید المان سفارشی شما قابلیت focus را داشته باشد، نیاز است شما حداقل tabindex=-1 را تنظیم کنید؛ حتی اگر استفاده کننده، آن را مقداردهی نکرده باشد:
connectedCallback() { if (!this.hasAttribute('role')) this.setAttribute('role', 'checkbox'); if (!this.hasAttribute('tabindex')) this.setAttribute('tabindex', -1); //element is not reachable via sequential keyboard navigation, but could be focused }
نظرات مطالب
ASP.NET MVC #21
با درود؛ من با استفاده از متد jQuery.Ajax و درخواست از یک کنترلر برای نمایش اطلاعات از دیتابیس به روش زیر عمل کردم
و کنترلر مربوط
و سوال اینکه وقتی از Return View استفاده کردم هیچ رکوردی بازگردانده نشد و با یک صفحه سفید مواجه شدم و باید حتما از Return Json استفاده کنم تا اطلاعات درخواستی نمایش داده بشه؟ آیا حتما باید از Return Json استفاده کرد ؟ و یا در کد نویسی من جایی اشکال هست ؟
<script type="text/javascript"> $(function () { getData(); }); function getData() { var $tbl = $('#tblEmployee'); $.ajax({ url: 'Home/EmployeeInfoData', type: 'Post', datatype: 'json', success: function (data) { if (data.length > 0) { $tbl.empty(); $tbl.append(' <tr><th>ID</th><th>Name</th><th>Family</th></tr>'); var rows = []; for (var i = 0; i < data.length; i++) { rows.push(' <tr><td>' + data[i].Id + '</td><td>' + data[i].Name + '</td><td>' + data[i].Family + '</td></tr>'); } $tbl.append(rows.join('')); } } }); } </script>
و کنترلر مربوط
[HttpPost] public ActionResult EmployeeInfoData() { InfoEmployee mp = new InfoEmployee(); var names = mp.GetData(); return Json(names); }