در این حالت با توجه به عدم وجود تگ فرم ، اعتبار سنجی به چه شکل انجام شود ؟(کدام یک از حالات فرمهای مبتنی بر قالب یا فرمهای مبتنی بر مدل در این سناریو سازگارتر است ؟)
در ادامه مباحث بررسی اجرای برنامههای دات نت بر روی لینوکس، قصد داریم برنامههای ASP.NET را به کمک Mono 3.0 و یک وب سرور لینوکسی، بر روی Ubuntu اجرا کنیم.
پیشنیازها
دو پروژه خالی ASP.NET Web forms و ASP.NET MVC را در VS.NET تحت ویندوز ایجاد نمائید. آنها را یکبار کامپایل کرده و اجرا کنید. سپس فایلهای آنها را به ubuntu منتقل کنید (پوشههای bin پروژهها فراموش نشوند؛ خصوصا نگارش MVC که به همراه یک سری کتابخانه جانبی است).
برای انتقال فایلها به لینوکس، اگر از VMWare workstation برای اجرا و آزمایش Ubuntu استفاده میکنید، کپی و paste مستقیم فایلها از ویندوز به درون ماشین مجازی لینوکس پشتیبانی میشود.
نصب وب سرور آزمایشی مونو یا XSP
اگر نیاز به یک وب سرور آزمایشی، چیزی شبیه به وب سرور توکار VS.NET داشتید، پروژه XSP جهت این نوع آزمایشات ایجاد شده است.
پس از نصب آن (که به همراه همان بسته PPA قسمت قبل، هم اکنون بر روی سیستم شما نصب است)، در ترمینال لینوکس، با استفاده از دستور cd به ریشه وب سایت خود وارد شوید، سپس دستور xsp4 را اجرا کنید تا وب سرور xsp4 مشغول هاست سایت شما شود (برای اجرا در مسیر /opt/bin/xsp4 نصب شده است).
اجرای برنامه ASP.NET Web forms 4 توسط XSP
بدون هیچ مشکل خاصی در همان ابتدای کار اجرا شد (البته باید دقت داشت که لینوکس به کوچکی و بزرگی حروف حساس است. یعنی حتما باید Default.aspx وارد شود و نه default.aspx):
اجرای برنامه ASP.NET MVC 4 توسط XSP
اجرا نشد! پیام میدهد که
و یا
علت اینجا است که XSP4 همراه با نسخه PPA، قدیمی است. بنابراین باید نسخه اصلی را از مخزن کد آن دریافت و کامپایل کنیم. پیشنیازهای اینکار مانند Git و Mono، در قسمت قبل دریافت شدند. سپس فرامین ذیل را در خط فرمان لینوکس اجرا کنید:
پس از کامپایل، اگر این نگارش جدید را اجرا کنید، به خطای ذیل خواهید رسید:
برای رفع این مشکل باید اینبار وب سرور جدید را با فرمان sudo یا دسترسی مدیریتی اجرا کنید تا مشکل برطرف شود.
البته من سورس دریافت شده را در خود monodevelop کامپایل کردم (فایل sln آنرا در monodevelop باز کرده و پروژه را build کنید). در این حالت دو فایل Mono.WebServer.dll و Mono.WebServer.XSP.exe در پوشه xsp/src/Mono.WebServer.XSP/bin/Debug ظاهر میشوند.
یکی دیگر از دلایل ظاهر شدن خطای فوق، نیاز به نصب این دو فایل در GAC است که به نحو زیر قابل انجام میباشد:
بعد این دو فایل dll و exe را در پوشه برنامه MVC خود کپی کنید و سپس دستور ذیل را اجرا نمائید:
اینبار وب سرور جدید، روی پورت 9000 شروع به کار میکند. اکنون اگر در فایرفاکس آدرس http://localhost:9000 را باز کنید، برنامه اجرا شده اما به خطای ذیل خواهید رسید:
برای رفع این مشکل باید اندکی فایل web.config برنامه را ویرایش کرد:
سعی بعدی ... اجرا نشد! با هر بار refresh صفحه یک خطای جدید نمایش میداد که ... Type خاصی را نمیتواند بارگذاری کند (به همراه نام اسمبلی مربوطه). برای رفع این مشکل dllهای ذیل را از پوشه bin پروژه MVC خود که از ویندوز به لینوکس کپی کردهاید، حذف کنید:
Microsoft.Web.Infrastructure.dll
System.Net.Http.dll
System.Net.Http.Formatting.dll
System.Web.Http.dll
System.Web.Http.WebHost.dll
این فایلها توسط تیم Mono به صورت مستقل پیاده سازی شدهاند و نیازی نیست تا از ویندوز به لینوکس کپی شوند.
بعد از حذف این فایلهای اضافی، برنامه ASP.NET MVC نیز اجرا شد:
چند نکته تکمیلی
- نحوه تشخیص موجود بودن یک DLL خاص، در نگارش جاری Mono نصب شده:
- اگر میخواهید مطمئن شوید که تمام اسمبلیهای موجود در GAC درست نصب شدهاند یا خیر، فرمان ذیل را اجرا کنید:
- در نسخه لینوکسی System.Web ممکن است یک سری از فضاهای نام هنوز موجود نباشند. لیست آنها را در این آدرس میتوانید مشاهده کنید:
پیشنیازها
دو پروژه خالی ASP.NET Web forms و ASP.NET MVC را در VS.NET تحت ویندوز ایجاد نمائید. آنها را یکبار کامپایل کرده و اجرا کنید. سپس فایلهای آنها را به ubuntu منتقل کنید (پوشههای bin پروژهها فراموش نشوند؛ خصوصا نگارش MVC که به همراه یک سری کتابخانه جانبی است).
برای انتقال فایلها به لینوکس، اگر از VMWare workstation برای اجرا و آزمایش Ubuntu استفاده میکنید، کپی و paste مستقیم فایلها از ویندوز به درون ماشین مجازی لینوکس پشتیبانی میشود.
نصب وب سرور آزمایشی مونو یا XSP
اگر نیاز به یک وب سرور آزمایشی، چیزی شبیه به وب سرور توکار VS.NET داشتید، پروژه XSP جهت این نوع آزمایشات ایجاد شده است.
پس از نصب آن (که به همراه همان بسته PPA قسمت قبل، هم اکنون بر روی سیستم شما نصب است)، در ترمینال لینوکس، با استفاده از دستور cd به ریشه وب سایت خود وارد شوید، سپس دستور xsp4 را اجرا کنید تا وب سرور xsp4 مشغول هاست سایت شما شود (برای اجرا در مسیر /opt/bin/xsp4 نصب شده است).
اجرای برنامه ASP.NET Web forms 4 توسط XSP
بدون هیچ مشکل خاصی در همان ابتدای کار اجرا شد (البته باید دقت داشت که لینوکس به کوچکی و بزرگی حروف حساس است. یعنی حتما باید Default.aspx وارد شود و نه default.aspx):
اجرای برنامه ASP.NET MVC 4 توسط XSP
اجرا نشد! پیام میدهد که
"Missing method System.Web.Security.FormsAuthentication::get_IsEnabled() in assembly System.Web.dll
Compiler Error Message: CS1703: An assembly with the same identity `mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' has already been imported. Consider removing one of the references
git clone git://github.com/mono/xsp.git cd xsp ./autogen.sh --prefix=/opt make sudo make install
System.IO.FileNotFoundException: Could not load file or assembly XSP, Version=3.0.0.0
البته من سورس دریافت شده را در خود monodevelop کامپایل کردم (فایل sln آنرا در monodevelop باز کرده و پروژه را build کنید). در این حالت دو فایل Mono.WebServer.dll و Mono.WebServer.XSP.exe در پوشه xsp/src/Mono.WebServer.XSP/bin/Debug ظاهر میشوند.
یکی دیگر از دلایل ظاهر شدن خطای فوق، نیاز به نصب این دو فایل در GAC است که به نحو زیر قابل انجام میباشد:
cd xsp/src/Mono.WebServer.XSP/bin/Debug sudo gacutil -i Mono.WebServer.XSP.exe sudo gacutil -i Mono.WebServer.dll
cd myMvcAppPath sudo mono Mono.WebServer.XSP.exe
CS0234: The type or namespace name `Helpers' does not exist in the namespace `System.Web'. Are you missing an assembly reference?
<system.web> <compilation debug="true" targetFramework="4.0"> <assemblies> <add assembly="System.Web.Helpers, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" /> <add assembly="System.Web.Mvc, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" /> </assemblies> </compilation>
Microsoft.Web.Infrastructure.dll
System.Net.Http.dll
System.Net.Http.Formatting.dll
System.Web.Http.dll
System.Web.Http.WebHost.dll
این فایلها توسط تیم Mono به صورت مستقل پیاده سازی شدهاند و نیازی نیست تا از ویندوز به لینوکس کپی شوند.
بعد از حذف این فایلهای اضافی، برنامه ASP.NET MVC نیز اجرا شد:
چند نکته تکمیلی
- نحوه تشخیص موجود بودن یک DLL خاص، در نگارش جاری Mono نصب شده:
$ gacutil -l Microsoft.Web.Infrastructure The following assemblies are installed into the GAC: Microsoft.Web.Infrastructure, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35 Number of items = 1
cd /opt/lib/mono/gac # assuming this is your main gac sudo find . */*/*.dll -exec gacutil -i '{}' \;
مطالب
AngularJS #4
در این قسمت قصد دارم تا یک سیستم ارسال دیدگاه را به کمک Angular پیاده سازی کنم. هدف از این مثال؛ آشنایی با چند Directive توکار Angular و همچنین آموختن چگونگی کار با سرویس http$ برای ارتباط با سرور است.
کدهای HTML زیر را در نظر بگیرید:
<div ng-app="myApp"> <div ng-controller="CommentCtrl"> <div ng-repeat="comment in comments"> <div style="float:right;cursor:pointer;" ng-click="remove(comment.Id,$index);">X</div> <a href="#"> <img style="width:32px;" ng-src="/Content/user.gif" alt="{{comment.Name}}"> </a> <div> <h4>{{comment.Name}}</h4> {{comment.CommentBody}} </div> </div> <div> <form action="/Comment/Add" method="post"> <div> <label for="Name">Name</label> <input id="Name" type="text" name="Name" ng-model="comment.Name" placeholder="Your Name" /> </div> <div> <label for="Email">Email</label> <input id="Email" type="text" name="Email" ng-model="comment.Email" placeholder="Your Email" /> </div> <div> <label for="CommentBody">Comment</label> <textarea id="CommentBody" name="CommentBody" ng-model="comment.CommentBody" placeholder="Your Comment"></textarea> </div> <button type="button" ng-click="addComment()">Send</button> </form> </div> </div> </div>
خب از ابتدا ساختار را مورد بررسی قرار میدهم و موارد ناآشنای آن را توضیح میدهم:
ng-app: خاصیت ng-app جز خواص پیش فرض HTML نیست و یک خاصیت سفارشی است که توسط Angular به صورت پیش فرض تعریف شده است. این خاصیت به Angular میگوید که کدام بخش از DOM باید توسط Angular مدیریت و پردازش شود. در اینجا div ای که با خاصیت ng-app مزین شده است به همراه تمامی عناصر فرزند آن توسط موتور پردازش گر DOM توکار مورد پردازش قرار گرفته و اصطلاحا کامپایل میشود. بله! اینجا از لفظ کامپایل شدن برای بیان این فرآیند استفاده کردم. هیچ کدام از این Directiveهای سفارشی به خودی خود برای مرورگر قابل تفسیر نیست و اینجاست که Angular وارد عمل شده و این Directiveها را به کدهای HTML و جاوا اسکریپت که برای مرورگر قابل فهم است تبدیل میکند. به همین جهت با ng-app مشخص میکنیم که کدام بخش از DOM باید توسط Angular تفسیر و مدیریت شود.شاید این سوال برای شما مطرح شده باشد که در مثال قبلی ng-app مقداری نداشت و برای تگ html تعریف شده بود. پاسخ این است که در مثال قبلی چون برنامهی ما دارای یک ماژول بیشتر نبود میتوانستیم از مقدار دهی ng-app صرف نظر کنیم؛ اما در این مثال ما قصد داریم کمی هم مفهوم ماژول را در Angular بررسی کنیم. در نتیجه در این مثال برنامهی ما از ماژولی به نام myApp تشکیل شده است. دلیل اینکه در این مثال ng-app بر روی یک div تعریف شده است این است که همین قسمت از DOM توسط Angular تفسیر شود برای ما کفایت میکند. هنگامی ng-app را بر روی html تعریف میکنیم که قصد داشته باشیم کل صفحه توسط Angular تفسیر شود.
ng-controller: در Angular کنترلرها تابع سازندهی کلاسهای سادهی جاوا اسکریپتی هستند که به کمک آنها بخشی از صفحه را مدیریت میکنیم. این که کدام بخش از صفحه توسط کدام کلاس کنترل و مدیریت شود، توسط ng-controller مشخص میشود. در اینجا هم عنصری که با ng-controller مشخص شده به همراه تمامی فرزندانش، توسط کلاس جاوا اسکریپتی به نام CommentCtrl مدیریت میشود. در حقیقت ما به کمک ng-controller مشخص میکنیم که کدام قسمت از View توسط کدام Controller مدیریت میشود. مرسوم است که در Angular نام کنترلرها با Ctrl خاتمه یابد.
ng-repeat: همهی نظرات دارای یک قالب html یکسان هستند که به ازای دادههای متفاوت تکرار شده اند. اگر میخواستیم نظرات را استفاده از موتور نمایشی Razor نشان دهیم از یک حلقهی foreach استفاده میکردیم. خبر خوب این است که ng-repeat هم دقیقا به مانند حلقهی foreach عمل میکند.در اینجا عبارت comment in comments دقیقا برابر با آن چیزی است که در یک حلقهی foreach مینوشتیم. Comments در اینجا یک لیست به مانند آرایه ای از comment هست که در کنترلر مقدار دهی شده است. پس اگر با حلقهی foreach مشکلی نداشته باشید با مفهوم ng-repeat هم مشکلی نخواهید داشت و دقیقا به همان شکل عمل مینماید.
ng-click: همان طور که گفتیم Directiveهای تعریف شده میتوانند یک event سفارشی نیز باشند. ng-click هم یک Directive تو کار است که توسط Angular به صورت پیش فرض تعریف شده است. کاملا مشخص است که یک تابع به نام remove تعریف شده است که به هنگام کلیک شدن، فراخوانی میشود. دو پارامتر هم به آن ارسال شده است. اولین پارامتر Id دیدگاه مورد نظر است تا به سرور ارسال شود و از پایگاه داده حذف شود. دومین پارامتر index$ است که یک متغیر ویژه است که توسط Angular در هر بار اجرای حلقهی ng-repeat مقدارش یک واحد افزایش مییابد. index$ هم به تابع remove ارسال میشود تا بتوان فهمید در سمت کلاینت کدام نظر باید حذف شود.
ng-src: از این Directive برای مشخص کردن src عکسها استفاده میشود. البته در این مثال چندان تفاوتی بین ng-src و src معمولی وجود ندارد. ولی اگر آدرس عکس به صورت Content/{{comment.Name}}.gif میبود دیگر وضع فرق میکرد. چرا که مرورگر با دیدن آدرس در src سعی به لود کردن آن عکس میکند و در این حالت در لود کردن آن عکس با شکست روبرو میشود. ng-src سبب میشود تا در ابتدا آدرس عکس توسط Angular تفسیر شود و سپس آن عکس توسط مرورگر لود شود.
{{comment.Name}}: آکلودهای دوتایی برای انقیاد داده (Data Binding) با view-model استفاده میشود. این نوع اقیاد داده در مثالهای قبلی مورد بررسی قرار گرفته است و نکتهی بیشتری در اینجا مطرح نیست.
ng-model: به کمک ng-model میتوان بین متن داخل textbox و خاصیت شی مورد نظر انقیاد داده بر قرار کرد و هر دو طرف از تغییرات یکدیگر آگاه شوند. به این عمل انقیاد داده دوطرفه (Two-Way Data-Binding) میگویند.برای مثال textbox مربوط به نام را به comment.Name و textbox مربوط به email را به comment.Email مقید(bind) شده است. هر تغییری که در محتوای هر کدام از طرفین صورت گیرد دیگری نیز از آن تغییر با خبر شده و آن را نمایش میدهد.
تا به اینجای کار قالب مربوط به HTML را بررسی کردیم. حال به سراغ کدهای جاوا اسکریپت میرویم:
var app = angular.module('myApp', []); app.controller('CommentCtrl', function ($scope, $http) { $scope.comment = {}; $http.get('/Comment/GetAll').success(function (data) { $scope.comments = data; }) $scope.addComment = function () { $http.post("/Comment/Add", $scope.comment).success(function () { $scope.comments.push({ Name: $scope.comment.Name, CommentBody: $scope.comment.CommentBody }); $scope.comment = {}; }); }; $scope.remove = function (id, index) { $http.post("/Comment/Remove", { id: id }).success(function () { $scope.comments.splice(index, 1); }); }; });
در تعریف ng-app اگر به یاد داشته باشید برای آن مقدار myApp در نظر گرفته شده بود. در اینجا هم ما به کمک متغیر سراسری angular که توسط خود کتابخانه تعریف شده است، ماژولی به نام myApp را تعریف کرده ایم. پارامتر دوم را فعلا توضیح نمیدهم، ولی در این حد بدانید که برای تعریف وابستگیهای این ماژول استفاده میشود که من آن را برابر یک آرایه خالی قرار داده ام.
در سطر بعد برای ماژول تعریف شده یک controller تعریف کرده ام. شاید دفعهی اول است که تعریف کنترلر به این شکل را مشاهده میکنید. اما چرا به این شکل کنترلر تعریف شده و به مانند قبل به شکل تابع سازندهی کلاس تعریف نشده است؟
پاسخ این است که اکثر برنامه نویسان از جمله خودم دل خوشی از متغیر سراسری ندارند. در شکل قبلی تعریف کنترلر، کنترلر به شکل یک متغیر سراسری تعریف میشد. اما استفاده از ماژول برای تعریف کنترلر سبب میشود تا کنترلرهای ما روی هوا تعریف نشده باشند و هر یک در جای مناسب خود باشند. به این شکل مدیریت کدهای برنامه نیز سادهتر بود. مثلا اگر کسی از شما بپرسد که فلان کنترلر کجا تعریف شده است؛ به راحتی میگویید که در فلان ماژول برنامه تعریف و مدیریت شده است.
در تابعی که به عنوان کنترلر تعریف شده است، دو پارامتر به عنوان وابستگی درخواست شده است. scope$ که برای ارتباط با view-model و انقیاد داده به کار میرود و http$ که برای ارتباط با سرور به کار میرود. نمونهی مناسب هر دوی این پارامترها توسط سیستم تزریق وابستگی تو کار angular در اختیار کنترلر قرار میگیرد.
قبلا چگونگی استفاده از scope$ برای اعمال انقیاد داده توضیح داده شده است. نکتهی جدیدی که مطرح است چگونگی استفاده از سرویس http$ برای ارتباط با سرور است. سرویس http $ دارای 4 متد put ، post ، get و delete است.
واقعا استفاده از این سرویس کاملا واضح و روشن است. در متد addComment وقتی که دیدگاه مورد نظر اضافه شد، به آرایهی کامنتها یک کامنت جدید میافزاییم و چون انقیاد داده دو طرفه است، بالافاصله دیدگاه جدید نیز در view به نمایش در میآید.کار تابع remove هم بسیار ساده است. با استفاده از index ارسالی، دیدگاه مورد نظر را از آرایهی کامنتها حذف میکنیم و ادامهی کار توسط انقیاد داده دو طرفه انجام میشود.
همان طور که مشاهده میشود مفاهیم انقیاد داده دو طرفه و تزریق وابستگی خودکار سرویسهای مورد نیاز، کار با angularjs را بسیار ساده و راحت کرده است. اصولا در بسیاری از موارد احتیاجی به باز اختراع چرخ نیست و کتابخانهی angular آن را برای ما از قبل تدارک دیده است.
کدهای این مثال ضمیمه شده است. این کدها در Visual Studio 2013 و به کمک ASP.NET MVC 5 و Entity Framework 6 نوشته شده است. سعی شده تا مثال نوشته شده به واقعیت نزدیک باشد. اگر دقت کنید مدل کامنت در مثالی که نوشتم به گونه ای است که دیدگاههای چند سطحی به همراه پاسخ هایش مد نظر بوده است. به عنوان تمرین نمایش درختی این گونه دیدگاهها را به کمک Angular انجام دهید. کافیست Treeview in Angular را جست و جو کنید؛ مطمئنا به نتایج زیادی میرسید. گرچه در مثال ضمیمه شده اگر جست و جو کنید من پیاده سازیش را انجام دادم. هدف از جست و جو در اینترنت مشاهده این است که بیشتر مسائل در Angular از پیش توسط دیگران حل شده است و احتیاجی نیست که شما با چالشهای جدیدی دست و پنجه نرم کنید.
پس به عنوان تمرین، دیدگاههای چند سطحی به همراه پاسخ که نمونه اش را در همین سایتی که درحال مشاهده آن هستید میبینید را به کمک AngularJS پیاده سازی کنید.
در مقالهی بعدی چگونگی انتقال منطق تجاری برنامه از کنترلر به لایه سرویس و چگونگی تعریف سرویس جدید را مورد بررسی قرار میدهم.
در قسمت قبل، اولین کامپوننت React خود را ایجاد کردیم و سپس جزئیات بیشتری از عبارات JSX را مانند نحوهی تعریف المانهای مختلف و تنظیم مقادیر ویژگیهای آنرا بررسی کردیم. در ادامهی همان مثال، در این قسمت، نحوهی نمایش لیستها و تعریف و مدیریت رویدادها را در کامپوننتهای React، بررسی میکنیم.
نحوهی رندر لیستی از اشیاء در کامپوننتهای React
فرض کنید میخواهیم لیستی از تگها را رندر کنیم. برای این منظور ابتدا دادههای مرتبط را به خاصیت state کامپوننت، اضافه میکنیم:
اکنون میخواهیم tags را توسط المانهای ul و ui رندر کنیم. اگر با Angular کار کرده باشید، به همراه یک دایرکتیو ngFor است که توسط آن میتوان یک حلقه را در قالب جاری، پیاده سازی و رندر کرد. اما در React و عبارات JSX، چیزی به نام مفهوم حلقهها وجود خارجی ندارد؛ چون JSX یک templating engine نیست. فقط بیان سادهی المانهایی است که قرار است توسط کامپایلر Babel به کدهای جاوا اسکریپتی ترجمه شوند. بنابراین اکنون این سؤال وجود دارد که چگونه میتوان لیستی از عناصر را در اینجا رندر کرد؟
در مطلب «React 16x - قسمت 3 - بررسی پیشنیازهای جاوا اسکریپتی - بخش 2» در مورد متد Array.map بحث شد. در اینجا میتوان توسط متد map، هر المان آرایهی تگها را به یک المان React تبدیل و سپس رندر کرد:
در این مثال، داخل المان ul، با یک {} شروع میکنیم تا بتوان به صورت پویا به مقدار آرایهی this.state.tags دسترسی پیدا کرد. سپس متد map را بر روی این آرایه فراخوانی میکنیم. متد map، هر عضو آرایهی tags را به callback function آن ارسال کرده و خروجی آنرا به صورت یک عبارت JSX که در نهایت به یک المان جاوا اسکریپتی خالص ترجمه خواهد شد، تبدیل میکند. این فرآیند سبب رندر لیست tags میشود:
هرچند اکنون لیستی از تگها در مرورگر رندر شدهاند، اما در کنسول توسعه دهندگان مرورگر، یک اخطار نیز درج شدهاست. علت اینجا است که React نیاز دارد تا بتواند هر آیتم رندر شده را به صورت منحصربفردی شناسایی کند. هدف این است که بتواند در صورت تغییر state هر المان در DOM مجازی خودش، خیلی سریع تشخیص دهد که چه چیزی تغییر کرده و فقط کدام قسمت خاص را باید در DOM اصلی، درج و به روز رسانی کند. برای رفع این مشکل، ویژگی key را به هر المان li در کدهای فوق اضافه میکنیم:
البته در مثال ما تگها منحصربفرد هستند؛ بنابراین استفادهی از آنها به عنوان key، مشکلی را ایجاد نمیکند. در یک برنامهی مفصلتر، تگها میتوانند شیء بوده و هر شیء دارای خاصیت id باشد که در این حالت فرضی میتوان از tag.id به عنوان key استفاده کرد. همچنین باید دانست که این key فقط نیاز است در لیست ul، منحصربفرد باشد و نیازی نیست تا در کل DOM منحصربفرد باشد.
رندر شرطی عناصر در کامپوننتهای React
در اینجا میخواهیم اگر تگی وجود نداشت، پیام متناسبی ارائه شود؛ در غیراینصورت لیست تگها همانند قبل نمایش داده شود (رندر شرطی یا conditional rendering). برای انجام اینکار در React، برخلاف Angular، دارای دایرکتیوهای ساختاری if/else نیستیم؛ چون همانطور که عنوان شد، JSX یک templating engine نیست. به همین جهت برای رندر شرطی المانها در React، باید از همان جاوا اسکریپت خالص کمک بگیریم:
یک روش حل این مساله، نوشتن متدی است که به همراه یک if/else است. در اینجا اگر آرایهی تگها، دارای عنصری نبود، یک پاراگراف متناظر نمایش داده میشود، در غیراینصورت همان قسمت رندر لیست تگها را که توسعه دادیم، بازگشت میدهیم. بنابراین این متد، دو خروجی JSX را بسته به شرایط مختلف میتواند داشته باشد. سپس از این متد به صورت {()this.renderTags} در متد render اصلی استفاده میکنیم:
برای آزمایش آن هم یکبار آرایهی tags را به نحو زیر خالی کنید:
روش دوم حل این نوع مسالهها، استفاده از روش زیر است؛ در این حالت خاص، فقط یک if را داریم، بدون وجود قسمت else:
ابتدا شرط مدنظر نوشته میشود، سپس پیامی را که باید در این حالت ارائه شود، پس از && مینویسیم. در مثال فوق اگر آرایهی tags خالی باشد، پیامی نمایش داده میشود.
اما این روش چگونه کار میکند؟! در اینجا && را به دو مقدار مشخص اعمال کردهایم. یکی حاصل یک مقایسه است و دیگری یک مقدار رشتهای مشخص. در جاوا اسکریپت برخلاف سایر زبانهای برنامه نویسی، میتوان && را بین دو مقدار غیر Boolean نیز اعمال کرد. در جاوا اسکریپت، یک رشتهی خالی به false تعبیر میشود و اگر تنها دارای یک حرف باشد، true درنظر گرفته میشود. برای نمونه در ترکیب 'true && 'Hi، هر دو قسمت به true تفسیر میشوند. در این حالت موتور جاوا اسکریپت، دومین عبارت (آخرین عبارت && شده) را بازگشت میدهد. همچنین در جاوا اسکریپت عدد صفر به false تفسیر میشود. بنابراین ترکیب true && 'Hi' && 1 مقدار 1 را بازگشت میدهد؛ چون عدد 1 هم از دیدگاه جاوا اسکریپت به true تفسیر خواهد شد.
مدیریت رخدادها در React
همانطور که در تصویر فوق نیز مشاهده میکنید، رخدادهای استاندارد DOM، دارای خواص معادل React ای نیز هستند. برای مثال زمانیکه مینویسیم onClick، دقیقا متناظر است با یک خاصیت المان React در عبارات JSX. بنابراین این نامها حساس به کوچکی و بزرگی حروف نیز هستند.
روش تعریف متدهای رخدادگردان در اینجا، با ذکر فعل handle شروع میشود:
سپس ارجاعی از این متد را (نه فراخوانی آنرا)، به خاصیت برای مثال onClick ارسال میکنیم:
اگر دقت کنید، onClick، ارجاع this.handleIncrement را دریافت کردهاست (یعنی بدون () ذکر شدهاست) و نه فراخوانی این متد را (با ذکر ()).
اکنون اگر این فایل را ذخیره کرده و خروجی را در مرورگر بررسی کنیم، با هربار کلیک بر روی دکمهی Increment، یک console.log صورت میگیرد.
در ادامه میخواهیم در این رخدادگردان، مقدار this.state.count را افزایش دهیم. برای این منظور ابتدا مقدار this.state.count را به نحو زیر لاگ میکنیم:
پس از ذخیرهی فایل و اجرای برنامه، اینبار با کلیک بر روی دکمهی Increment، بلافاصله خطای «Uncaught TypeError: Cannot read property 'state' of undefined» در کنسول توسعه دهندههای مرورگر ظاهر میشود. عنوان میکند که شیء this در این متد، undefined است؛ بنابراین امکان خواندن خاصیت state از آن وجود ندارد.
bind مجدد شیء this در رخدادگردانهای React
در مورد this و bind مجدد آن در مطلب «React 16x - قسمت 2 - بررسی پیشنیازهای جاوا اسکریپتی - بخش 1» مفصل بحث کردیم و در اینجا میخواهیم از نتایج آن استفاده کنیم.
همانطور که مشاهده کردید، در متد رویدادگران handleIncrement، به شیء this دسترسی نداریم. چرا؟ چون this در جاوا اسکریپت نسبت به سایر زبانهای برنامه نویسی، متفاوت رفتار میکند. بسته به اینکه یک متد یا تابع، چگونه فراخوانی میشود، this میتواند اشیاء متفاوتی را بازگشت دهد. اگر تابعی به عنوان یک متد و جزئی از یک شیء فراخوانی شود، this در این حالت همواره ارجاعی را به آن شیء باز میگرداند. اما اگر آن تابع به صورت متکی به خود فراخوانی شد، به صورت پیشفرض ارجاعی را به شیء سراسری window مرورگر، بازگشت میدهد و اگر strict mode فعال باشد، تنها undefined را بازگشت میدهد. به همین جهت است که در اینجا خطای undefined بودن this را دریافت میکنیم.
یک روش حل این مشکل که پیشتر نیز در مورد آن توضیح دادیم، استفاده از متد bind است:
زمانیکه شیءای از نوع کلاس جاری ایجاد میشود، متد constructor آن نیز فراخوانی خواهد شد. در این مرحله دسترسی کاملی به شیء this وجود دارد که نمونهی آنرا با console.log نوشته شده میتوانید آزمایش کنید. در اینجا چون کامپوننت جاری از کلاس Component مشتق شدهاست، پیش از دسترسی به شیء this، نیاز است سازندهی کلاس پایه توسط متد super فراخوانی شود. اکنون که به this دسترسی داریم، میتوان توسط متد bind، مقدار شیء this شیءای دیگر مانند this.handleIncrement را تنظیم مجدد کنیم (متدها نیز در جاوا اسکریپت شیء هستند). خروجی آن، یک وهلهی جدید از شیء handleIncrement است که this آن اینبار به وهلهای از شیء جاری اشاره میکند. به همین جهت خروجی آنرا به this.handleIncrement انتساب میدهیم تا مشکل تعریف نشده بودن this آن برطرف شود.
اکنون اگر برنامه را اجرا کنید، با کلیک بر روی دکمهی Increment، بجای this.state.count لاگ شده، مقدار آن که صفر است، در کنسول توسعه دهندههای مرورگر ظاهر میشود.
این یک روش است که کار میکند؛ اما کمی طولانی است و به ازای هر روال رویدادگردانی باید دقیقا به همین نحو تکرار شود. روش دیگر، تبدیل متد handleIncrement به یک arrow function است و همانطور که در قسمت دوم این سری نیز بررسی کردیم، arrow functionها، this شیء جاری را بازنویسی نمیکنند؛ بلکه آنرا به ارث میبرند. بنابراین ابتدا کدهای سازندهی فوق را حذف میکنیم (چون دیگر نیازی به آنها نیست) و سپس متد handleIncrement سابق را به صورت زیر، تبدیل به یک arrow function میکنیم:
به این ترتیب با کلیک بر روی دکمهی Increment، مجددا همان خروجی تصویر قبلی را دریافت میکنیم؛ این روش سادهتر و تمیزتر است و نیازی به rebind دستی تک تک رویدادگردانهای کامپوننت جاری در این حالت وجود ندارد.
به روز رسانی state در کامپوننتهای React
اکنون که در روال رویدادگردان handleIncrement به شیء this و سپس مقدار this.state.count آن دسترسی پیدا کردهایم، میخواهیم با هربار کلیک بر روی این دکمه، یک واحد مقدار آنرا افزایش داده و در UI نمایش دهیم.
در React، خواص شیء state را جهت نمایش آنها در UI، مستقیما تغییر نمیدهیم. به عبارت دیگر نوشتن یک چنین کدی در React برای به روز رسانی UI، مرسوم نیست:
اگر تغییر فوق را اعمال و سپس برنامه را اجرا کنید، با کلیک بر روی دکمهی Increment ... اتفاقی رخ نمیدهد! رفتار React با Angular متفاوت است و در اینجا هرچند توسط فراخوانی {()this.formatCount} کار نمایش خاصیت count انجام میشود، اما به ظاهر، تغییرات مقدار count، به عبارات JSX متصل نیست. در کامپوننتهای Angular اگر مقدار خاصیتی را تغییر دهید و اگر این خاصیت در قالب آن کامپوننت، به آن خاصیت bind شده باشد، شاهد به روز رسانی آنی UI خواهید بود (Change Detection آنی و به ازای هر تغییری)؛ اما در React خیر. هرچند در همان Angular هم توصیه میشود که از حالت changeDetection: ChangeDetectionStrategy.OnPush برای رسیدن به حداکثر کارآیی نمایشی کامپوننتها استفاده شود؛ حالت OnPush در Angular، به روش تشخیص تغییرات React که در ادامه توضیح داده میشود، بیشتر شبیه است.
در کدهای فوق هرچند با کلیک بر روی دکمهی Increment، مقدار count افزایش یافتهاست، اما React از وقوع این تغییرات مطلع نیست. به همین جهت است که هیچ تغییری را در UI برنامه مشاهده نمیکنید.
با اجرای قطعه کد فوق، یک چنین اخطاری نیز در کنسول توسعه دهندگان مرورگر ظاهر میشود:
برای رفع این مشکل باید از یکی از متدهای به ارث برده شدهی از کلاس پایهی Component، به نام setState استفاده کرد. به این ترتیب به React اعلام میکنیم که state تغییر کردهاست (فعالسازی Change Detection، فقط در صورت نیاز). سپس React شروع به محاسبهی تغییرات کرده و در نتیجه قسمتهای متناظری از UI را برای هماهنگ سازی DOM مجازی خودش با DOM اصلی، به روز رسانی میکند.
زمانیکه از متد setState استفاده میکنیم، شیءای را باید به صورت یک پارامتر به آن ارسال کنیم. در این حالت مقادیر آن یا به خاصیت state جاری اضافه میشوند و یا در صورت از پیش موجود بودن، همان خواص را بازنویسی میکنند:
در اینجا به متد this.setState که از قسمت extends Component جاری به ارث رسیدهاست، یک شیء را با خاصیت count و مقدار جدیدی، ارسال میکنیم.
در این مرحله، فایل جاری را ذخیره کرده و پس از بارگذاری مجدد برنامه در مرورگر، بر روی دکمهی Increment کلیک کنید. اینبار ... کار میکند! چون React از تغییرات مطلع شدهاست:
وقتی state تغییر میکند، چه اتفاقاتی رخ میدهند؟
با فراخوانی متد this.setState، به React اعلام میکنیم که state یک کامپوننت قرار است تغییر کند. سپس React فراخوانی مجدد متد Render را در صف اجرایی خودش قرار میدهد تا در زمانی در آینده، اجرا شود؛ این فراخوانی async است. کار متد render، بازگشت یک المان جدید React است. در اینجا DOM مجازی React از چند المان، به صورت یک div و دو فرزند دکمه و span تشکیل شدهاست. در این حالت یک DOM مجازی قدیمی نیز از قبل (پیش از اجرای مجدد متد render) وجود دارد. در این لحظه، React این دو DOM مجازی را کنار هم قرار میدهد و محاسبه میکند که در اینجا دقیقا کدام المانها نسبت به قبل تغییر کردهاند. برای نمونه در اینجا تشخیص میدهد که span است که تغییر کرده، چون مقدار count، توسط آن نمایش داده میشود. در این حالت از کل DOM اصلی، تنها همان span تغییر کرده را به روز رسانی میکند و نه کل DOM را (و نه اعمال مجدد کل المانهای حاصل از متد render را).
این مورد را میتوان به نحو زیر آزمایش و مشاهده کرد:
در مرورگر بر روی المان span که شمارهها را نمایش میدهد، کلیک راست کرده و گزینهی inspect را انتخاب کنید. سپس بر روی دکمهی Increment کلیک نمائید. مرورگر قسمتی را که به روز میشود، با رنگی مشخص و متمایز، به صورت لحظهای نمایش میدهد:
ارسال پارامترها به متدهای رویدادگردان
تا اینجا متد handleIncrement، بدون پارامتر تعریف شدهاست. فرض کنید در یک برنامهی واقعی قرار است با کلیک بر روی این دکمه، id یک محصول را نیز به handleIncrement، منتقل و ارسال کنیم. اما در onClick={this.handleIncrement} تعریف شده، یک ارجاع را به متد handleIncrement داریم. بنابراین برای حل این مساله نمیتوان از روشی مانند onClick={this.handleIncrement(1)} استفاده کرد که در آن عدد فرضی 1 به صورت آرگومان متد handleIncrement ذکر شدهاست.
یک روش حل این مساله، تعریف متد دومی است که متد handleIncrement پارامتر دار را فراخوانی میکند:
و در این حالت برای مثال متد handleIncrement یک شیء را پذیرفتهاست:
سپس بجای تعریف onClick={this.handleIncrement}، از متد doHandleIncrement استفاده خواهیم کرد؛ یعنی onClick={this.doHandleIncrement}
هرچند این روش کار میکند، اما بیش از اندازه طولانی شدهاست. راه حل بهتر، استفاده از یک inline function است:
یعنی کل arrow function مربوط به doHandleIncrement را داخل onClick قرار میدهیم و چون یک سطری است، نیازی به ذکر {} و سمیکالن انتهای آنرا هم ندارد.
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: sample-04-part02.zip
نحوهی رندر لیستی از اشیاء در کامپوننتهای React
فرض کنید میخواهیم لیستی از تگها را رندر کنیم. برای این منظور ابتدا دادههای مرتبط را به خاصیت state کامپوننت، اضافه میکنیم:
class Counter extends Component { state = { count: 0, tags: ["tag 1", "tag 2", "tag 3"] };
در مطلب «React 16x - قسمت 3 - بررسی پیشنیازهای جاوا اسکریپتی - بخش 2» در مورد متد Array.map بحث شد. در اینجا میتوان توسط متد map، هر المان آرایهی تگها را به یک المان React تبدیل و سپس رندر کرد:
class Counter extends Component { state = { count: 0, tags: ["tag 1", "tag 2", "tag 3"] }; render() { return ( <div> <span className={this.getBadgeClasses()}>{this.formatCount()}</span> <button className="btn btn-secondary btn-sm">Increment</button> <ul> {this.state.tags.map(tag => ( <li>{tag}</li> ))} </ul> </div> ); }
هرچند اکنون لیستی از تگها در مرورگر رندر شدهاند، اما در کنسول توسعه دهندگان مرورگر، یک اخطار نیز درج شدهاست. علت اینجا است که React نیاز دارد تا بتواند هر آیتم رندر شده را به صورت منحصربفردی شناسایی کند. هدف این است که بتواند در صورت تغییر state هر المان در DOM مجازی خودش، خیلی سریع تشخیص دهد که چه چیزی تغییر کرده و فقط کدام قسمت خاص را باید در DOM اصلی، درج و به روز رسانی کند. برای رفع این مشکل، ویژگی key را به هر المان li در کدهای فوق اضافه میکنیم:
<li key={tag}>{tag}</li>
رندر شرطی عناصر در کامپوننتهای React
در اینجا میخواهیم اگر تگی وجود نداشت، پیام متناسبی ارائه شود؛ در غیراینصورت لیست تگها همانند قبل نمایش داده شود (رندر شرطی یا conditional rendering). برای انجام اینکار در React، برخلاف Angular، دارای دایرکتیوهای ساختاری if/else نیستیم؛ چون همانطور که عنوان شد، JSX یک templating engine نیست. به همین جهت برای رندر شرطی المانها در React، باید از همان جاوا اسکریپت خالص کمک بگیریم:
renderTags() { if (this.state.tags.length === 0) { return <p>There are no tags!</p>; } return ( <ul> {this.state.tags.map(tag => ( <li key={tag}>{tag}</li> ))} </ul> ); }
render() { return ( <div> <span className={this.getBadgeClasses()}>{this.formatCount()}</span> <button className="btn btn-secondary btn-sm">Increment</button> {this.renderTags()} </div> ); }
state = { count: 0, tags: [] };
روش دوم حل این نوع مسالهها، استفاده از روش زیر است؛ در این حالت خاص، فقط یک if را داریم، بدون وجود قسمت else:
{this.state.tags.length === 0 && "Please create a new tag!"}
اما این روش چگونه کار میکند؟! در اینجا && را به دو مقدار مشخص اعمال کردهایم. یکی حاصل یک مقایسه است و دیگری یک مقدار رشتهای مشخص. در جاوا اسکریپت برخلاف سایر زبانهای برنامه نویسی، میتوان && را بین دو مقدار غیر Boolean نیز اعمال کرد. در جاوا اسکریپت، یک رشتهی خالی به false تعبیر میشود و اگر تنها دارای یک حرف باشد، true درنظر گرفته میشود. برای نمونه در ترکیب 'true && 'Hi، هر دو قسمت به true تفسیر میشوند. در این حالت موتور جاوا اسکریپت، دومین عبارت (آخرین عبارت && شده) را بازگشت میدهد. همچنین در جاوا اسکریپت عدد صفر به false تفسیر میشود. بنابراین ترکیب true && 'Hi' && 1 مقدار 1 را بازگشت میدهد؛ چون عدد 1 هم از دیدگاه جاوا اسکریپت به true تفسیر خواهد شد.
مدیریت رخدادها در React
همانطور که در تصویر فوق نیز مشاهده میکنید، رخدادهای استاندارد DOM، دارای خواص معادل React ای نیز هستند. برای مثال زمانیکه مینویسیم onClick، دقیقا متناظر است با یک خاصیت المان React در عبارات JSX. بنابراین این نامها حساس به کوچکی و بزرگی حروف نیز هستند.
روش تعریف متدهای رخدادگردان در اینجا، با ذکر فعل handle شروع میشود:
handleIncrement() { console.log("Increment clicked!"); }
<button onClick={this.handleIncrement} className="btn btn-secondary btn-sm" > Increment </button>
اکنون اگر این فایل را ذخیره کرده و خروجی را در مرورگر بررسی کنیم، با هربار کلیک بر روی دکمهی Increment، یک console.log صورت میگیرد.
در ادامه میخواهیم در این رخدادگردان، مقدار this.state.count را افزایش دهیم. برای این منظور ابتدا مقدار this.state.count را به نحو زیر لاگ میکنیم:
handleIncrement() { console.log("Increment clicked!", this.state.count); }
bind مجدد شیء this در رخدادگردانهای React
در مورد this و bind مجدد آن در مطلب «React 16x - قسمت 2 - بررسی پیشنیازهای جاوا اسکریپتی - بخش 1» مفصل بحث کردیم و در اینجا میخواهیم از نتایج آن استفاده کنیم.
همانطور که مشاهده کردید، در متد رویدادگران handleIncrement، به شیء this دسترسی نداریم. چرا؟ چون this در جاوا اسکریپت نسبت به سایر زبانهای برنامه نویسی، متفاوت رفتار میکند. بسته به اینکه یک متد یا تابع، چگونه فراخوانی میشود، this میتواند اشیاء متفاوتی را بازگشت دهد. اگر تابعی به عنوان یک متد و جزئی از یک شیء فراخوانی شود، this در این حالت همواره ارجاعی را به آن شیء باز میگرداند. اما اگر آن تابع به صورت متکی به خود فراخوانی شد، به صورت پیشفرض ارجاعی را به شیء سراسری window مرورگر، بازگشت میدهد و اگر strict mode فعال باشد، تنها undefined را بازگشت میدهد. به همین جهت است که در اینجا خطای undefined بودن this را دریافت میکنیم.
یک روش حل این مشکل که پیشتر نیز در مورد آن توضیح دادیم، استفاده از متد bind است:
constructor() { super(); console.log("constructor", this); this.handleIncrement = this.handleIncrement.bind(this); }
اکنون اگر برنامه را اجرا کنید، با کلیک بر روی دکمهی Increment، بجای this.state.count لاگ شده، مقدار آن که صفر است، در کنسول توسعه دهندههای مرورگر ظاهر میشود.
این یک روش است که کار میکند؛ اما کمی طولانی است و به ازای هر روال رویدادگردانی باید دقیقا به همین نحو تکرار شود. روش دیگر، تبدیل متد handleIncrement به یک arrow function است و همانطور که در قسمت دوم این سری نیز بررسی کردیم، arrow functionها، this شیء جاری را بازنویسی نمیکنند؛ بلکه آنرا به ارث میبرند. بنابراین ابتدا کدهای سازندهی فوق را حذف میکنیم (چون دیگر نیازی به آنها نیست) و سپس متد handleIncrement سابق را به صورت زیر، تبدیل به یک arrow function میکنیم:
handleIncrement = () => { console.log("Increment clicked!", this.state.count); }
به روز رسانی state در کامپوننتهای React
اکنون که در روال رویدادگردان handleIncrement به شیء this و سپس مقدار this.state.count آن دسترسی پیدا کردهایم، میخواهیم با هربار کلیک بر روی این دکمه، یک واحد مقدار آنرا افزایش داده و در UI نمایش دهیم.
در React، خواص شیء state را جهت نمایش آنها در UI، مستقیما تغییر نمیدهیم. به عبارت دیگر نوشتن یک چنین کدی در React برای به روز رسانی UI، مرسوم نیست:
handleIncrement = () => { this.state.count++; };
در کدهای فوق هرچند با کلیک بر روی دکمهی Increment، مقدار count افزایش یافتهاست، اما React از وقوع این تغییرات مطلع نیست. به همین جهت است که هیچ تغییری را در UI برنامه مشاهده نمیکنید.
با اجرای قطعه کد فوق، یک چنین اخطاری نیز در کنسول توسعه دهندگان مرورگر ظاهر میشود:
Line 33:5: Do not mutate state directly. Use setState() react/no-direct-mutation-state
برای رفع این مشکل باید از یکی از متدهای به ارث برده شدهی از کلاس پایهی Component، به نام setState استفاده کرد. به این ترتیب به React اعلام میکنیم که state تغییر کردهاست (فعالسازی Change Detection، فقط در صورت نیاز). سپس React شروع به محاسبهی تغییرات کرده و در نتیجه قسمتهای متناظری از UI را برای هماهنگ سازی DOM مجازی خودش با DOM اصلی، به روز رسانی میکند.
زمانیکه از متد setState استفاده میکنیم، شیءای را باید به صورت یک پارامتر به آن ارسال کنیم. در این حالت مقادیر آن یا به خاصیت state جاری اضافه میشوند و یا در صورت از پیش موجود بودن، همان خواص را بازنویسی میکنند:
handleIncrement = () => { this.setState({ count: this.state.count + 1 }); };
در این مرحله، فایل جاری را ذخیره کرده و پس از بارگذاری مجدد برنامه در مرورگر، بر روی دکمهی Increment کلیک کنید. اینبار ... کار میکند! چون React از تغییرات مطلع شدهاست:
وقتی state تغییر میکند، چه اتفاقاتی رخ میدهند؟
با فراخوانی متد this.setState، به React اعلام میکنیم که state یک کامپوننت قرار است تغییر کند. سپس React فراخوانی مجدد متد Render را در صف اجرایی خودش قرار میدهد تا در زمانی در آینده، اجرا شود؛ این فراخوانی async است. کار متد render، بازگشت یک المان جدید React است. در اینجا DOM مجازی React از چند المان، به صورت یک div و دو فرزند دکمه و span تشکیل شدهاست. در این حالت یک DOM مجازی قدیمی نیز از قبل (پیش از اجرای مجدد متد render) وجود دارد. در این لحظه، React این دو DOM مجازی را کنار هم قرار میدهد و محاسبه میکند که در اینجا دقیقا کدام المانها نسبت به قبل تغییر کردهاند. برای نمونه در اینجا تشخیص میدهد که span است که تغییر کرده، چون مقدار count، توسط آن نمایش داده میشود. در این حالت از کل DOM اصلی، تنها همان span تغییر کرده را به روز رسانی میکند و نه کل DOM را (و نه اعمال مجدد کل المانهای حاصل از متد render را).
این مورد را میتوان به نحو زیر آزمایش و مشاهده کرد:
در مرورگر بر روی المان span که شمارهها را نمایش میدهد، کلیک راست کرده و گزینهی inspect را انتخاب کنید. سپس بر روی دکمهی Increment کلیک نمائید. مرورگر قسمتی را که به روز میشود، با رنگی مشخص و متمایز، به صورت لحظهای نمایش میدهد:
ارسال پارامترها به متدهای رویدادگردان
تا اینجا متد handleIncrement، بدون پارامتر تعریف شدهاست. فرض کنید در یک برنامهی واقعی قرار است با کلیک بر روی این دکمه، id یک محصول را نیز به handleIncrement، منتقل و ارسال کنیم. اما در onClick={this.handleIncrement} تعریف شده، یک ارجاع را به متد handleIncrement داریم. بنابراین برای حل این مساله نمیتوان از روشی مانند onClick={this.handleIncrement(1)} استفاده کرد که در آن عدد فرضی 1 به صورت آرگومان متد handleIncrement ذکر شدهاست.
یک روش حل این مساله، تعریف متد دومی است که متد handleIncrement پارامتر دار را فراخوانی میکند:
doHandleIncrement = () => { this.handleIncrement({ id: 1, name: "Product 1" }); };
handleIncrement = product => { console.log(product); this.setState({ count: this.state.count + 1 }); };
هرچند این روش کار میکند، اما بیش از اندازه طولانی شدهاست. راه حل بهتر، استفاده از یک inline function است:
onClick={() => this.handleIncrement({ id: 1, name: "Product 1" })}
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: sample-04-part02.zip
فرمهای Blazor به همراه پشتیبانی از ویژگی Remote که به همراه ASP.NET Core ارائه میشود، نیستند. هرچند میتوان در حین ارسال فرم به سرور، نتیجهی اعتبارسنجی از راه دور و سمت سرور را به کاربر نمایش داد، اما تجربهی کاربری آن در حد Remote validation نیست. یعنی میخواهیم در حین ورود اطلاعات و یا انتقال focus به کنترل دیگری، اعتبارسنجی سمت سرور صورت گیرد و نه فقط در زمان ارسال کل اطلاعات به سرور، در پایان کار. در این مطلب روشی را جهت پیاده سازی این عملیات بررسی خواهیم کرد.
ایجاد یک پروژهی ابتدایی Blazor WASM
پروژهای را که در این مطلب تکمیل خواهیم کرد، از نوع Blazor WASM هاست شدهاست. بنابراین در پوشهی فرضی BlazorAsyncValidation، دستور «dotnet new blazorwasm --hosted» را صادر میکنیم تا ساختار ابتدایی پروژه که به همراه یک کلاینت Blazor WASM و یک سرور ASP.NET Core Web API است، تشکیل شود. از قسمت Web API، برای پیاده سازی اعتبارسنجی سمت سرور استفاده خواهیم کرد.
مدل ثبت نام برنامه
مدل ثبت نام برنامه تنها از یک خاصیت نام تشکیل شده و در پروژهی Shared قرار میگیرد تا هم در کلاینت و هم در سرور قابل استفاده باشد:
این نام است که میخواهیم عدم تکراری بودن آنرا حین ثبت نام در سمت سرور، بررسی کنیم. به همین جهت کنترلر API زیر را برای آن تدارک خواهیم دید.
کنترلر API ثبت نام برنامه
کنترلر زیر که در پوشهی BlazorAsyncValidation\Server\Controllers قرار میگیرد، منطق بررسی تکراری نبودن نام دریافتی از برنامهی کلاینت را شبیه به منطق remote validation استاندارد MVC، پیاده سازی میکند که در نهایت یک true و یا false را باز میگرداند.
در اینجا خروجی بازگشت داده کاملا در اختیار شما است و نیازی نیست تا حتما ارتباطی با MVC داشته باشد؛ چون مدیریت سمت کلاینت بررسی آنرا خودمان انجام خواهیم داد و نه یک کتابخانهی از پیش نوشته شده و مشخص.
غنی سازی فرم استاندارد Blazor جهت انجام Remote validation
اگر بخواهیم از EditForm استاندارد Blazor در حالت متداول آن و بدون هیچ تغییری استفاده کنیم، مانند مثال زیر که InputText متصل به خاصیت Name مربوط به Dto برنامه را نمایش میدهد:
تنها رخدادی که در اختیار ما قرار میگیرد، رخداد submit (در حالت موفقیت اعتبارسنجی سمت کلاینت و یا تنها submit معمولی) است. بنابراین برای دسترسی به رخدادهای بیشتر EditForm، نیاز است با EditContext آن کار کنیم:
به همین جهت EditContext را در سطح کامپوننت جاری تعریف کرده و همچنین سرویس HttpClient را جهت ارسال اطلاعات Name و دریافت پاسخ true/false از سرور و یک ValidationMessageStore را برای نگهداری تعاریف خطاهای سفارشی که قرار است در فرم نمایش داده شوند، معرفی میکنیم.
ValidationMessageStore به همراه متد Add است و اگر به آن نام فیلد مدنظر را به همراه پیامی، اضافه کنیم، این اطلاعات را به صورت خطای اعتبارسنجی توسط کامپوننت ValidationMessage نمایش میدهد.
محل مقدار دهی اولیهی این اشیاء نیز در روال رویدادگردان OnInitialized به صورت زیر است:
در اینجا ابتدا یک نمونهی جدید از EditContext، بر اساس Model فرم، تشکیل میشود. سپس ValidationMessageStore سفارشی که قرار است خطاهای اعتبارسنجی remote ما را نمایش دهد نیز نمونه سازی میشود. در ادامه امکان دسترسی به رخدادهای OnFieldChanged ، OnValidationStateChanged و OnValidationRequested را خواهیم داشت که در حالت معمولی کار با EditForm میسر نیستند.
برای مثال اگر فیلدی تغییر کند، رویداد OnFieldChanged صادر میشود. در همینجا است که کار فراخوانی متد OnValidateFieldAsync که در ادامه معرفی میشود را انجام میدهیم تا کار اعتبارسنجی Async سمت سرور را انجام دهد. اگر نتیجهای به همراه آن بود، توسط messageStore به صورت یک خطای اعتبارسنجی نمایش داده خواهد شد و همچنین EditContext نیز با فراخوانی متد NotifyValidationStateChanged، وادار به بهروز رسانی وضعیت اعتبارسنجی خود میگردد.
متد سفارشی OnValidateFieldAsync که کار اعتبارسنجی سمت سرور را انجام میدهد، به صورت زیر تعریف شدهاست:
در اینجا درخواستی به سمت آدرس api/Register/IsUserNameUnique ارسال شده و سپس بر اساس مقدار true و یا false آن، پیامی را بازگشت میدهد. اگر نال را بازگشت دهد یعنی مشکلی نبوده.
یک نکته: InputText استاندارد در حالت معمول آن، پس از تغییر focus به یک کنترل دیگر، سبب بروز رویداد OnFieldChanged میشود و نه در حالت فشرده شدن کلیدها. به همین جهت اگر برنامه پیوستی را میخواهید آزمایش کنید، نیاز است فقط focus را تغییر دهید و یا یک کنترل سفارشی را برای اینکار توسعه دهید.
کدهای کامل این مطلب را از اینجا میتوانید دریافت کنید: BlazorAsyncValidation.zip
ایجاد یک پروژهی ابتدایی Blazor WASM
پروژهای را که در این مطلب تکمیل خواهیم کرد، از نوع Blazor WASM هاست شدهاست. بنابراین در پوشهی فرضی BlazorAsyncValidation، دستور «dotnet new blazorwasm --hosted» را صادر میکنیم تا ساختار ابتدایی پروژه که به همراه یک کلاینت Blazor WASM و یک سرور ASP.NET Core Web API است، تشکیل شود. از قسمت Web API، برای پیاده سازی اعتبارسنجی سمت سرور استفاده خواهیم کرد.
مدل ثبت نام برنامه
مدل ثبت نام برنامه تنها از یک خاصیت نام تشکیل شده و در پروژهی Shared قرار میگیرد تا هم در کلاینت و هم در سرور قابل استفاده باشد:
using System.ComponentModel.DataAnnotations; namespace BlazorAsyncValidation.Shared; public class UserDto { [Required] public string Name { set; get; } = default!; }
کنترلر API ثبت نام برنامه
کنترلر زیر که در پوشهی BlazorAsyncValidation\Server\Controllers قرار میگیرد، منطق بررسی تکراری نبودن نام دریافتی از برنامهی کلاینت را شبیه به منطق remote validation استاندارد MVC، پیاده سازی میکند که در نهایت یک true و یا false را باز میگرداند.
در اینجا خروجی بازگشت داده کاملا در اختیار شما است و نیازی نیست تا حتما ارتباطی با MVC داشته باشد؛ چون مدیریت سمت کلاینت بررسی آنرا خودمان انجام خواهیم داد و نه یک کتابخانهی از پیش نوشته شده و مشخص.
using BlazorAsyncValidation.Shared; using Microsoft.AspNetCore.Mvc; namespace BlazorAsyncValidation.Server.Controllers; [ApiController, Route("api/[controller]/[action]")] public class RegisterController : ControllerBase { [HttpPost] public IActionResult IsUserNameUnique([FromBody] UserDto userModel) { if (string.Equals(userModel?.Name, "Vahid", StringComparison.OrdinalIgnoreCase)) { return Ok(false); } return Ok(true); } }
غنی سازی فرم استاندارد Blazor جهت انجام Remote validation
اگر بخواهیم از EditForm استاندارد Blazor در حالت متداول آن و بدون هیچ تغییری استفاده کنیم، مانند مثال زیر که InputText متصل به خاصیت Name مربوط به Dto برنامه را نمایش میدهد:
@page "/" <PageTitle>Index</PageTitle> <h2>Register</h2> <EditForm EditContext="@EditContext" OnValidSubmit="DoSubmitAsync"> <DataAnnotationsValidator/> <div class="row mb-2"> <label class="col-form-label col-lg-2">Name:</label> <div class="col-lg-10"> <InputText @bind-Value="Model.Name" class="form-control"/> <ValidationMessage For="@(() => Model.Name)"/> </div> </div> <button class="btn btn-secondary" type="submit">Submit</button> </EditForm>
public partial class Index { private const string UserValidationUrl = "/api/Register/IsUserNameUnique"; private ValidationMessageStore? _messageStore; [Inject] private HttpClient HttpClient { set; get; } = default!; private EditContext? EditContext { set; get; } private UserDto Model { get; } = new();
ValidationMessageStore به همراه متد Add است و اگر به آن نام فیلد مدنظر را به همراه پیامی، اضافه کنیم، این اطلاعات را به صورت خطای اعتبارسنجی توسط کامپوننت ValidationMessage نمایش میدهد.
محل مقدار دهی اولیهی این اشیاء نیز در روال رویدادگردان OnInitialized به صورت زیر است:
protected override void OnInitialized() { EditContext = new EditContext(Model); _messageStore = new ValidationMessageStore(EditContext); EditContext.OnFieldChanged += (sender, eventArgs) => { var fieldIdentifier = eventArgs.FieldIdentifier; _messageStore?.Clear(fieldIdentifier); _ = InvokeAsync(async () => { var errors = await OnValidateFieldAsync(fieldIdentifier.FieldName); if (errors?.Any() != true) { return; } foreach (var error in errors) { _messageStore?.Add(fieldIdentifier, error); } EditContext.NotifyValidationStateChanged(); }); StateHasChanged(); }; EditContext.OnValidationStateChanged += (sender, eventArgs) => StateHasChanged(); EditContext.OnValidationRequested += (sender, eventArgs) => _messageStore?.Clear(); }
برای مثال اگر فیلدی تغییر کند، رویداد OnFieldChanged صادر میشود. در همینجا است که کار فراخوانی متد OnValidateFieldAsync که در ادامه معرفی میشود را انجام میدهیم تا کار اعتبارسنجی Async سمت سرور را انجام دهد. اگر نتیجهای به همراه آن بود، توسط messageStore به صورت یک خطای اعتبارسنجی نمایش داده خواهد شد و همچنین EditContext نیز با فراخوانی متد NotifyValidationStateChanged، وادار به بهروز رسانی وضعیت اعتبارسنجی خود میگردد.
متد سفارشی OnValidateFieldAsync که کار اعتبارسنجی سمت سرور را انجام میدهد، به صورت زیر تعریف شدهاست:
private async Task<IList<string>?> OnValidateFieldAsync(string fieldName) { switch (fieldName) { case nameof(UserDto.Name): var response = await HttpClient.PostAsJsonAsync( UserValidationUrl, new UserDto { Name = Model.Name }); var responseContent = await response.Content.ReadAsStringAsync(); if (string.Equals(responseContent, "false", StringComparison.OrdinalIgnoreCase)) { return new List<string> { $"`{Model.Name}` is in use. Please choose another name." }; } // TIP: It's better to use the `DntDebounceInputText` component for this case to reduce the network round-trips. break; } return null; }
یک نکته: InputText استاندارد در حالت معمول آن، پس از تغییر focus به یک کنترل دیگر، سبب بروز رویداد OnFieldChanged میشود و نه در حالت فشرده شدن کلیدها. به همین جهت اگر برنامه پیوستی را میخواهید آزمایش کنید، نیاز است فقط focus را تغییر دهید و یا یک کنترل سفارشی را برای اینکار توسعه دهید.
کدهای کامل این مطلب را از اینجا میتوانید دریافت کنید: BlazorAsyncValidation.zip
برای استفاده از قابلیتهای Ajax کتابخانه jQuery ، شش متد زیر در اختیار برنامه نویسها است:
$.ajax(), load(), $.get(), $.getJSON(), $.getScript(), and $.post()
برای مثال کد زیر زمان جاری را از سرور دریافت کرده و نتیجه را در سه تکست باکس قرار داده شده در صفحه نمایش میدهد.
ابتدا وب سرویس ساده زیر را در نظر بگیرید که زمان شمسی جاری را بر میگرداند:
using System;
using System.Globalization;
using System.Web.Script.Services;
using System.Web.Services;
namespace TestJQueryAjax
{
[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
[System.ComponentModel.ToolboxItem(false)]
[ScriptService]
public class AjaxSrv : WebService
{
public class TimeInfo
{
public string Date { set; get; }
public string Hr { set; get; }
public string Min { set; get; }
}
[WebMethod(EnableSession = true)]
[ScriptMethod(ResponseFormat = ResponseFormat.Json)]
public TimeInfo GetTime()
{
DateTime now = new DateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day);
PersianCalendar pc = new PersianCalendar();
string today = string.Format("{0}/{1}/{2}",
pc.GetYear(now),
pc.GetMonth(now).ToString("00"),
pc.GetDayOfMonth(now).ToString("00"));
TimeInfo ti = new TimeInfo
{
Date = today,
Hr = DateTime.Now.Hour.ToString("00"),
Min = DateTime.Now.Minute.ToString("00")
};
return ti;
}
}
}
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="TestFillCtrls.aspx.cs"
Inherits="TestJQueryAjax.TestFillCtrls" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title></title>
<script src="js/jquery.js" type="text/javascript"></script>
<script type="text/javascript">
function validate() {
$.ajax({
type: "POST",
url: 'AjaxSrv.asmx/GetTime',
data: '{}',
contentType: "application/json; charset=utf-8",
dataType: "json",
success:
function(msg) {
$("#<%=txtDate.ClientID %>").val(msg.d.Date);
$("#<%=txtHr.ClientID %>").val(msg.d.Hr);
$("#<%=txtMin.ClientID %>").val(msg.d.Min);
},
error:
function(XMLHttpRequest, textStatus, errorThrown) {
alert("خطایی رخ داده است");
}
});
//debugger;
}
</script>
</head>
<body>
<form id="form1" runat="server">
<div>
<asp:TextBox ID="txtDate" runat="server" />
<br />
<asp:TextBox ID="txtHr" runat="server" />
<br />
<asp:TextBox ID="txtMin" runat="server" />
<br />
<asp:Button ID="btnGetTime" runat="server" Text="Click here!" UseSubmitBehavior="false"
OnClientClick="validate();return false;" />
</div>
</form>
</body>
</html>
{"d":{"__type":"TestJQueryAjax.AjaxSrv+TimeInfo","Date":"1388/07/14","Hr":"12","Min":"59"}}
باید به خاطر داشت که برای هر 6 متد Ajax ایی jQuery ، عملیات کش شدن اطلاعات در مرورگر کاربر به صورت پیش فرض فعال است. اما این نکته تنها زمانیکه dataType مورد استفاده از نوع script یا jsonp باشد، صادق نبوده و کش شدن به صورت خودکار غیرفعال میگردد.
روش سنتی غیرفعال کردن کش در حین عملیات اجکسی، استفاده از یک کوئری استرینگ متغیر در پایان url درخواستی است. به این صورت مرورگر درخواست صادره را جدید فرض کرده و از کش خود استفاده نمینماید (همین مورد در حالت کش شدن تصاویر هم صادق است).
jQuery نیز همین عملیات را در پشت صحنه انجام داده اما تنظیم آنرا به نحوی مطلوبتری ارائه میدهد. یا پارامتر cache را در تعریف متد ajax خود اضافه نموده و مقدار آن را مساوی false قرار دهید و یا جهت تاثیر گذاری بر روی کلیه متدهای مورد استفاده، پیش از استفاده از آنها این تنظیم را مشخص سازید:
$.ajaxSetup({cache: false});
راهنمای رایگان زیر نحوه نصب ویندوز سرور 2003 تا اس کیوال سرور 2005 ، تنظیمات IIS و نصب SharePoint و ابزارهای لازم برای برنامه نویسی آنرا در یک ماشین مجازی (اینبار در VMware) به صورت مصور و قدم به قدم توضیح داده است.
فهرست مطالب آن:
- Introduction
- Enabling 64-bit Guest OS Support on your Motherboard (if required)
- Creating the Virtual Machine
- Editing the Virtual Machine Settings
- Building Windows
- Configuring Windows
- Patch and Backup
- Domain Promotion and Configuration
- Moss Domain Accounts Creation
- Pop 3 Email Service Configuration
- Moss 2007 Base Installation
- SQL Server 2005 64-bit Enterprise Installation and Configuration
- Create MossSetup login
- Patch and Backup
- Office Enterprise 2007 Installation and Configuration
- SharePoint Designer 2007 Installation
- Visual Studio 2005 Team Developer Edition Installation
- Development Tools Installation
- Force IIS to use 64 bit mode and disable 32 bit mode
- Complete Moss 2007 Installation
- MOSS 2007 Basic Configuration Guide
- Check Search Services and Event Log
- Backup
Download
یا برای مثال این روند از اکسچنج سرور 2007 شروع شد (میل سرور مایکروسافت). نسخه سازمانی این محصول فقط 64 بیتی است.
بازخوردهای دوره
استفاده از Async و Await در برنامههای ASP.NET MVC
- پیشنیاز مطالعه قسمت جاری، مطالعه 6 قسمت اول این دوره است.
- «همزمان اجرا میشه»
خیر. متدهای Async واقعی مثل نمونه ارائه شده در EF غیرهمزمان اجرا میشوند. یعنی، ترد جاری را آزاد کرده و ASP.NET میتواند از آن ترد برای پاسخ دهی به یک درخواست رسیده دیگر استفاده کند.
- «باید منتظر پاسخ از db بمونه»
استفاده از await و async سبب بازنویسی بدنه متد توسط یک state machine در پشت صحنه میشوند. یعنی اینطور نیست که روش اجرای آن blocking است و تا رسیدن پاسخ از بانک اطلاعاتی، از این ترد دیگر نمیشود استفاده کرد. جایی که await فراخوانی میشود، ترد جاری برای استفاده بعدی آزاد خواهد شد. در ادامه مابقی کدها تبدیل به یک IEnumerator میشوند که هر دستور آن شامل یک yield return است. هر مرحله که تمام شد، MoveNext این IEnumerator فراخوانی میشود تا به مرحلهی بعدی برسد. به این روش استفاده از coroutines هم گفته میشود که در سی شارپ 5، کامپایلر کار تولید کدهای آنرا انجام میدهد. برای مطالعه بیشتر:
- انجام پی در پی اعمال Async به کمک Iterators - قسمت اول
- انجام پی در پی اعمال Async به کمک Iterators - قسمت دوم
- «چون تا فایل آپلود نشه ذخیره آدرس تو db بی معنیه»
ذخیره آدرس هم یک قسمت از کار است و اتفاقا وابسته به سیستم جاری هم نیست. وابسته است به یک بانک اطلاعاتی که خارج از مرزهای سیستم، به صورت مستقل در حال فعالیت است (عموما البته؛ مثلا اگر از SQL Server استفاده میشود).
برای ذخیره فایلها در سیستم هم متدهای Async به کلاس Stream در دات نت 4.5 اضافه شدهاند؛ مثل WriteAsync . در این حالت هم میتوان از await WriteAsync برای ذخیره اطلاعات و بازهم آزاد کردن ترد جاری استفاده کرد.
- «همزمان اجرا میشه»
خیر. متدهای Async واقعی مثل نمونه ارائه شده در EF غیرهمزمان اجرا میشوند. یعنی، ترد جاری را آزاد کرده و ASP.NET میتواند از آن ترد برای پاسخ دهی به یک درخواست رسیده دیگر استفاده کند.
- «باید منتظر پاسخ از db بمونه»
استفاده از await و async سبب بازنویسی بدنه متد توسط یک state machine در پشت صحنه میشوند. یعنی اینطور نیست که روش اجرای آن blocking است و تا رسیدن پاسخ از بانک اطلاعاتی، از این ترد دیگر نمیشود استفاده کرد. جایی که await فراخوانی میشود، ترد جاری برای استفاده بعدی آزاد خواهد شد. در ادامه مابقی کدها تبدیل به یک IEnumerator میشوند که هر دستور آن شامل یک yield return است. هر مرحله که تمام شد، MoveNext این IEnumerator فراخوانی میشود تا به مرحلهی بعدی برسد. به این روش استفاده از coroutines هم گفته میشود که در سی شارپ 5، کامپایلر کار تولید کدهای آنرا انجام میدهد. برای مطالعه بیشتر:
- انجام پی در پی اعمال Async به کمک Iterators - قسمت اول
- انجام پی در پی اعمال Async به کمک Iterators - قسمت دوم
- «چون تا فایل آپلود نشه ذخیره آدرس تو db بی معنیه»
ذخیره آدرس هم یک قسمت از کار است و اتفاقا وابسته به سیستم جاری هم نیست. وابسته است به یک بانک اطلاعاتی که خارج از مرزهای سیستم، به صورت مستقل در حال فعالیت است (عموما البته؛ مثلا اگر از SQL Server استفاده میشود).
برای ذخیره فایلها در سیستم هم متدهای Async به کلاس Stream در دات نت 4.5 اضافه شدهاند؛ مثل WriteAsync . در این حالت هم میتوان از await WriteAsync برای ذخیره اطلاعات و بازهم آزاد کردن ترد جاری استفاده کرد.
نظرات اشتراکها
ارتقاء XP به Winodws 8 با کمک SCCM
نه الزاما. نیازهای سخت افزاری آنچنانی ندارد و این موارد روی سیستمهای حداقل از 7 سال قبل به این طرف موجود هستند (من روی یک ماشین مجازی، با حداقلهایی کمتر از موارد عنوان شده توسط مایکروسافت هم نصبش کردم).
اشتراکها