«... در اولین قسمت از این مجموعه در مورد تعریف کلی و مزایا و معایب میکروسرویسها صحبت کردیم. در دومین قسمت به سراغ API Gatewayها، نقش آنها در توسعه میکروسرویس و ویژگیها یک API Gateway صحبت کردیم. سپس در قسمت سوم در مورد ارتباط بین سرویسها و انواع روشهای برقراری ارتباط صحبت کردیم و نهایتا در چهارمین قسمت در مورد تکنولوژیهای توسعه میکروسرویسها صحبت کردیم. سپس در پنجمین قسمت از این مجموعه در مورد Service Discovery مطالبی را بررسی کردیم. در نهایت در ششمین قسمت از این مجموعه در مورد دادهها و مدیریت گردش اطلاعات در میکروسرویسها مطالبی را بیان کردیم. حالا در این قسمت میخواهیم در مورد نحوه انتشار و نصب میکروسرویسها مطالبی را بررسی کنیم ...»
در قسمت قبلی
شما را با DataTables آشنا کردم. به طور خلاصه نحوه اعمال کردن
DataTables به یک جدول ساده html را گفتیم که با این کار به صورت پیش
فرض، امکاناتی مثل فیلتر کردن داده ها، صفحه بندی و مرتب سازی آنها و نیز
اعمال شدن استایلهای css به همین جدول html خام اضافه میشود. نکته مهم در
مثال قبلی این بود که دادههای درون این جدول با کدنویسی خام html فراهم
شدند، اما این را در نظر داشته باشید که اکثریت مواقع باید دادهها از یک
بانک اطلاعاتی دریافت شوند و سپس درون جدول قرار بگیرند.
در این قسمت سعی خواهیم کرد تا منبع داده جدول را یک آرایه جاوا اسکریپتی و سپس کالکشنی از آبجکتهای جاوا اسکریپتی (json) در نظر بگیریم و نیز برخی ویژگیهای پیش فرض پلاگین را غیر فعال نمائیم.
فرض کنید میخواهید لیستی از اطلاعات دانشجویان شامل نام (FirstName)، نام خانوادگی (LastName)، و سن (Age) را نمایش دهید. اطلاعات قرار است در جدول زیر قرار بگیرند:
مشاهده میکنید که این جدول فقط شامل قسمت header است و در بدنه آن هیچ سطری قرار نگرفته است. در این مثال اطلاعات از یک آرایه جاوا اسکریپتی باید خوانده شوند و تبدیل به html شده و در نهایت درون قسمت <tbody></tbody> آن تزریق شوند. خوشبختانه DataTables برای این کار امکانات آماده ای را در اختیار قرار میدهد. این کار بدین صورت قابل انجام است:
شرح کد:
aaData : یک آرایه دو بعدی (که به آن ماتریس یا آرایه ای از آرایهها هم گفته میشود) است که مقادیر سلول هایی را نشان میدهد که در جدول قرار خواهند گرفت. تعداد ستونها در این آرایه دو بعدی باید با تعداد ستونهای جدول html متناظر یکسان باشند.
در مثال بالا از یک ماتریس به عنوان منبع داده استفاده شد. منبع داده میتواند به فرمت json نیز باشد. البته در این صورت باید ستونهای جدول html را هم به پلاگین معرفی کنید، بدین صورت:
aaData : همان طور که گفته شد در این قسمت دیتاهای درون جدول آورده میشوند و در این مثال آنها به فرمت json نوشته شده اند.
aoColumns : در این قسمت باید اسم ستونهای جدول ذکر شوند.
غیرفعال کردن بعضی از ویژگیهای پیش فرض DataTables
همان طور که گفته شد پلاگین DataTables به صورت پیش فرض ویژگیهای مرتب سازی (sorting)، صفحه بندی (paging)، فیلتر کردن دادهها (filtering)، و غیره را به جدول مورد نظرش اعمال میکند. و بدین صورت قابل تغییر است:
bPaginate : بیان میکند آیا صفحه بندی سطرهای جدول فعال باشد یا نه.
bLengthChange : در صورتی که قابلیت صفحه بندی فعال باشد ، بیان میکند که کاربر بتواند اندازه صفحه را تغییر دهد یا نه.
bFilter : بیان میکند آیا قابلیت فیلتر کردن دادهها فعال باشد یا نه.
bSort : بیان میکند قابلیت مرتب سازی دادههای جدول فعال باشد یا نه.
bInfo : بیان میکند که قسمت info زیر گرید نشان داده شود یا نه (در این قسمت اطلاعاتی راجع به تعداد کل رکوردهای بایند شده به جدول و نیز رکوردهای درون صفحه جاری نشان داده میشود)
bAutoWidth : در صورتی که این گزینه فعال باشد اندازه عرض هر ستون به صوتر خودکار توسط DataTables مقدار دهی خواهد شد.
مقدارهای قابل قبول برای هر کدام از این خصوصیات : true یا false
کدهای مربوط به این مثال را میتوانید از لینک زیر دریافت کنید:
DataTables-DoteNetTips-Tutorial-02.zip
در این قسمت سعی خواهیم کرد تا منبع داده جدول را یک آرایه جاوا اسکریپتی و سپس کالکشنی از آبجکتهای جاوا اسکریپتی (json) در نظر بگیریم و نیز برخی ویژگیهای پیش فرض پلاگین را غیر فعال نمائیم.
فرض کنید میخواهید لیستی از اطلاعات دانشجویان شامل نام (FirstName)، نام خانوادگی (LastName)، و سن (Age) را نمایش دهید. اطلاعات قرار است در جدول زیر قرار بگیرند:
<table id="std-grid"> <thead> <th>نام</th> <th>نام خانوادگی</th> <th>سن</th> </thead> <tbody> </tbody> </table>
مشاهده میکنید که این جدول فقط شامل قسمت header است و در بدنه آن هیچ سطری قرار نگرفته است. در این مثال اطلاعات از یک آرایه جاوا اسکریپتی باید خوانده شوند و تبدیل به html شده و در نهایت درون قسمت <tbody></tbody> آن تزریق شوند. خوشبختانه DataTables برای این کار امکانات آماده ای را در اختیار قرار میدهد. این کار بدین صورت قابل انجام است:
<script> $(document).ready(function() { $('#std-grid').dataTable({ "aaData": [ ["پژمان", "پارسائی", "24"], ["سعید", "الیاسی", "25"], ["محمد رضا", "گلزار", "20"], ["آرش", "ایرانی", "19"], ["مرتضی", "فرمانی", "22"], ["سعید", "حمیدیان", "23"], ["امین", "پارسانیا", "23"], ["محمد امین", "فقیهی", "24"], ["محمد", "خرمی", "25"], ["سینا", "امیریان", "20"], ["آرش", "ایرانی", "19"], ["وحید", "فرزانه", "22"], ["امیر علی", "فرمانی", "23"], ["امین", "حسینی", "23"], }); }); </script>
aaData : یک آرایه دو بعدی (که به آن ماتریس یا آرایه ای از آرایهها هم گفته میشود) است که مقادیر سلول هایی را نشان میدهد که در جدول قرار خواهند گرفت. تعداد ستونها در این آرایه دو بعدی باید با تعداد ستونهای جدول html متناظر یکسان باشند.
در مثال بالا از یک ماتریس به عنوان منبع داده استفاده شد. منبع داده میتواند به فرمت json نیز باشد. البته در این صورت باید ستونهای جدول html را هم به پلاگین معرفی کنید، بدین صورت:
$(document).ready(function() { $('#std-grid').dataTable({ "aaData": [ {"FirstName" : "پژمان", "LastName" : "پارسائی", "Age" : "24"}, { "FirstName": "سعید", "LastName": "الیاسی", "Age": "25" }, { "FirstName": "محمد رضا", "LastName": "گلزار", "Age": "24" }, { "FirstName": "آرش", "LastName": "ایرانی", "Age": "24" }, { "FirstName": "مرتضی", "LastName": "فرمانی", "Age": "24" }, { "FirstName": "سعید", "LastName": "حمیدیان", "Age": "24" }, { "FirstName": "امین", "LastName": "پارسانیا", "Age": "24" }, { "FirstName": "محمد امین", "LastName": "فقیهی", "Age": "24" }, { "FirstName": "محمد", "LastName": "خرمی", "Age": "24" }, { "FirstName": "سینا", "LastName": "امیریان", "Age": "24" }, { "FirstName": "آرش", "LastName": "ایرانی", "Age": "24" }, { "FirstName": "وحید", "LastName": "فرزانه", "Age": "24" }, { "FirstName": "امیر علی", "LastName": "فرمانی", "Age": "24" }, { "FirstName": "امین", "LastName": "حسینی", "Age": "24" }, ], "aoColumns": [ { "mDataProp": "FirstName" }, { "mDataProp": "LastName" }, { "mDataProp": "Age" } ] }); });
aoColumns : در این قسمت باید اسم ستونهای جدول ذکر شوند.
غیرفعال کردن بعضی از ویژگیهای پیش فرض DataTables
همان طور که گفته شد پلاگین DataTables به صورت پیش فرض ویژگیهای مرتب سازی (sorting)، صفحه بندی (paging)، فیلتر کردن دادهها (filtering)، و غیره را به جدول مورد نظرش اعمال میکند. و بدین صورت قابل تغییر است:
$('#std-grid').dataTable({ "bPaginate": false, "bLengthChange": false, "bFilter": false, "bSort": false, "bInfo": true, "bAutoWidth": false });
bLengthChange : در صورتی که قابلیت صفحه بندی فعال باشد ، بیان میکند که کاربر بتواند اندازه صفحه را تغییر دهد یا نه.
bFilter : بیان میکند آیا قابلیت فیلتر کردن دادهها فعال باشد یا نه.
bSort : بیان میکند قابلیت مرتب سازی دادههای جدول فعال باشد یا نه.
bInfo : بیان میکند که قسمت info زیر گرید نشان داده شود یا نه (در این قسمت اطلاعاتی راجع به تعداد کل رکوردهای بایند شده به جدول و نیز رکوردهای درون صفحه جاری نشان داده میشود)
bAutoWidth : در صورتی که این گزینه فعال باشد اندازه عرض هر ستون به صوتر خودکار توسط DataTables مقدار دهی خواهد شد.
مقدارهای قابل قبول برای هر کدام از این خصوصیات : true یا false
کدهای مربوط به این مثال را میتوانید از لینک زیر دریافت کنید:
DataTables-DoteNetTips-Tutorial-02.zip
در قسمت قبل، با useState Hook آشنا شدیم. همچنین چندین مثال را در مورد نحوهی تعریف تکی و یا چندتایی آن در یک کامپوننت تابعی، با انواع و اقسام دادههای مختلف، بررسی کردیم؛ اما بهتر است از کدام حالت استفاده شود؟ آیا بهتر است به ازای هر خاصیت state، یکبار useState Hook جدیدی را تعریف کنیم و یا بهتر است همانند کامپوننتهای کلاسی، یک شیء کامل را به همراه چندین خاصیت، به یک تک useState Hook معرفی کنیم؟
پیاده سازی یک فرم لاگین با استفاده از چندین useState Hook
در ابتدا، یک مثال کاربردیتر را به کمک useState Hookها پیاده سازی میکنیم. در اینجا هر المان فرم را به یک useState Hook مجزا، متصل کردهایم. کدهای کامل این کامپوننت را در ادامه مشاهده میکنید:
توضیحات:
- اگر دقت کرده باشید، اینبار این کامپوننت تابعی را به صورت متداول ()function Login تعریف کردهایم. مزیت یک چنین تعریفی، امکان export در محل آن میباشد:
و دیگر برخلاف حالت استفادهی از arrow functionها برای تعریف کامپوننتهای تابعی، نیازی نیست تا این export را جداگانه در این ماژول درج کرد.
به علاوه وجود واژهی default در اینجا سبب میشود که برای import آن، بتوان از هر نام دلخواهی استفاده کرد و در اینجا اجباری به استفادهی از نام Login وجود ندارد که نمونهی استفادهی از آن در فایل index.js، میتواند به صورت زیر باشد:
- همانطور که در قسمت قبل نیز بررسی کردیم، useState Hookها را با هر نوع دادهی دلخواهی میتوان مقدار دهی اولیه کرد؛ برای مثال با یک int و یا یک object. همچنین الزامی هم به تعریف فقط یک useState Hook وجود ندارد و هر قسمتی از state را میتوان توسط یک useState Hook مجزا، تعریف و مدیریت کرد.
- فرم لاگین تعریف شده، از یک فیلد نام کاربری و یک فیلد کلمهی عبور تشکیل شدهاست.
- اکنون میخواهیم اطلاعات دریافت شدهی از کاربر را در state کامپوننت جاری منعکس کنیم. به همین جهت، کار با import متد useState شروع میشود. سپس به ازای هر فیلد در فرم، یک state مجزا را تعریف میکنیم:
- اکنون برای به روز رسانی مقادیر درج شدهی در stateهای تعریف شده بر اساس اطلاعات وارد شدهی توسط کاربر، از رویداد onChange استفاده میکنیم؛ برای مثال:
در اینجا تابع مدیریت کنندهی رویداد onChange، به صورت inline تعریف شدهاست. پیشتر اگر با کامپوننتهای کلاسی میخواستیم اینکار را انجام دهیم، نیاز به clone شیء state، دسترسی به خاصیت متناظر با نام فیلد تعریف شدهی در آن به صورت پویا، به روز رسانی آن و در آخر به روز رسانی state با مقدار جدید شیء state میبود. اما در اینجا نیازی به دانستن نام المان و یا نام خاصیتی نیست.
- پس از به روز رسانی state، میخواهیم در حین submit فرم، این اطلاعات را برای مثال به صورت یک شیء، به سمت سرور ارسال کنیم. به همین جهت نیاز است رویداد onSubmit فرم را مدیریت کرد. در این متد ابتدا از post back معمول آن به سمت سرور جلوگیری میشود و سپس بر اساس متغیرهای تعریف شدهی در state، یک شیء را ایجاد کردهایم:
همچنین چون در پایین فرم نیز میخواهیم این اطلاعات را به صورت JSON نمایش دهیم:
یک state مجزا را هم برای این شیء تعریف:
و در handleSubmit، به روز رسانی کردهایم.
- دو سطر بعدی را که در انتهای handleSubmit مشاهده میکنید، روشی است برای خالی کردن المانهای فرم، پس از ارسال اطلاعات فرم، برای مثال به backend server. البته این حالت فقط برای حالتی نیاز است که فرم قرار نباشد به آدرس دیگری Redirect شود. برای خالی کردن المانهای فرم، المانهای آنرا باید تبدیل به controlled elements کرد که اینکار با مقدار دهی value آنها توسط value={username} صورت گرفتهاست. به این ترتیب محتوای این المانها با اطلاعاتی که در state داریم، قابل کنترل میشوند.
پیاده سازی فرم ثبت نام با استفاده از تنها یک useState Hook
مثال دوم این مطلب نیز در مورد مدیریت المانهای یک فرم توسط useState Hook است؛ با این تفاوت که در اینجا تنها یک شیء، کل state را تشکیل میدهد. کدهای کامل این مثال را در ادامه مشاهده میکنید:
توضیحات:
- فرم ثبت نام فوق از سه فیلد نام کاربری، ایمیل و کلمهی عبور تشکیل شدهاست.
- اینبار نحوهی تشکیل state مرتبط با این سه فیلد را بسیار شبیه به حالت مدیریت state در کامپوننتهای کلاسی، تعریف کردهایم؛ که تنها با یک تک شیء، انجام میشود و نام آنرا form در نظر گرفتهایم:
- اکنون باید راهی را بیابیم تا این خواص شیء form را بر اساس ورودیهای کاربر، به روز رسانی کنیم. به همین جهت رویداد onChange این ورودی را به متغیر handleChange که متد منتسب به آن، این تغییرات را ردیابی میکند، متصل میکنیم:
متد رویدادگردان منتسب به handleChange نیز به صورت زیر تعریف میشود:
این متد بر اساس name المانهای ورودی عمل میکند (در مثال اول این قسمت، نیازی به دانستن نام المانها نبود). زمانیکه یک شیء را به صورت [event.target.name]: event.target.value تعریف میکنیم، یعنی قرار است نام خاصیت این شیء را به صورت پویا تعریف کنیم و مقدار آن نیز از target.value شیء رویداد رسیده، تامین میشود. سپس این شیء جدید، با فراخوانی متد setForm، سبب به روز رسانی شیء form موجود در state میشود.
- علت وجود spread operator تعریف شدهی در اینجا یعنی form...، این است که در حالت استفادهی از useState، برخلاف حالت کار با کامپوننتهای کلاسی، خواص اضافه شدهی به state، به شیء نهایی به صورت خودکار اضافه نمیشوند و باید کار یکی سازی را توسط spread operator انجام داد. برای مثال فرض کنید که کاربر، فیلد نام کاربری را ابتدا ثبت میکند. بنابراین در این لحظه، شیء ارسالی به setForm، فقط دارای خاصیت username خواهد شد. اکنون اگر در ادامه، کاربر فیلد ایمیل را تکمیل کند، اینبار فقط خاصیت ایمیل در این شیء قرار خواهد گرفت (یا مقدار قبلی را به روز رسانی میکند) و از سایر خواص صرفنظر میشود؛ مگر اینکه توسط spread operator، سایر خواص پیشین موجود در شیء form را نیز در اینجا لحاظ کنیم، تا اطلاعاتی را از دست نداده باشیم.
بنابراین به صورت خلاصه در روش سنتی کار با کامپوننتهای کلاسی، فراخوانی متد this.setState کار merge خواص را انجام میدهد؛ اما در اینجا فقط کار replace صورت میگیرد و باید کار merge خواص یک شیء را به صورت دستی و توسط یک spread operator انجام دهیم. البته در قسمت قبل چون تمام خواص شیء تعریف شدهی در state را با هم به روز رسانی میکردیم:
نیازی به تعریف spread operator نبود؛ اما در مثال جاری، هربار فقط یک خاصیت به روز رسانی میشود.
- سایر فیلدهای فرم نیز به همین روش onChange={handleChange}، به متد رویدادگردان فوق متصل میشوند.
- در پایان برای مدیریت رخداد ارسال فرم، handleSubmit را به صورت زیر تعریف کردهایم:
در اینجا برخلاف مثال اول، دیگر نیازی به تشکیل دستی یک شیء جدید برای ارسال به سرور وجود ندارد و هم اکنون اطلاعات کل شیء form، در اختیار برنامه است.
- همچنین چون در پایین فرم نیز میخواهیم این اطلاعات را به صورت JSON نمایش دهیم:
یک state مجزا را هم برای این شیء تعریف:
و در handleSubmit، آنرا با فراخوانی متد setUser، به روز رسانی کردهایم.
- برای پاک کردن المانهای فرم، پس از submit آن، ابتدا نیاز است این المانها را تبدیل به controlled elements کرد که اینکار با مقدار دهی value آنها توسط برای مثال value={form.username} صورت گرفتهاست. به این ترتیب محتوای این المانها با اطلاعاتی که در state داریم، قابل کنترل میشوند. اکنون اگر setForm را با یک شیء خالی مقدار دهی کنیم، به صورت خودکار المانهای فرم را پاک میکند. برای اینکار بجای تعریف شیء موجود در state به صورت inline:
میتوان آنرا خارج از تابع کامپوننت قرار داد:
و سپس آنرا به عنوان مقدار اولیه، به صورت setForm(initialFormState)، فراخوانی کرد؛ تا سبب پاک شدن المانهای فرم شود.
مقایسهی روشهای مختلف مدیریت state توسط useState Hook
پیاده سازی یک فرم لاگین با استفاده از چندین useState Hook
در ابتدا، یک مثال کاربردیتر را به کمک useState Hookها پیاده سازی میکنیم. در اینجا هر المان فرم را به یک useState Hook مجزا، متصل کردهایم. کدهای کامل این کامپوننت را در ادامه مشاهده میکنید:
import React, { useState } from "react"; export default function Login() { const [username, setUsername] = useState(""); const [password, setPassword] = useState(""); const [user, setUser] = useState(null); const handleSubmit = event => { event.preventDefault(); const userData = { username, password }; setUser(userData); setUsername(""); setPassword(""); }; return ( <> <h2 className="mt-3">Login</h2> <form onSubmit={handleSubmit}> <div className="form-group"> <label htmlFor="username">Username</label> <input type="text" name="username" id="username" onChange={event => setUsername(event.target.value)} value={username} className="form-control" /> </div> <div className="form-group"> <label htmlFor="password">Password</label> <input type="password" name="password" id="password" onChange={event => setPassword(event.target.value)} value={password} className="form-control" /> </div> <button type="submit">Submit</button> </form> {user && JSON.stringify(user, null, 2)} </> ); }
- اگر دقت کرده باشید، اینبار این کامپوننت تابعی را به صورت متداول ()function Login تعریف کردهایم. مزیت یک چنین تعریفی، امکان export در محل آن میباشد:
export default function Login() {
به علاوه وجود واژهی default در اینجا سبب میشود که برای import آن، بتوان از هر نام دلخواهی استفاده کرد و در اینجا اجباری به استفادهی از نام Login وجود ندارد که نمونهی استفادهی از آن در فایل index.js، میتواند به صورت زیر باشد:
import App from "./components/part02/Login";
- فرم لاگین تعریف شده، از یک فیلد نام کاربری و یک فیلد کلمهی عبور تشکیل شدهاست.
- اکنون میخواهیم اطلاعات دریافت شدهی از کاربر را در state کامپوننت جاری منعکس کنیم. به همین جهت، کار با import متد useState شروع میشود. سپس به ازای هر فیلد در فرم، یک state مجزا را تعریف میکنیم:
const [username, setUsername] = useState(""); const [password, setPassword] = useState("");
<input type="text" name="username" id="username" onChange={event => setUsername(event.target.value)} value={username} className="form-control" />
- پس از به روز رسانی state، میخواهیم در حین submit فرم، این اطلاعات را برای مثال به صورت یک شیء، به سمت سرور ارسال کنیم. به همین جهت نیاز است رویداد onSubmit فرم را مدیریت کرد. در این متد ابتدا از post back معمول آن به سمت سرور جلوگیری میشود و سپس بر اساس متغیرهای تعریف شدهی در state، یک شیء را ایجاد کردهایم:
const handleSubmit = event => { event.preventDefault(); const userData = { username, password }; setUser(userData); setUsername(""); setPassword(""); };
{user && JSON.stringify(user, null, 2)}
const [user, setUser] = useState(null);
- دو سطر بعدی را که در انتهای handleSubmit مشاهده میکنید، روشی است برای خالی کردن المانهای فرم، پس از ارسال اطلاعات فرم، برای مثال به backend server. البته این حالت فقط برای حالتی نیاز است که فرم قرار نباشد به آدرس دیگری Redirect شود. برای خالی کردن المانهای فرم، المانهای آنرا باید تبدیل به controlled elements کرد که اینکار با مقدار دهی value آنها توسط value={username} صورت گرفتهاست. به این ترتیب محتوای این المانها با اطلاعاتی که در state داریم، قابل کنترل میشوند.
مثال دوم این مطلب نیز در مورد مدیریت المانهای یک فرم توسط useState Hook است؛ با این تفاوت که در اینجا تنها یک شیء، کل state را تشکیل میدهد. کدهای کامل این مثال را در ادامه مشاهده میکنید:
import React, { useState } from "react"; const initialFormState = { username: "", email: "", password: "" }; export default function Register() { const [form, setForm] = useState(initialFormState); const [user, setUser] = useState(null); const handleChange = event => { setForm({ ...form, [event.target.name]: event.target.value }); }; const handleSubmit = event => { event.preventDefault(); setUser(form); setForm(initialFormState); }; return ( <> <h2 className="mt-3">Register</h2> <form onSubmit={handleSubmit}> <div className="form-group"> <label htmlFor="username">Username</label> <input type="text" name="username" id="username" onChange={handleChange} value={form.username} className="form-control" /> </div> <div className="form-group"> <label htmlFor="email">Email</label> <input type="email" name="email" id="email" onChange={handleChange} value={form.email} className="form-control" /> </div> <div className="form-group"> <label htmlFor="password">Password</label> <input type="password" name="password" id="password" onChange={handleChange} value={form.password} className="form-control" /> </div> <button type="submit" className="btn btn-primary"> Submit </button> </form> {user && JSON.stringify(user, null, 2)} </> ); }
- فرم ثبت نام فوق از سه فیلد نام کاربری، ایمیل و کلمهی عبور تشکیل شدهاست.
- اینبار نحوهی تشکیل state مرتبط با این سه فیلد را بسیار شبیه به حالت مدیریت state در کامپوننتهای کلاسی، تعریف کردهایم؛ که تنها با یک تک شیء، انجام میشود و نام آنرا form در نظر گرفتهایم:
const [form, setForm] = useState({ username: "", email: "", password: ""});
<input type="text" name="username" id="username" onChange={handleChange} value={form.username} className="form-control" />
const handleChange = event => { setForm({ ...form, [event.target.name]: event.target.value }); };
- علت وجود spread operator تعریف شدهی در اینجا یعنی form...، این است که در حالت استفادهی از useState، برخلاف حالت کار با کامپوننتهای کلاسی، خواص اضافه شدهی به state، به شیء نهایی به صورت خودکار اضافه نمیشوند و باید کار یکی سازی را توسط spread operator انجام داد. برای مثال فرض کنید که کاربر، فیلد نام کاربری را ابتدا ثبت میکند. بنابراین در این لحظه، شیء ارسالی به setForm، فقط دارای خاصیت username خواهد شد. اکنون اگر در ادامه، کاربر فیلد ایمیل را تکمیل کند، اینبار فقط خاصیت ایمیل در این شیء قرار خواهد گرفت (یا مقدار قبلی را به روز رسانی میکند) و از سایر خواص صرفنظر میشود؛ مگر اینکه توسط spread operator، سایر خواص پیشین موجود در شیء form را نیز در اینجا لحاظ کنیم، تا اطلاعاتی را از دست نداده باشیم.
بنابراین به صورت خلاصه در روش سنتی کار با کامپوننتهای کلاسی، فراخوانی متد this.setState کار merge خواص را انجام میدهد؛ اما در اینجا فقط کار replace صورت میگیرد و باید کار merge خواص یک شیء را به صورت دستی و توسط یک spread operator انجام دهیم. البته در قسمت قبل چون تمام خواص شیء تعریف شدهی در state را با هم به روز رسانی میکردیم:
setMousePosition({ x: event.pageX, y: event.pageY });
- سایر فیلدهای فرم نیز به همین روش onChange={handleChange}، به متد رویدادگردان فوق متصل میشوند.
- در پایان برای مدیریت رخداد ارسال فرم، handleSubmit را به صورت زیر تعریف کردهایم:
const handleSubmit = event => { event.preventDefault(); setUser(form); setForm(initialFormState); };
- همچنین چون در پایین فرم نیز میخواهیم این اطلاعات را به صورت JSON نمایش دهیم:
{user && JSON.stringify(user, null, 2)}
const [user, setUser] = useState(null);
- برای پاک کردن المانهای فرم، پس از submit آن، ابتدا نیاز است این المانها را تبدیل به controlled elements کرد که اینکار با مقدار دهی value آنها توسط برای مثال value={form.username} صورت گرفتهاست. به این ترتیب محتوای این المانها با اطلاعاتی که در state داریم، قابل کنترل میشوند. اکنون اگر setForm را با یک شیء خالی مقدار دهی کنیم، به صورت خودکار المانهای فرم را پاک میکند. برای اینکار بجای تعریف شیء موجود در state به صورت inline:
const [form, setForm] = useState({ username: "", email: "", password: ""});
const initialFormState = { username: "", email: "", password: "" }; export default function Register() { const [form, setForm] = useState(initialFormState);
مقایسهی روشهای مختلف مدیریت state توسط useState Hook
همانطور که مشاهده کردید، با useState Hook، به انعطاف پذیری بیشتری برای مدیریت حالت، نسبت به روش سنتی کامپوننتهای کلاسی رسیدهایم. در حالت تعریف یک useState به ازای هر فیلد، روش تعریف رویدادگردانها و همچنین تبدیل المانها به المانهای کنترل شده، نسبت به روش تعریف تنها یک useState به ازای کل فرم، سادهتر و قابل درکتر است. اما زمانیکه نیاز به پاک کردن المانهای فرم باشد، روش کار کردن با یک تک شیء، سادهتر است. درکل بهتر است برای خواص غیرمرتبط state، به ازای هر کدام، یک useState را تعریف کرد و برای یک فرم، همان روش قرار دادن اطلاعات تمام المانها در یک شیء، برای کار با فرمهای طولانیتر، سریعتر و قابلیت مدیریت سادهتری را به همراه دارد.
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: sample-30-part-02.zip
نظرات مطالب
مقابله با XSS ؛ یکبار برای همیشه!
مشکلی مشاهده نشد.
- ابتدا به مثال مربوطه، کتابخانهی AntiXSS مایکروسافت اضافه شد.
- سپس در حالت مشاهدهی HTML این ادیتور، تگ اسکریپتی جهت بررسی تزریق XSS اضافه گردید:
- سپس مقدار body پیش از و همچنین پس از عبور آن از کتابخانهی Anti-XSS بررسی شد.
پیش از عبور: (اصل محتوای ارسالی به سرور)
پس از عبور: (محتوای پاکسازی شده)
- ابتدا به مثال مربوطه، کتابخانهی AntiXSS مایکروسافت اضافه شد.
- سپس در حالت مشاهدهی HTML این ادیتور، تگ اسکریپتی جهت بررسی تزریق XSS اضافه گردید:
- سپس مقدار body پیش از و همچنین پس از عبور آن از کتابخانهی Anti-XSS بررسی شد.
پیش از عبور: (اصل محتوای ارسالی به سرور)
پس از عبور: (محتوای پاکسازی شده)
عموما از ajax برای ارائه سایتهایی سریع، با حداقل ریفرش و حداقل مصرف پهنای باند سرور، استفاده میشود. اما این روش، مشکلات خاص خود را نیز دارا است. عموما محتوای پویای بارگذاری شده، سبب تغییر آدرس صفحهی جاری در مرورگر نمیشود. برای مثال اگر قرار است چندین برگه در صفحه به صورت ajax ایی بارگذاری شوند، تغییر سریع محتوا را مشاهده میکنید، اما خبری از تغییر آدرس جاری صفحه در مرورگر نیست. همچنین روشهای ajax ایی عموما SEO friendly نیستند. زیرا اکثر موتورهای جستجو فاقد پردازشگرهای جاوا اسکریپت میباشند و محتوای پویای ajax ایی را مشاهده نمیکنند. برای آدرس دهی این مشکلات مهم، افزونهای به نام pjax طراحی شدهاست که کار آن دریافت محتوای HTML ایی از سرور و قرار دادن آن در یک جایگاه خاص مانند یک div است. در پشت صحنهی آن از jQuery ajax استفاده شده، به همراه push state
سایتهای بسیاری خودشان را با این الگو وفق دادهاند. برای نمونه Twitter و Github از مفهوم pjax استفادهی وسیعی دارند. برای نمونه، layout یا master page یک سایت را درنظر بگیرید. به ازای مرور هر صفحه، یکبار باید تمام قسمتهای تکراری layout از سرور بارگذاری شوند. توسط pjax به سرور اعلام میکنیم، ما تنها نیاز به body صفحات را داریم و نه کل صفحه را. همچنین اگر مرورگر از جاوا اسکریپت استفاده نمیکند، لطفا کل صفحه را همانند گذشته بازگشت بده. به علاوه مسایل سمت کلاینت مانند تغییر آدرس مرورگر و تغییر عنوان صفحه نیز به صورت خودکار مدیریت شوند. این تکنیک را دقیقا در حین مرور مخزنهای کد Github میتوانید مشاهده کنید. فقط قسمتی که لیست فایلها را ارائه میدهد، از سرور دریافت میگردد و نه کل صفحه.
بکارگیری pjax در ASP.NET MVC
مطابق توضیحاتی که ارائه شد، برای پیاده سازی سازی pjax نیاز به دو فایل layout داریم. یکی برای حالت ajax ایی و دیگری برای حالت بارگذاری کامل صفحه. حالت ajax ایی آن تنها از رندرکردن body پشتیبانی میکند؛ و نه ارائه تمام قسمتهای صفحه مانند هدر، فوتر، منوها و غیره. بنابراین خواهیم داشت:
الف) تعریف فایلهای layout سازگار با pjax
ابتدا یک فایل جدید را به نام _PjaxLayout.cshtml به پوشهی Shared اضافه کنید؛ با این محتوا:
سپس layout اصلی سایت را به نحو ذیل تغییر دهید
در فایل PjaxLayout خبری از هدر و فوتر نیست و فقط یک عنوان و نمایش body را به همراه دارد.
فایل layout اصلی سایت همانند قبل است. فقط RenderBody آن داخل یک div با id مساوی pjaxContainer قرار گرفته و از آن در فراخوانی افزونهی pjax استفاده شدهاست. همانطور که ملاحظه میکنید، مطابق تنظیمات ابتدای هدر layout، فقط لینکهایی که دارای ویژگی withpjax باشند، توسط pjax پردازش خواهند شد.
ب) تغییر فایل ViewStart برنامه
در فایل ViewStart، کار مقدار دهی layout پیش فرض صورت گرفتهاست. اکنون نیاز است این فایل را جهت معرفی layout دوم تعریف شده مخصوص pjax، اندکی ویرایش کنیم:
افزونهی pjax، هدری را به نام X-PJAX به سرور ارسال میکند. بر این اساس میتوان تصمیم گرفت که آیا از layout اصلی (در صورتیکه مرورگر از جاوا اسکریپت پشتیبانی نمیکند و این هدر را ارسال نکردهاست) یا از layout سبکتر pjax استفاده شود.
ج) آزمایش برنامه
یک کنترلر ساده را به نحو فوق با دو اکشن متد و دو View متناظر با آن ایجاد کنید.
سپس View متد Index را به نحو ذیل تغییر دهید:
در این View یک لینک معمولی به اکشن متد About اضافه شدهاست. فقط در ویژگیهای html آن، یک ویژگی جدید به نام withpjax را نیز اضافه کردهایم تا در صورت امکان و پشتیبانی مرورگر، از pjax استفاده شود.
اکنون اگر برنامه را اجرا کنید، چنین خروجی را در برگهی network آن مشاهده خواهید کرد:
همانطور که ملاحظه میکنید، با کلیک بر روی لینک About، یک درخواست pjax ایی به سرور ارسال شدهاست؛ به همراه هدرهای ویژه آن. هنوز قسمتهای اصلی layout سایت مشخص هستند (و مجددا از سرور درخواست نشدهاند). آدرس صفحه عوض شدهاست. به علاوه قسمت body آن تنها تغییر کردهاست.
این مثال را از اینجا نیز میتوانید دریافت کنید
PajxMvcApp.zip
برای مطالعه بیشتر
A Faster Web With PJAX
Favour PJAX over dynamically loaded partial views
What is PJAX and why
Pjax.Mvc
Using pjax with ASP.Net MVC3
Getting started with PJAX with ASP.NET MVC
ASP.NET MVC with PAjax or PushState/ReplaceState and Ajax
pjax = pushState + AJAX
Push state API همان HTML5 History API است؛ به این معنا که هرچند محتوای صفحهی جاری به صورت پویا بارگذاری میشود، اما آدرس مرورگر نیز به صورت خودکار تنظیم خواهد شد؛ به همراه عنوان صفحه. به علاوه تاریخچهی مرور صفحات نیز در مرورگر به روز رسانی شده و امکان حرکت بین صفحات توسط دکمههای back و forward همانند قبل وجود خواهد داشت. همچنین اگر مرورگر جاری سایت، امکان استفاده از جاوا اسکریپت را نداشته باشد، به صورت خودکار به حالت بارگذاری کامل صفحه سوئیچ خواهد کرد.سایتهای بسیاری خودشان را با این الگو وفق دادهاند. برای نمونه Twitter و Github از مفهوم pjax استفادهی وسیعی دارند. برای نمونه، layout یا master page یک سایت را درنظر بگیرید. به ازای مرور هر صفحه، یکبار باید تمام قسمتهای تکراری layout از سرور بارگذاری شوند. توسط pjax به سرور اعلام میکنیم، ما تنها نیاز به body صفحات را داریم و نه کل صفحه را. همچنین اگر مرورگر از جاوا اسکریپت استفاده نمیکند، لطفا کل صفحه را همانند گذشته بازگشت بده. به علاوه مسایل سمت کلاینت مانند تغییر آدرس مرورگر و تغییر عنوان صفحه نیز به صورت خودکار مدیریت شوند. این تکنیک را دقیقا در حین مرور مخزنهای کد Github میتوانید مشاهده کنید. فقط قسمتی که لیست فایلها را ارائه میدهد، از سرور دریافت میگردد و نه کل صفحه.
بکارگیری pjax در ASP.NET MVC
مطابق توضیحاتی که ارائه شد، برای پیاده سازی سازی pjax نیاز به دو فایل layout داریم. یکی برای حالت ajax ایی و دیگری برای حالت بارگذاری کامل صفحه. حالت ajax ایی آن تنها از رندرکردن body پشتیبانی میکند؛ و نه ارائه تمام قسمتهای صفحه مانند هدر، فوتر، منوها و غیره. بنابراین خواهیم داشت:
الف) تعریف فایلهای layout سازگار با pjax
ابتدا یک فایل جدید را به نام _PjaxLayout.cshtml به پوشهی Shared اضافه کنید؛ با این محتوا:
<title>@ViewBag.Title</title> @RenderBody()
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width" /> <title>@ViewBag.Title</title> <link href="~/Content/Site.css" rel="stylesheet" /> <script src="~/Scripts/jquery-1.8.2.min.js"></script> <script src="~/Scripts/jquery.pjax.js"></script> <script type="text/javascript"> $(function () { $(document).pjax('a[withpjax]', '#pjaxContainer', { timeout: 5000 }); }); </script> </head> <body> <div>Main layout ...</div> <div id="pjaxContainer"> @RenderBody() </div> </body> </html>
فایل layout اصلی سایت همانند قبل است. فقط RenderBody آن داخل یک div با id مساوی pjaxContainer قرار گرفته و از آن در فراخوانی افزونهی pjax استفاده شدهاست. همانطور که ملاحظه میکنید، مطابق تنظیمات ابتدای هدر layout، فقط لینکهایی که دارای ویژگی withpjax باشند، توسط pjax پردازش خواهند شد.
ب) تغییر فایل ViewStart برنامه
در فایل ViewStart، کار مقدار دهی layout پیش فرض صورت گرفتهاست. اکنون نیاز است این فایل را جهت معرفی layout دوم تعریف شده مخصوص pjax، اندکی ویرایش کنیم:
@{ if (Request.Headers["X-PJAX"] != null) { Layout = "~/Views/Shared/_PjaxLayout.cshtml"; } else { Layout = "~/Views/Shared/_Layout.cshtml"; } }
ج) آزمایش برنامه
using System.Web.Mvc; namespace PajxMvcApp.Controllers { public class HomeController : Controller { public ActionResult Index() { return View(); } public ActionResult About() { return View(); } } }
سپس View متد Index را به نحو ذیل تغییر دهید:
@{ ViewBag.Title = "Index"; } <h2>Index</h2> @Html.ActionLink(linkText: "About", actionName:"About", routeValues: null, controllerName:"Home", htmlAttributes: new { withpjax = "with-pjax"})
اکنون اگر برنامه را اجرا کنید، چنین خروجی را در برگهی network آن مشاهده خواهید کرد:
همانطور که ملاحظه میکنید، با کلیک بر روی لینک About، یک درخواست pjax ایی به سرور ارسال شدهاست؛ به همراه هدرهای ویژه آن. هنوز قسمتهای اصلی layout سایت مشخص هستند (و مجددا از سرور درخواست نشدهاند). آدرس صفحه عوض شدهاست. به علاوه قسمت body آن تنها تغییر کردهاست.
این مثال را از اینجا نیز میتوانید دریافت کنید
PajxMvcApp.zip
برای مطالعه بیشتر
A Faster Web With PJAX
Favour PJAX over dynamically loaded partial views
What is PJAX and why
Pjax.Mvc
Using pjax with ASP.Net MVC3
Getting started with PJAX with ASP.NET MVC
ASP.NET MVC with PAjax or PushState/ReplaceState and Ajax
معرفی :
Svelte یک رویکرد جدید برای ایجاد رابط کاربری است که به ما کمک میکند صفحاتی پویا به صورت SPA با کارآیی و کیفیت بالا و همچنین کمترین حجم کد تولید کنیم. تفاوت اصلی svelte با رقبای سنتی خود مانند vue - React - angular این است که Svelte تنها یک فریم ورک نیست، بلکه درواقع یک کامپایلر است که همین موضوع سبب شده توجه زیادی را اخیرا به خود جلب کند. در فریم ورکهای سنتی، تمام عملیات در browser انجام میشود یا بهتر است بگوییم در run-time؛ ولی svelte تمام این عملیات را زمان build شدن برنامه شما انجام میدهد و کد جاوا اسکریپتی بدون هیچ وابستگی به هیچ پکیجی تولید میکند. نکته دیگری که باید به آن اشاره کنم این است که برخلاف سایر فریم ورکها، svelte از virtual DOM استفاده نمیکند! در بخشهای بعد در مورد virtual DOM و معایب آن بیشتر صحبت خواهیم کرد.
مقایسه مختصر فریم ورکهای معتبر :
مقایسههایی که در ادامه قصد دارم به اشتراک بگذارم، بر مبنای سه بخش Performance - size - lines of code است که به صورت مختصر با هم بررسی خواهیم کرد و کاری با جزئیات این مقایسه نداریم؛ چرا که هدف از نشان دادن این مقایسه صرفا این است که شاید برای ما سوال شود چرا باید یا بهتر است به این فریم ورک اهمیت بدهیم.
- Performance : کارآیی - که در ارتباط با مدت زمان پاسخ گویی و قابل استفاده شدن برنامه میباشد. (مقایسه به درصد - هرچه بیشتر عملکرد بهتر)
در مقایسه اول، اکثر فریم ورکها، امتیازی بالای 90 درصد دارند که در واقع نشان دهنده این است شما از هرکدام از این موارد استفاده کنید، چندان تفاوتی را احساس نخواهید کرد.
با توجه به اینکه svelte به نسبت بقیه این فریم ورکها که خیلی از آنها کاملا جا افتاده هستند، بسیار جدید است و جای بهبود دارد از نظر performance عملکرد قابل قبولی از خود نشان داده است.
- Size : اندازه - که نشان دهنده حجم نهایی فایلهای تولید شده ( Css-Html-JavaScript ) فریم ورک است. این مقایسه اندازه فریم ورک و تمام وابستگیهای آن است که به bundle نهایی برنامه اضافه شده است (هر چه اندازه فایل کمتر باشد بهتر است چراکه توسط کاربر نهایی زودتر دانلود میشود).
در مقایسه size یکی از دلایل محبوبیت این کامپایلر را مشاهده میکنید که تفاوت قابل توجهی نسبت به سایر فریم ورکها دارد.
- Lines of Code : تعداد خطوط کد - نشان دهنده این است که یک نویسنده بر اساس این فریم ورکها چند سطر کد را باید برای تهیهی یک برنامهی جدید بنویسد.
نکته دیگری که باید اینجا بهش اشاره کنم، ساده بودن svelte است. این سادگی سبب میشود میزان کدنویسی برای ساخت یک برنامه به مراتب کمتر از فریم ورکهای دیگر باشد. که در نتیجه بازدهی استفاده از آن را بالاتر خواهد برد.
برای کسب اطلاعات بیشتر و مطالعه منبع این مقایسه میتوانید به این لینک مراجعه نمایید.
نتیجه گیری : ( مزایا - معایب )
درمورد مزایای استفاده از svelte میتوان به راحتی کارکردن با آن، حجم بسیار کم کدهای نهایی برنامه و عملکرد مناسب آن و همینطور استفاده نکردن از virtualDom اشاره کرد؛ چرا که برای اولین بار کدهای تولید شده به معنای واقعی واکنش گرا خواهند بود.
هرچند معایبی هم شاید داشته باشد که قبل از هر چیز بهتر است به آنها اشاره کنم. بزرگترین و شاید تنها ایرادی که من میتوانم از این فناوری بگیرم این است که خالق این تکنولوژی یک نفر است! angular توسط شرکت google توسعه داده میشود. react توسط فیسبوک توسعه داده میشود. vue درست است که شرکت بزرگی آن را توسعه نمیدهد ولی نتیجه یک کار تیمی و چند صد نفر برنامه نویس مختلف است که به صورت open source به توسعه آن میپردازند. شاید این تنها نکته منفی باشد که اعتماد به این تکنولوژی را سخت کرده است.
دانلود و نصب :
پیش نیاز :
قبل از هرچیز برای نصب Svelte به Node.Js نیاز داریم. قبل از شروع کار، از نصب بودن آن اطمینان حاصل نمایید.
ساخت اولین برنامه :
npx degit sveltejs/template my-svelte-project cd my-svelte-project npm install npm run dev
البته با استفاده از اسکریپت dev، کدهای ما برای زمان برنامه نویسی بهینه شدهاند و چندان برای پابلیش و استفاده مناسب نیستند؛ لذا برای تولید کدهای مناسب برای محصول نهایی میتوانیم از دستور npm run build استفاده کنیم.
در بخش بعد به بررسی ساختار فایلها و کدهای ایجاد شده Svelte میپردازیم.
مقدمه
موقعی که سینمای ناطق کار خود را آغاز
کرد، بسیاری از مردم از آن استقبال کردند و بسیاری از سینماگران که این
استقبال را دیدند، رفته رفته به سمت سینمای ناطق کشیده شدند. ولی در این بین
یک مشکلی ایجاد شده بود؛ اینکه ناشنوایان دیگر مانند قدیم یعنی دوران صامت
نمیتوانستند فیلمها را تماشا کنند، پس نیاز بود این مشکل به نحوی رفع شود. از اینجا بود که ایدهی زیرنویس شکل گرفت و این مشکل را رفع نمود. بعدها
فیلمها انتقال دهندهی فرهنگ و پیوند دهندهی مردم با فرهنگهای مختلف شدند
ولی تفاوت در زبان باعث میشد که این امر به خوبی صورت نگیرد. به همین علت
زیرنویس، وظیفهی دیگری را هم پیدا کرد و آن رساندن پیام فیلم با زبان خود
مخاطب بود. امروزه تهیهی زیرنویسها توسط بسیاری از افراد که با زبان انگلیسی
(آشنایی با یک زبان میانی برای ترجمه زیرنویس) آشنایی دارند رواج پیدا کرده
و روزانه نزدیک به صد زیرنویس یا گاها بیشتر با زبانهای مختلف بر روی
اینترنت قرار میگیرند. بزرگترین سایتی که در حال حاضر با شهرت جهانی در این
زمینه فعالیت دارد سایت subscene.com است.
آشنایی با انواع زیرنویسها
زیرنویسها فرمتهای مختلفی دارند مانند srt,sub idx,smi و ... ولی در حال حاضر معروفترین و معتبرترین فرمت در بین همهی فرمتها Subrip با پسوند SRT میباشد که قالب متنی به صورت زیر دارد:
203 00:16:38,731 --> 00:16:41,325 <i>Happy Christmas, your arse I pray God it's our last</i>
بررسی مشکل ما با زیرنویس در تلویزیونها
یکی از مشکلاتی
که ما در اجرای زیرنویسها بر روی تلویزیونها داریم این است که حروف
فارسی را به خوبی نمیشناسند و در هنگام نمایش با مشکل مواجه میشوند که
البته در اکثر مواقع با تبدیل زیرنویس از ANSI به Unicode یا UTF-8 مشکل حل
میشود. ولی در بعضی مواقع تلویزیون یا پلیرها از پشتیبانی زبان فارسی
سرباز میزنند و زیرنویس را به شکل زیر نمایش میدهند.
سلام = م ا ل س
به این جهت ما از یک برنامه به اسم
srttouni استفاده میکنیم که با استفاده یک روش جایگزینی و معکوس سازی، مشکل
ما را حل میکند. ولی باز هم این برنامه مشکلاتی دارد و از آنجا که برنامه
نویس این برنامه که واقعا کمال تشکر را از ایشان، دارم مشخص نیست، مجبور شدم
به جای گزارش، خودم این مشکلات را حل کنم.
مشکلات این برنامه :
- عدم حذف تگها ، گاها برنامه نویسها از تگ هایی چون Bold,italic,underline,color استفاده میکنند که معدود برنامههایی آن را پشتیبانی کرده و تلویزیون و پلیرها هم که اصلا پشتیبانی نمیکنند و باعث میشود که متن روی تلویزیون مثل کد html ظاهر شود
- بعضی جملات دوبار روی صفحه ظاهر میشوند.
- تنها یک فایل را در هر زمان تبدیل میکند. مثلا اگر یک سریال چند قسمته داشته باشید، برای هر قسمت باید زیرنویس را انتخاب کرده و تبدیل کنید، در صورتی که میتوان دستور داد تمام زیرنویسهای داخل دایرکتوری را تبدیل کرد یا چند زیرنویس را برای این منظور انتخاب کرد.
نحوهی خواندن زیرنویس با کدنویسی
با تشکر از دوست عزیز ما در این صفحه میتوان
گفت یک کد تقریبا خوب و جامعی را برای خواندن این قالب داریم. بار دیگر
نگاهی به قالب یک دیالوگ در زیرنویس میاندازیم و آن را بررسی میکنیم:
203 00:16:38,731 --> 00:16:41,325 <i>Happy Christmas, your arse I pray God it's our last</i>
کد زیر در کلاس SubRipServices وظیفهی خواندن محتوای فایل srt را بر اساس عبارتی که دادیم دارد:
در اولین خط ما یک Regular Expersion یا یک عبارت با قاعده تعریف کردیم که در اینجا میتوانید
با خصوصیات آن آشنا شوید. ما برای این کلاس یک الگو ایجاد کردیم و بر حسب
این الگو، متن یک زیرنویس را خواهد گشت و خطوطی را که با این تعریف جور در
میآیند و معتبر هستند، برای ما باز میگرداند.
private readonly static Regex regex_srt = new Regex(@"(?<sequence>\d+)\r\n(?<start>\d{2}\:\d{2}\:\d{2},\d{3}) --\> " + @"(?<end>\d{2}\:\d{2}\:\d{2},\d{3})\r\n(?<text>[\s\S]*?)\r\n\r\n", RegexOptions.Compiled); public string ToUnicode(string lines) { string subtitle= regex_srt.Replace(lines,delegate(Match m) { string text = m.Groups["text"].Value; //1.remove tags text = CleanScriptTags(text); //2.replace letters PersianReshape reshaper = new PersianReshape(); text = reshaper.reshape(text); string[] splitedlines = text.Split(new string[] { Environment.NewLine }, StringSplitOptions.None); text = ""; foreach (string line in splitedlines) { //3.reverse tags text += ReverseText(reshaper.reshape(line))+Environment.NewLine ; } return string.Format("{0}\r\n{1} --> {2}\r\n", m.Groups["sequence"], m.Groups["start"].Value, m.Groups["end"]) + text + Environment.NewLine+Environment.NewLine ; } ); return subtitle; }
عبارتهایی که به صورت <name>? تعریف شدهاند در واقع یک
نامگذاری برای هر قسمت از الگوی ما هستند تا بعدا این امکان برای ما فراهم
شود که خطوط برگشتی را تجزیه کنیم که مثلا فقط قسمت متن را دریافت کنیم،
یا فقط قسمت زمان شروع یا پایان را دریافت کنیم و ...
متد tounicode یک آرگومان متنی دارد (lines) که شامل محتویات فایل
زیرنویس است. متد Replace در شی regex_srt با هر بار پیدا کردن یک متن بر
اساس الگو در رشته lines دلیگیتی را فرا میخواند که در اولین پارامتر آن
که از نوع matchEvaluator است، شامل اطلاعات متنی است که بر اساس الگو، یافت
شده است. خروجی آن از نوع string میباشد که با متن پیدا شده بر اساس الگو
جابجا خواهد کرد و در نهایت بعد از چندین بار اجرا شدن، کل متنهای تعویض
شده، به داخل متغیر subtitle ارسال خواهند شد.
کاری که ما در اینجا میکنیم این است که هر دیالوگ داخل زیرنویس را بر
اساس الگو، یافته و متن آن را تغییر داده و متن جدید را جایگزین متن قبلی
میکنیم. اگر زیرنویس ما 800 دیالوگ داشته باشد این دلیگیت 800 مرتبه اجرا
خواهد شد.
از آنجا که ما تنها میخواهیم متن زیرنویس را تغییر دهیم، در اولین
خط فرامین این دلیگیت تعریف شده، متن مورد نظر را بر اساس همان گروههایی
که تعریف کردهایم دریافت میکنیم و در متغیر text قرار میدهیم:
m.Groups["text"].Value
private static readonly Regex regex_tags = new Regex("<.*?>", RegexOptions.Compiled); private string CleanScriptTags(string html) { return regex_tags.Replace(html, string.Empty); }
PersianReshape reshaper = new PersianReshape(); text = reshaper.reshape(text); string[] splitedlines = text.Split(new string[] { Environment.NewLine }, StringSplitOptions.None); text = ""; foreach (string line in splitedlines) { //3.reverse tags text += ReverseText(reshaper.reshape(line))+Environment.NewLine ; }
بلوک اول طبق گفتهی ویکی پدیا دستهی متنوعی از حروف مورد نیاز برای زبان فارسی، اردو، پاکستانی و تعدادی از زبانهای آسیای مرکزی است.
بلوک دوم شامل نمادها و نشانههای زبان
عربی است و در حال حاضر برای کد کردن استفاده نمیشوند و دلیل حضور آن
برای سازگاری با سیستمهای قدیمی است.
اگر خوب به مشکلی که در بالا برای
زیرنویسها اشاره کردیم دقت کنید، گفتیم حروف از هم جدا نشان داده میشوند و
اگر به بلوک دوم در لینکهای داده شده نگاه کنید میبینید که حروف متصل را
داراست. یعنی برای حرف س 4 حرف یا کدپوینت داراست : سـ برای کلماتی مثل سبد، ـس برای کلماتی مثل شانس، ـسـ برای کلماتی مثل بسیار، ولی خود س برای کلمات غیر متصل مثل ناس، البته بعضی حروف یک یا دو حالت میطلبند مثل د، ر که فقط دو حالت ـد و د ، ـر و ر را دارند یا مثل آ که یک حالت دارد.
من قبلا یک کلاس به نام lettersTable ایجاد کرده بودم (و دیگر نوشتن آن را ادامه ندادم) که برای هر حرف، یک آیتم در شیءایی از نوع dictionary
ساخته بودم و هر کدپوینت بلوک اول را در آن کلید و کد متقابلش را در بلوک
دوم، به صورت مقدار ذخیره کرده بودم (گفتیم که هر نماد در بلوک اول،
برابر با 4 نماد در بلوک دوم است؛ ولی ما در دیکشنری تنها مقدار اول را
ذخیره میکنیم. زیرا کد بقیه نمادها دقیقا پشت سر یکدیگر قرار گرفتهاند که
میتوان با یک جمع ساده از عدد 0 تا 3، به مقدار هر کدام از نمادها
رسید. البته ناگفته نماند بعضی نمادها 2 عدد بودند که این هم باید بررسی
شود). برای همین هر کاراکتر را با کاراکتر قبل و بعد میگرفتم و بررسی
میکردم و از یک جدول دیکشنری دیگر هم به اسم specialchars هم استفاده کردم
تا آن کاراکترهایی که تنها دو نماد یا یک نماد را دارند، بررسی کنم و این
کاراکترها همان کاراکترهایی بودند که اگر قبل یک حرف هم بیایند، حرف بعدی
به آنها نمیچسبد. برای درک بهتر، این عبارت مثال زیر را برای حرف س در
نظر بگیرید:
مستطیل = چون بین هر دو طرف س حر وجود دارد قطعا باید شکل س به صورت ـسـ انتخاب شود ، حالا مثال زیر را در نظر بگیرید:
دست = دـست که اشتباه است و باید باشد دست یعنی شکل سـ باید صدا زده شود، پس این مورد هم باید لحاظ شود.
نمونهای از کد این کلاس:
Dictionary<int ,int> letters=new Dictionary<int, int>(); //0=0x0 ,1=1x0 ,2=0x1 ,3=1x1 private void FillPrimaryTable() { //آ letters.Add(1570, 65153); //ا letters.Add(1575, 65166); //أ letters.Add(1571, 65155); //ب letters.Add(1576, 65167); //ت letters.Add(1578, 65173); //ث letters.Add(1579, 65177); //ج letters.Add(1580, 65181); ..... } Dictionary<int,byte> specialchars=new Dictionary<int, byte>(); private void SetSpecialChars() { //آ specialchars.Add(1570, 0); //ا specialchars.Add(1575, 0); //د2 specialchars.Add(1583, 1); //ذ2 specialchars.Add(1584, 1); //ر2 specialchars.Add(1585, 1); //ز2 specialchars.Add(1586, 1); //ژ specialchars.Add(1688, 1); //و2 specialchars.Add(1608, 1); //أ specialchars.Add(1571, 1); }
در آن متد هر بار یک حرف را انتخاب میکرد و حرف قبلی و بعدی آن را ارسال میکرد تا تابع CalculateIncrease آن را محاسبه کرده و کاراکتر نهایی را باز گرداند و به متغیر finalText اضافه میکرد. ولی در حین نوشتن، زمانی را به یاد آوردم که اندروید به تازگی آمده بود و هنوز در آن زمان از زبان فارسی پشتیبانی نمیکرد و حروف برنامههایی که مینوشتیم به صورت جدا از هم بود و همین مشکل را داشت که ما این مشکل را با استفاده از یک کلاس جاوا که دوست عزیزی آن را در اینجا به اشتراک گذاشته بود، حل میکردیم. پس به این صورت بود که از ادامهی نوشتن کلاس انصراف دادم و از یک کلاس دقیقتر و آماده استفاده کردم.
در واقع این کلاس همین کار بالا را با
روشی بهتر انجام میدهد. همهی نمادها به طور دقیقتری کنترل میشوند
حتی تنوینها و دیگر علائم، همه نمادها با کدهای متناظر
در یک آرایه ذخیره شدهاند که ما در بالا از نوع Dictionary استفاده کرده
بودیم.
تنها کاری که نیاز بود، باید این کد به
سی شارپ تبدیل میشد و از آنجایی که این دو زبان خیلی شبیه به هم هستند، حدود
ده دقیقهای برای ویرایش کد وقت برد که میتوانید کلاس نهایی را از اینجا دریافت کنید.
پس خط زیر در متد ToUnicode کار تبدیل اصلی را صورت میدهد:
PersianReshape reshaper = new PersianReshape(); text = reshaper.reshape(text);
//3.reverse tags text = ReverseText(text);
string[] splitedlines = text.Split(new string[] { Environment.NewLine }, StringSplitOptions.None); text = ""; foreach (string line in splitedlines) { //3.reverse tags text += ReverseText(reshaper.reshape(line))+Environment.NewLine ; }
این دو تابع برای معکوس کردن عادی یک رشته به کار میروند:
ولی این تابع ReverseText جمعی از عملیات معکوس سازی ویژهی ماست؛ مرحله اول، مرحله دریافت و ذخیرهی حروف خاص در ابتدای رشته به اسم پیشوند prefix است:
مرحلهی دوم هم دریافت و ذخیرهی حروف خاص در انتهای رشته به اسم پسوند postfix است که به این تابع اضافه میکنیم:
مرحلهی سوم عملیات معکوس سازی روی رشته است و سپس با استفاده از یک Regular
Expression حروف انگلیسی و اعداد بین حروف فارسی را یافته و یک معکوس سازی
هم روی آنها انجام میدهیم تا به حالت اولشان برگردند. کل عملیات معکوس سازی
در اینجا به پایان میرسد:
تعریف عبارت با قاعدهی بالا به اسم unTargetedLetters:
آخر سر هم رشته را بهعلاوه پیشوند و پسوند جدا شده بر میگردانیم:
کد کامل تابع بدین شکل در میآید:
private string Reverse(string text) { return Reverse(text,0,text.Length); } private string Reverse(string text,int start,int end) { if (end < start) return text; string reverseText = ""; for (int i = end-1; i >=start; i--) { reverseText += text[i]; } return reverseText; }
private string ReverseText(string text) { char[] chararray = text.ToCharArray(); string reverseText = ""; bool prefixcomp = false; bool postfixcomp = false; string prefix = ""; string postfix = ""; #region get prefix symbols for (int i = 0; i < chararray.Length; i++) { if (!prefixcomp) { char ch =(char) chararray.GetValue(i) ; if (ch< 130) { prefix += chararray.GetValue(i); } else { prefixcomp = true; break; } } } #endregion }
#region get postfix symbols for (int i = chararray.Length - 1; i >-1 ; i--) { if (!postfixcomp && prefix.Length!=text.Length) { char ch = (char)chararray.GetValue(i); if (ch < 130) { postfix += chararray.GetValue(i); } else { postfixcomp = true; break; } } } #endregion
#region reverse text reverseText = Reverse(text, prefix.Length, text.Length-postfix.Length); reverseText = unTagetdLettersRegex.Replace(reverseText, delegate(Match m) { return Reverse(m.Value); }); #endregion
private static readonly Regex unTagetdLettersRegex = new Regex(@"[A-Za-z0-9]+", RegexOptions.Compiled);
return prefix+ reverseText+postfix;
private static readonly Regex unTagetdLettersRegex = new Regex(@"[A-Za-z0-9]+", RegexOptions.Compiled); private string ReverseText(string text) { char[] chararray = text.ToCharArray(); string reverseText = ""; bool prefixcomp = false; bool postfixcomp = false; string prefix = ""; string postfix = ""; #region get prefix symbols for (int i = 0; i < chararray.Length; i++) { if (!prefixcomp) { char ch =(char) chararray.GetValue(i) ; if (ch< 130) { prefix += chararray.GetValue(i); } else { prefixcomp = true; break; } } } #endregion #region get postfix symbols for (int i = chararray.Length - 1; i >-1 ; i--) { if (!postfixcomp && prefix.Length!=text.Length) { char ch = (char)chararray.GetValue(i); if (ch < 130) { postfix += chararray.GetValue(i); } else { postfixcomp = true; break; } } } #endregion #region reverse text reverseText = Reverse(text, prefix.Length, text.Length-postfix.Length); reverseText = unTagetdLettersRegex.Replace(reverseText, delegate(Match m) { return Reverse(m.Value); }); #endregion return prefix+ reverseText+postfix; }
در نهایت، خط آخر دلیگت همه چیز را طبق
فرمت یک دیالوگ srt چینش کرده و بر میگردانیم.
return string.Format("{0}\r\n{1} --> {2}\r\n", m.Groups["sequence"], m.Groups["start"].Value, m.Groups["end"]) + text + Environment.NewLine+Environment.NewLine ;
نمایی از برنامهی نهایی
اجرای زیرنویس تبدیل شده روی کامپیوتر
روی پلیر یا تلویزیون
نکتهی نهایی: هنگام تست زیرنویس روی فیلم متوجه شدم پلیر خطوط بلند را که در صفحهی نمایش جا نمیشود، میشکند و به دو خط تقسیم میکند. ولی نکتهی خنده دار اینجا بود که خط اول را پایین میاندازد و خط دوم را بالا. برای همین این تکه کد را نوشتم و به طور جداگانه در گیت هاب هم قرار دادهام.
این تکه کد را هم بعد از
به برنامه اضافه میکنیم:
روی پلیر یا تلویزیون
نکتهی نهایی: هنگام تست زیرنویس روی فیلم متوجه شدم پلیر خطوط بلند را که در صفحهی نمایش جا نمیشود، میشکند و به دو خط تقسیم میکند. ولی نکتهی خنده دار اینجا بود که خط اول را پایین میاندازد و خط دوم را بالا. برای همین این تکه کد را نوشتم و به طور جداگانه در گیت هاب هم قرار دادهام.
این تکه کد را هم بعد از
//1.remove tags text = CleanScriptTags(text);
text =StringUtils.ConvertToMultiLine(text);
کد متد ConvertToMultiline:
namespace Utils { public static class StringUtils { public static string ConvertToMultiLine(String text, int min = 30, int max = 40) { if (text.Trim() == "") return text; string[] words = text.Split(new string[] { " " }, StringSplitOptions.None); string text1 = ""; string text2 = ""; foreach (string w in words) { if (text1.Length < min) { if (text1.Length == 0) { text1 = w; continue; } if (w.Length + text1.Length <= max) text1 += " " + w; } else text2 += w + " "; } text1 = text1.Trim(); text2 = text2.Trim(); if (text2.Length > 0) { text1 += Environment.NewLine + ConvertToMultiLine(text2, min, max); } return text1; } } }
برنامه مورد نظر را به طور کامل میتوانید از اینجا یا اینجا به صورت فایل نهایی و هم سورس دریافت کنید.
مطالب
ASP.NET MVC #13
اعتبار سنجی اطلاعات ورودی در فرمهای ASP.NET MVC
زمانیکه شروع به دریافت اطلاعات از کاربران کردیم، نیاز خواهد بود تا اعتبار اطلاعات ورودی را نیز ارزیابی کنیم. در ASP.NET MVC، به کمک یک سری متادیتا، نحوهی اعتبار سنجی، تعریف شده و سپس فریم ورک بر اساس این ویژگیها، به صورت خودکار اعتبار اطلاعات انتساب داده شده به خواص یک مدل را در سمت کلاینت و همچنین در سمت سرور بررسی مینماید.
این ویژگیها در اسمبلی System.ComponentModel.DataAnnotations.dll قرار دارند که به صورت پیش فرض در هر پروژه جدید ASP.NET MVC لحاظ میشود.
یک مثال کاربردی
مدل زیر را به پوشه مدلهای یک پروژه جدید خالی ASP.NET MVC اضافه کنید:
using System;
using System.ComponentModel.DataAnnotations;
namespace MvcApplication9.Models
{
public class Customer
{
public int Id { set; get; }
[Required(ErrorMessage = "Name is required.")]
[StringLength(50)]
public string Name { set; get; }
[Display(Name = "Email address")]
[Required(ErrorMessage = "Email address is required.")]
[RegularExpression(@"\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*",
ErrorMessage = "Please enter a valid email address.")]
public string Email { set; get; }
[Range(0, 10)]
[Required(ErrorMessage = "Rating is required.")]
public double Rating { set; get; }
[Display(Name = "Start date")]
[Required(ErrorMessage = "Start date is required.")]
public DateTime StartDate { set; get; }
}
}
سپس کنترلر جدید زیر را نیز به برنامه اضافه نمائید:
using System.Web.Mvc;
using MvcApplication9.Models;
namespace MvcApplication9.Controllers
{
public class CustomerController : Controller
{
[HttpGet]
public ActionResult Create()
{
var customer = new Customer();
return View(customer);
}
[HttpPost]
public ActionResult Create(Customer customer)
{
if (this.ModelState.IsValid)
{
//todo: save data
return Redirect("/");
}
return View(customer);
}
}
}
بر روی متد Create کلیک راست کرده و گزینه Add view را انتخاب کنید. در صفحه باز شده، گزینه Create a strongly typed view را انتخاب کرده و مدل را Customer انتخاب کنید. همچنین قالب Scaffolding را نیز بر روی Create قرار دهید.
توضیحات تکمیلی
همانطور که در مدل برنامه ملاحظه مینمائید، به کمک یک سری متادیتا یا اصطلاحا data annotations، تعاریف اعتبار سنجی، به همراه عبارات خطایی که باید به کاربر نمایش داده شوند، مشخص شده است. ویژگی Required مشخص میکند که کاربر مجبور است این فیلد را تکمیل کند. به کمک ویژگی StringLength، حداکثر تعداد حروف قابل قبول مشخص میشود. با استفاده از ویژگی RegularExpression، مقدار وارد شده با الگوی عبارت باقاعده مشخص گردیده، مقایسه شده و در صورت عدم تطابق، پیغام خطایی به کاربر نمایش داده خواهد شد. به کمک ویژگی Range، بازه اطلاعات قابل قبول، مشخص میگردد.
ویژگی دیگری نیز به نام System.Web.Mvc.Compare مهیا است که برای مقایسه بین مقادیر دو خاصیت کاربرد دارد. برای مثال در یک فرم ثبت نام، عموما از کاربر درخواست میشود که کلمه عبورش را دوبار وارد کند. ویژگی Compare در یک چنین مثالی کاربرد خواهد داشت.
در مورد جزئیات کنترلر تعریف شده در قسمت 11 مفصل توضیح داده شد. برای مثال خاصیت this.ModelState.IsValid مشخص میکند که آیا کارmodel binding موفق بوده یا خیر و همچنین اعتبار سنجیهای تعریف شده نیز در اینجا تاثیر داده میشوند. بنابراین بررسی آن پیش از ذخیره سازی اطلاعات ضروری است.
در حالت HttpGet صفحه ورود اطلاعات به کاربر نمایش داده خواهد شد و در حالت HttpPost، اطلاعات وارد شده دریافت میگردد. اگر دست آخر، ModelState معتبر نبود، همان اطلاعات نادرست وارد شده به کاربر مجددا نمایش داده خواهد شد تا فرم پاک نشود و بتواند آنها را اصلاح کند.
برنامه را اجرا کنید. با مراجعه به مسیر http://localhost/customer/create، صفحه ورود اطلاعات کاربر نمایش داده خواهد شد. در اینجا برای مثال در قسمت ورود اطلاعات آدرس ایمیل، مقدار abc را وارد کنید. بلافاصله خطای اعتبار سنجی عدم اعتبار مقدار ورودی نمایش داده میشود. یعنی فریم ورک، اعتبار سنجی سمت کاربر را نیز به صورت خودکار مهیا کرده است.
اگر علاقمند باشید که صرفا جهت آزمایش، اعتبار سنجی سمت کاربر را غیرفعال کنید، به فایل web.config برنامه مراجعه کرده و تنظیم زیر را تغییر دهید:
<appSettings>
<add key="ClientValidationEnabled" value="true"/>
البته این تنظیم تاثیر سراسری دارد. اگر قصد داشته باشیم که این تنظیم را تنها به یک view خاص اعمال کنیم، میتوان از متد زیر کمک گرفت:
@{ Html.EnableClientValidation(false); }
در این حالت اگر مجددا برنامه را اجرا کرده و اطلاعات نادرستی را وارد کنیم، باز هم همان خطاهای تعریف شده، به کاربر نمایش داده خواهد شد. اما اینبار یکبار رفت و برگشت اجباری به سرور صورت خواهد گرفت، زیرا اعتبار سنجی سمت کاربر (که درون مرورگر و توسط کدهای جاوا اسکریپتی اجرا میشود)، غیرفعال شده است. البته امکان غیرفعال کردن جاوا اسکریپت توسط کاربر نیز وجود دارد. به همین جهت بررسی خودکار سمت سرور، امنیت سیستم را بهبود خواهد بخشید.
نحوه تعریف عناصر مرتبط با اعتبار سنجی در Viewهای برنامه نیز به شکل زیر است:
<script src="@Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
@using (Html.BeginForm()) {
@Html.ValidationSummary(true)
<fieldset>
<legend>Customer</legend>
<div class="editor-label">
@Html.LabelFor(model => model.Name)
</div>
<div class="editor-field">
@Html.EditorFor(model => model.Name)
@Html.ValidationMessageFor(model => model.Name)
</div>
همانطور که ملاحظه میکنید به صورت پیش فرض از jQuery validator در سمت کلاینت استفاده شده است. فایل jquery.validate.unobtrusive متعلق به تیم ASP.NET MVC است و کار آن وفق دادن سیستم موجود، با jQuery validator میباشد (validation adapter). در نگارشهای قبلی، از کتابخانههای اعتبار سنجی مایکروسافت استفاده شده بود، اما از نگارش سه به بعد، jQuery به عنوان کتابخانه برگزیده مطرح است.
Unobtrusive همچنین در اینجا به معنای مجزا سازی کدهای جاوا اسکریپتی، از سورس HTML صفحه و استفاده از ویژگیهای data-* مرتبط با HTML5 برای معرفی اطلاعات مورد نیاز اعتبار سنجی است:
<input data-val="true" data-val-required="The Birthday field is required." id="Birthday" name="Birthday" type="text" value="" />
اگر خواستید این مساله را بررسی کنید، فایل web.config قرار گرفته در ریشه اصلی برنامه را باز کنید. در آنجا مقدار UnobtrusiveJavaScriptEnabled را false کرده و بار دیگر برنامه را اجرا کنید. در این حالت کلیه کدهای اعتبار سنجی، به داخل سورس View رندر شده، تزریق میشوند و مجزا از آن نخواهند بود.
نحوهی تعریف این اسکریپتها نیز جالب توجه است. متد Url.Content، یک متد سمت سرور میباشد که در زمان اجرای برنامه، مسیر نسبی وارد شده را بر اساس ساختار سایت اصلاح میکند. حرف ~ بکارگرفته شده، در ASP.NET به معنای ریشه سایت است. بنابراین مسیر نسبی تعریف شده از ریشه سایت شروع و تفسیر میشود.
اگر از این متد استفاده نکنیم، مجبور خواهیم شد که مسیرهای نسبی را به شکل زیر تعریف کنیم:
<script src="../../Scripts/customvaildation.js" type="text/javascript"></script>
در این حالت بسته به محل قرارگیری صفحات و همچنین برنامه در سایت، ممکن است آدرس فوق صحیح باشد یا خیر. اما استفاده از متد Url.Content، کار مسیریابی نهایی را خودکار میکند.
البته اگر به فایل Views/Shared/_Layout.cshtml، مراجعه کنید، تعریف و الحاق کتابخانه اصلی jQuery در آنجا انجام شده است. بنابراین میتوان این دو تعریف دیگر مرتبط با اعتبار سنجی را به آن فایل هم منتقل کرد تا همهجا در دسترس باشند.
توسط متد Html.ValidationSummary، خطاهای اعتبار سنجی مدل که به صورت دستی اضافه شده باشند نمایش داده میشود. این مورد در قسمت 11 توضیح داده شد (چون پارامتر آن true وارد شده، فقط خطاهای سطح مدل را نمایش میدهد).
متد Html.ValidationMessageFor، با توجه به متادیتای یک خاصیت و همچنین استثناهای صادر شده حین model binding خطایی را به کاربر نمایش خواهد داد.
اعتبار سنجی سفارشی
ویژگیهای اعتبار سنجی از پیش تعریف شده، پر کاربردترینها هستند؛ اما کافی نیستند. برای مثال در مدل فوق، StartDate نباید کمتر از سال 2000 وارد شود و همچنین در آینده هم نباید باشد. این موارد اعتبار سنجی سفارشی را چگونه باید با فریم ورک، یکپارچه کرد؟
حداقل دو روش برای حل این مساله وجود دارد:
الف) نوشتن یک ویژگی اعتبار سنجی سفارشی
ب) پیاده سازی اینترفیس IValidatableObject
تعریف یک ویژگی اعتبار سنجی سفارشی
using System;
using System.ComponentModel.DataAnnotations;
namespace MvcApplication9.CustomValidators
{
public class MyDateValidator : ValidationAttribute
{
public int MinYear { set; get; }
public override bool IsValid(object value)
{
if (value == null) return false;
var date = (DateTime)value;
if (date > DateTime.Now || date < new DateTime(MinYear, 1, 1))
return false;
return true;
}
}
}
برای نوشتن یک ویژگی اعتبار سنجی سفارشی، با ارث بری از کلاس ValidationAttribute شروع میکنیم. سپس باید متد IsValid آنرا تحریف کنیم. اگر این متد false برگرداند به معنای شکست اعتبار سنجی میباشد.
در ادامه برای بکارگیری آن خواهیم داشت:
[Display(Name = "Start date")]
[Required(ErrorMessage = "Start date is required.")]
[MyDateValidator(MinYear = 2000,
ErrorMessage = "Please enter a valid date.")]
public DateTime StartDate { set; get; }
اکنون مجددا برنامه را اجرا نمائید. اگر تاریخ غیرمعتبری وارد شود، اعتبار سنجی سمت سرور رخ داده و سپس نتیجه به کاربر نمایش داده میشود.
اعتبار سنجی سفارشی به کمک پیاده سازی اینترفیس IValidatableObject
یک سؤال: اگر اعتبار سنجی ما پیچیدهتر باشد چطور؟ مثلا نیاز باشد مقادیر دریافتی چندین خاصیت با هم مقایسه شده و سپس بر این اساس تصمیم گیری شود. برای حل این مشکل میتوان از اینترفیس IValidatableObject کمک گرفت. در این حالت مدل تعریف شده باید اینترفیس یاد شده را پیاده سازی نماید. برای مثال:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using MvcApplication9.CustomValidators;
namespace MvcApplication9.Models
{
public class Customer : IValidatableObject
{
//... same as before
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
var fields = new[] { "StartDate" };
if (StartDate > DateTime.Now || StartDate < new DateTime(2000, 1, 1))
yield return new ValidationResult("Please enter a valid date.", fields);
if (Rating > 4 && StartDate < new DateTime(2003, 1, 1))
yield return new ValidationResult("Accepted date should be greater than 2003", fields);
}
}
}
در اینجا در متد Validate، فرصت خواهیم داشت تا به مقادیر کلیه خواص تعریف شده در مدل دسترسی پیدا کرده و بر این اساس اعتبار سنجی بهتری را انجام دهیم. اگر اطلاعات وارد شده مطابق منطق مورد نظر نباشند، کافی است توسط yield return new ValidationResult، یک پیغام را به همراه فیلدهایی که باید این پیغام را نمایش دهند، بازگردانیم.
به این نوع مدلها، self validating models هم گفته میشود.
یک نکته:
از MVC3 به بعد، حین کار با ValidationAttribute، امکان تحریف متد IsValid به همراه پارامتری از نوع ValidationContext نیز وجود دارد. به این ترتیب میتوان به اطلاعات سایر خواص نیز دست یافت. البته در این حالت نیاز به استفاده از Reflection خواهد بود و پیاده سازی IValidatableObject، طبیعیتر به نظر میرسد:
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var info = validationContext.ObjectType.GetProperty("Rating");
//...
return ValidationResult.Success;
}
فعال سازی سمت کلاینت اعتبار سنجیهای سفارشی
اعتبار سنجیهای سفارشی تولید شده تا به اینجا، تنها سمت سرور است که فعال میشوند. به عبارتی باید یکبار اطلاعات به سرور ارسال شده و در بازگشت، نتیجه عملیات به کاربر نمایش داده خواهد شد. اما ویژگیهای توکاری مانند Required و Range و امثال آن، علاوه بر سمت سرور، سمت کاربر هم فعال هستند و اگر جاوا اسکریپت در مرورگر کاربر غیرفعال نشده باشد، نیازی به ارسال اطلاعات یک فرم به سرور جهت اعتبار سنجی اولیه، نخواهد بود.
در اینجا باید سه مرحله برای پیاده سازی اعتبار سنجی سمت کلاینت طی شود:
الف) ویژگی سفارشی اعتبار سنجی تعریف شده باید اینترفیس IClientValidatable را پیاده سازی کند.
ب) سپس باید متد jQuery validation متناظر را پیاده سازی کرد.
ج) و همچنین مانند تیم ASP.NET MVC، باید unobtrusive adapter خود را نیز پیاده سازی کنیم. به این ترتیب متادیتای ASP.NET MVC به فرمتی که افزونه jQuery validator آنرا درک میکند، وفق داده خواهد شد.
در ادامه، تکمیل کلاس سفارشی MyDateValidator را ادامه خواهیم داد:
using System;
using System.ComponentModel.DataAnnotations;
using System.Web.Mvc;
using System.Collections.Generic;
namespace MvcApplication9.CustomValidators
{
public class MyDateValidator : ValidationAttribute, IClientValidatable
{
// ... same as before
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(
ModelMetadata metadata,
ControllerContext context)
{
var rule = new ModelClientValidationRule
{
ValidationType = "mydatevalidator",
ErrorMessage = FormatErrorMessage(metadata.GetDisplayName())
};
yield return rule;
}
}
}
در اینجا نحوه پیاده سازی اینترفیس IClientValidatable را ملاحظه مینمائید. ValidationType، نام متدی خواهد بود که در سمت کلاینت، کار بررسی اعتبار دادهها را به عهده خواهد گرفت.
سپس برای مثال یک فایل جدید به نام customvaildation.js به پوشه اسکریپتهای برنامه با محتوای زیر اضافه خواهیم کرد:
/// <reference path="jquery-1.5.1-vsdoc.js" />
/// <reference path="jquery.validate-vsdoc.js" />
/// <reference path="jquery.validate.unobtrusive.js" />
jQuery.validator.addMethod("mydatevalidator",
function (value, element, param) {
return Date.parse(value) < new Date();
});
jQuery.validator.unobtrusive.adapters.addBool("mydatevalidator");
توسط referenceهایی که مشاهده میکنید، intellisense جیکوئری در VS.NET فعال میشود.
سپس به کمک متد jQuery.validator.addMethod، همان مقدار ValidationType پیشین را معرفی و در ادامه بر اساس مقدار value دریافتی، تصمیم گیری خواهیم کرد. اگر خروجی false باشد، به معنای شکست اعتبار سنجی است.
همچنین توسط متد jQuery.validator.unobtrusive.adapters.addBool، این متد جدید را به مجموعه وفق دهندهها اضافه میکنیم.
و در آخر این فایل جدید باید به View مورد نظر یا فایل master page سیستم اضافه شود:
<script src="@Url.Content("~/Scripts/customvaildation.js")" type="text/javascript"></script>
تغییر رنگ و ظاهر پیغامهای اعتبار سنجی
اگر از رنگ پیش فرض قرمز پیغامهای اعتبار سنجی خرسند نیستید، باید اندکی CSS سایت را ویرایش کرد که شامل اعمال تغییرات به موارد ذیل خواهد شد:
1. .field-validation-error
2. .field-validation-valid
3. .input-validation-error
4. .input-validation-valid
5. .validation-summary-errors
6. .validation-summary-valid
نحوه جدا سازی تعاریف متادیتا از کلاسهای مدل برنامه
فرض کنید مدلهای برنامه شما به کمک یک code generator تولید میشوند. در این حالت هرگونه ویژگی اضافی تعریف شده در این کلاسها پس از تولید مجدد کدها از دست خواهند رفت. به همین منظور امکان تعریف مجزای متادیتاها نیز پیش بینی شده است:
[MetadataType(typeof(CustomerMetadata))]
public partial class Customer
{
class CustomerMetadata
{
}
}
public partial class Customer : IValidatableObject
{
حالت کلی روش انجام آن هم به شکلی است که ملاحظه میکنید. کلاس اصلی، به صورت partial معرفی خواهد شد. سپس کلاس partial دیگری نیز به همین نام که در برگیرنده یک کلاس داخلی دیگر برای تعاریف متادیتا است، به پروژه اضافه میگردد. به کمک ویژگی MetadataType، کلاسی که قرار است ویژگیهای خواص از آن خوانده شود، معرفی میگردد. موارد عنوان شده، شکل کلی این پیاده سازی است. برای نمونه اگر با WCF RIA Services کار کرده باشید، از این روش زیاد استفاده میشود. کلاس خصوصی تو در توی تعریف شده صرفا وظیفه ارائه متادیتاهای تعریف شده را به فریم ورک خواهد داشت و هیچ کاربرد دیگری ندارد.
در ادامه کلیه خواص کلاس Customer به همراه متادیتای آنها باید به کلاس CustomerMetadata منتقل شوند. اکنون میتوان تمام متادیتای کلاس اصلی Customer را حذف کرد.
اعتبار سنجی از راه دور (remote validation)
فرض کنید شخصی مشغول به پر کردن فرم ثبت نام، در سایت شما است. پس از اینکه نام کاربری دلخواه خود را وارد کرد و مثلا به فیلد ورود کلمه عبور رسید، در همین حال و بدون ارسال کل صفحه به سرور، به او پیغام دهیم که نام کاربری وارد شده، هم اکنون توسط شخص دیگری در حال استفاده است. این مکانیزم از ASP.NET MVC3 به بعد تحت عنوان Remote validation در دسترس است و یک درخواست Ajaxایی خودکار را به سرور ارسال خواهد کرد و نتیجه نهایی را به کاربر نمایش میدهد؛ کارهایی که به سادگی توسط کدهای جاوا اسکریپتی قابل مدیریت نیستند و نیاز به تعامل با سرور، در این بین وجود دارد. پیاده سازی آن هم به نحو زیر است:
برای مثال خاصیت Name را در مدل برنامه به نحو زیر تغییر دهید:
[Required(ErrorMessage = "Name is required.")]
[StringLength(50)]
[System.Web.Mvc.Remote(action: "CheckUserNameAndEmail",
controller: "Customer",
AdditionalFields = "Email",
HttpMethod = "POST",
ErrorMessage = "Username is not available.")]
public string Name { set; get; }
سپس متد زیر را نیز به کنترلر Customer اضافه کنید:
[HttpPost]
[OutputCache(Location = OutputCacheLocation.None, NoStore = true)]
public ActionResult CheckUserNameAndEmail(string name, string email)
{
if (name.ToLowerInvariant() == "vahid") return Json(false);
if (email.ToLowerInvariant() == "name@site.com") return Json(false);
//...
return Json(true);
}
توضیحات:
توسط ویژگی System.Web.Mvc.Remote، نام کنترلر و متدی که در آن قرار است به صورت خودکار توسط jQuery Ajax فراخوانی شود، مشخص خواهند شد. همچنین اگر نیاز بود فیلدهای دیگری نیز به این متد کنترلر ارسال شوند، میتوان آنها را توسط خاصیت AdditionalFields، مشخص کرد.
سپس در کدهای کنترلر مشخص شده، متدی با پارامترهای خاصیت مورد نظر و فیلدهای اضافی دیگر، تعریف میشود. در اینجا فرصت خواهیم داشت تا برای مثال پس از بررسی بانک اطلاعاتی، خروجی Json ایی را بازگردانیم. return Json false به معنای شکست اعتبار سنجی است.
توسط ویژگی OutputCache، از کش شدن نتیجه درخواستهای Ajaxایی جلوگیری کردهایم. همچنین نوع درخواست هم جهت امنیت بیشتر، به HttpPost محدود شده است.
تمام کاری که باید انجام شود همین مقدار است و مابقی مسایل مرتبط با اعمال و پیاده سازی آن خودکار است.
استفاده از مکانیزم اعتبار سنجی مبتنی برمتادیتا در خارج از ASP.Net MVC
مباحثی را که در این قسمت ملاحظه نمودید، منحصر به ASP.NET MVC نیستند. برای نمونه توسط متد الحاقی زیر نیز میتوان یک مدل را مثلا در یک برنامه کنسول هم اعتبار سنجی کرد. بدیهی است در این حالت نیاز خواهد بود تا ارجاعی را به اسمبلی System.ComponentModel.DataAnnotations، به برنامه اضافه کنیم و تمام عملیات هم دستی است و فریم ورک ویژهای هم وجود ندارد تا یک سری از کارها را به صورت خودکار انجام دهد.
using System.ComponentModel.DataAnnotations;
namespace MvcApplication9.Helper
{
public static class ValidationHelper
{
public static bool TryValidateObject(this object instance)
{
return Validator.TryValidateObject(instance, new ValidationContext(instance, null, null), null);
}
}
}
با توجه به تغییرات اخیر در سیست مسیریابی React-router-dom نسخه 6 ، جهت محافظت از مسیرها میتوان کد زیر را مورد بررسی قرار داد.
نحوه استفاده
const ProtectedRoute = ( {children,roles }) => { const isLoggedIn=authService.isLoggedIn(); if (!isLoggedIn) { return <Navigate to="/login" replace />; } if(roles) { //checkRoles if(result_roles===false) return <Navigate to="/login" replace />; } return children; }; export default ProtectedRoute
<Routes> <Route path="/product/new" element={ <ProtectedRoute roles={["hesabdar", "anbardar"]}> <AdminTemplate> <NewProduct/> </AdminTemplate> </ProtectedRoute> }/> </Routes>
با توجه به اینکه در نسخه اخیر امکان استفاده از هیچ تگی جز Route در زیر مجموعه تگ Routes نیست. باید این کامپوننت در داخل خصوصیت element تعریف گردد. در این حالت نیز به راحتی امکان تعریف قالب پدر یا مستر نیز وجود دارد.
در sfc مربوط به ProtctedRoute ابتدا دو فیلد به نامهای children و roles از props دریافت میگردد. children یک خصوصیتی است که توسط خود rect فراهم شده و شامل کامپوننتهای فرزند میباشد و roles نیز یک فیلد تعریف شده توسط کاربر باید باشد که مشخص میکند چه نقش یا نقش هایی به این آدرس دسترسی دارند. در این کامپوننت ابتدا بررسی میشود که اگر کاربر لاگین نکرده است باید به صفحه لاگین هدایت شود و در صورتی که roles مورد نظر نیز وارد شده است مقادیر آن بررسی میگردد و اگر شامل هیچ یک از نقشهای تعریف شده نبود مجددا به صفحه لاگین هدایت میشود و در صورتی که شروط بالا تایید شد مقدار children بازگردانده میشود.
در مطلب «Angular CLI - قسمت پنجم - ساخت و توزیع برنامه» با نحوهی ساخت و توزیع برنامههای Angular، در دو حالت محیط توسعه و محیط ارائهی نهایی آشنا شدیم. همچنین در مطلب «یکپارچه سازی Angular CLI و ASP.NET Core در VS 2017» نحوهی ترکیب یک برنامهی ASP.NET Core و Angular را بررسی کردیم. در اینجا میخواهیم فایل index.html ایی را که Angular CLI تولید میکند، با فایل Layout برنامههای ASP.NET Core جایگزین کنیم؛ تا بتوانیم در صورت نیاز، سفارشی سازیهای بیشتری را به صفحهی اول سایت اعمال نمائیم.
استفاده از Tag Helpers ویژهی ASP.NET Core برای مدیریت محیطهای توسعه و تولید
فایلهای برنامهی تک صفحهای تولید شدهی توسط Angular CLI، در نهایت یک چنین شکلی را خواهند داشت:
این فایلها نیز در حالت توسعه تهیه شدهاند. در یک برنامهی واقعی، صفحهی سادهی index.html تولیدی آن، تنها میتواند یک قالب شروع به کار باشد و نه فایل نهایی که قرار است ارائه شود. نیاز است به این فایل تگهای بیشتری را اضافه کرد و سفارشی سازیهای خاصی را به آن اعمال نمود. در این حالت با توجه به بازنویسی و تولید مجدد این فایل در هر بار ساخت برنامه، میتوان از فایل Layout پروژهی ASP.NET Core جاری استفاده کرد. به این ترتیب از مزایای Razor و تمام زیرساختی که در اختیار داریم نیز محروم نخواهیم شد.
بنابراین تنها کاری را که باید انجام دهیم، کپی ساختار فایل index.html تولیدی به فایل Layout برنامه است.
مشکل! در حالت توسعه، نام فایلهای تولید شده به همین سادگی است که ملاحظه میکنید. اما در حالت ارائهی نهایی، این فایلها به همراه یک هش نیز تولید میشوند (پیاده سازی مفهوم cache busting و اجبار به بهروز رسانی کش مرورگر، باتوجه به تغییر آدرس فایلها)؛ مانند vendor.ea3f8329096dbf5632af.bundle.js
راه حل اول: تولید فایلهای نهایی بدون هش
در حین ساخت و تولید یک برنامهی Angular CLI در حالت ارائهی نهایی، تنها کافی است سوئیچ output-hashing، به none تنظیم شود، تا این هشها به نام فایلهای تولیدی اضافه نشوند.
درکل بهتر است از این روش استفاده نشود، چون با وجود پروکسیهای کش کردن اطلاعات در بین راه، احتمال اینکه کاربران نگارشهای قدیمی برنامه را مشاهده کنند، بسیار زیاد است.
راه حل دوم: تگ Script در ASP.NET Core اجازهی ذکر تمام فایلهای اسکریپت یک پوشه را نیز میدهد
هرچند این قابلیت جالب است و سبب الحاق یکجای تمام فایلهای js موجود در پوشهی wwwroot خواهد شد، اما پاسخگوی کار ما نخواهد بود؛ چون ترتیب قرارگیری این فایلها مهم است.
راه حل واقعی
در اینجا کدهای کامل فایل Views\Shared\_Layout.cshtml را که میتواند جایگزین فایل index.html تولیدی توسط Angular CLI باشد، ملاحظه میکنید:
در اینجا دو حالت توسعه و همچنین ارائهی نهایی مدنظر قرار گرفتهاند. در حالت توسعه، دقیقا از همان نامهای سادهی تولیدی استفاده شدهاست و در حالت ارائهی نهایی چون نام فایلها به صورت
تولید میشوند، میتوان قسمت هش را با * جایگزین کرد؛ مانند "asp-src-include="~/vendor*.js
همچنین باید دقت داشت که در حالت توسعه، تمام شیوه نامههای برنامه در فایل styles.bundle.js قرار میگیرند. اما در حالت ارائهی نهایی، این فایل وجود نداشته و با نام کلی styles*.css تولید میشود که باید در head صفحه قرار گیرد (مانند تنظیمات حالت تولید در Layout فوق).
اصلاح قسمت URL Rewrite برنامه
در حالت کار با برنامههای تک صفحهای وب، در اولین درخواست رسیدهی به برنامه ممکن است آدرسی درخواست شود که معادل کنترلر و اکشن متدی را در برنامهی سمت سرور نداشته باشد. در این حالت کاربر را به همان صفحهی index.html هدایت میکنیم تا سیستم مسیریابی سمت کلاینت، کار نمایش آن صفحه را انجام دهد:
از آنجائیکه پس از اصلاحات فوق دیگر از این فایل استفاده نمیشود، باید تغییر ذیل را نیز اعمال کرد:
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید.
استفاده از Tag Helpers ویژهی ASP.NET Core برای مدیریت محیطهای توسعه و تولید
فایلهای برنامهی تک صفحهای تولید شدهی توسط Angular CLI، در نهایت یک چنین شکلی را خواهند داشت:
این فایلها نیز در حالت توسعه تهیه شدهاند. در یک برنامهی واقعی، صفحهی سادهی index.html تولیدی آن، تنها میتواند یک قالب شروع به کار باشد و نه فایل نهایی که قرار است ارائه شود. نیاز است به این فایل تگهای بیشتری را اضافه کرد و سفارشی سازیهای خاصی را به آن اعمال نمود. در این حالت با توجه به بازنویسی و تولید مجدد این فایل در هر بار ساخت برنامه، میتوان از فایل Layout پروژهی ASP.NET Core جاری استفاده کرد. به این ترتیب از مزایای Razor و تمام زیرساختی که در اختیار داریم نیز محروم نخواهیم شد.
بنابراین تنها کاری را که باید انجام دهیم، کپی ساختار فایل index.html تولیدی به فایل Layout برنامه است.
مشکل! در حالت توسعه، نام فایلهای تولید شده به همین سادگی است که ملاحظه میکنید. اما در حالت ارائهی نهایی، این فایلها به همراه یک هش نیز تولید میشوند (پیاده سازی مفهوم cache busting و اجبار به بهروز رسانی کش مرورگر، باتوجه به تغییر آدرس فایلها)؛ مانند vendor.ea3f8329096dbf5632af.bundle.js
راه حل اول: تولید فایلهای نهایی بدون هش
ng build -prod --output-hashing=none
درکل بهتر است از این روش استفاده نشود، چون با وجود پروکسیهای کش کردن اطلاعات در بین راه، احتمال اینکه کاربران نگارشهای قدیمی برنامه را مشاهده کنند، بسیار زیاد است.
راه حل دوم: تگ Script در ASP.NET Core اجازهی ذکر تمام فایلهای اسکریپت یک پوشه را نیز میدهد
<script type="text/javascript" asp-src-include="*.js"></script>
راه حل واقعی
در اینجا کدهای کامل فایل Views\Shared\_Layout.cshtml را که میتواند جایگزین فایل index.html تولیدی توسط Angular CLI باشد، ملاحظه میکنید:
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <link rel="icon" type="image/x-icon" href="favicon.ico"> <title>ng2-lab</title> <base href="/"> <environment names="Development"> </environment> <environment names="Staging,Production"> <link rel="stylesheet" asp-href-include="~/styles*.css" /> </environment> </head> <body> @RenderBody() <app-root></app-root> <environment names="Development"> <script type="text/javascript" src="/inline.bundle.js"></script> <script type="text/javascript" src="/polyfills.bundle.js"></script> <script type="text/javascript" src="/scripts.bundle.js"></script> <script type="text/javascript" src="/styles.bundle.js"></script> <script type="text/javascript" src="/vendor.bundle.js"></script> <script type="text/javascript" src="/main.bundle.js"></script> </environment> <environment names="Production,Staging"> <script type="text/javascript" asp-src-include="~/inline*.js"></script> <script type="text/javascript" asp-src-include="~/polyfills*.js"></script> <script type="text/javascript" asp-src-include="~/scripts*.js"></script> <script type="text/javascript" asp-src-include="~/vendor*.js"></script> <script type="text/javascript" asp-src-include="~/main*.js"></script> </environment> </body> </html>
[name].[hash].bundle.js
همچنین باید دقت داشت که در حالت توسعه، تمام شیوه نامههای برنامه در فایل styles.bundle.js قرار میگیرند. اما در حالت ارائهی نهایی، این فایل وجود نداشته و با نام کلی styles*.css تولید میشود که باید در head صفحه قرار گیرد (مانند تنظیمات حالت تولید در Layout فوق).
اصلاح قسمت URL Rewrite برنامه
در حالت کار با برنامههای تک صفحهای وب، در اولین درخواست رسیدهی به برنامه ممکن است آدرسی درخواست شود که معادل کنترلر و اکشن متدی را در برنامهی سمت سرور نداشته باشد. در این حالت کاربر را به همان صفحهی index.html هدایت میکنیم تا سیستم مسیریابی سمت کلاینت، کار نمایش آن صفحه را انجام دهد:
app.Use(async (context, next) => { await next(); var path = context.Request.Path.Value; if (path != null && context.Response.StatusCode == 404 && !Path.HasExtension(path) && !path.StartsWith("/api/", StringComparison.OrdinalIgnoreCase)) { context.Request.Path = "/index.html"; await next(); } });
//context.Request.Path = "/index.html"; context.Request.Path = "/"; // since we are using views/shared/_layout.cshtml now.
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید.