اثر وجود سشن بر پردازش موازی در ASP.NET
در Asp.net برای نگهداری یکسری از اطلاعات کاربر و یا هرگونه اطلاعات مربوط به یک کاربر خاص از Sessionها استفاده میکردیم.
حال در Mvc برای نگهداری هر گونه اطلاعات کاربر Login شده از چی باید استفاده کرد. اگر جواب کوکی هاست این کوکیها در سمت کلاینت ثبت میشود و نیاز است که اطلاعات در سمت سرور باشد.
کدم بهتره است : بخشی از حافظه برای اطلاعات کاربر جاری مصرف شود و درعوض در هر درخواستی از آن استفاده شود و یا خیر هربار این اطلاعات واکشی شود (البته بدون درنظر گرفتن سطح دوم کش).
همچنین Sessionها در Mvc با Session در Asp.net چه تفاوت هایی دارد؟
ممنون میشم ابهام بنده را برطرف بفرمایید.
معرفی کتابخانهی DNTCaptcha.Core
مروری بر Blazor (قسمت اول)
Url Routing در ASP.Net WebForms
با استفاده از jQuery ، تحت نظر قرار دادن ورودیهای کاربران در تمام فیلدهای ورودی صفحه کار سادهای است؛ اما جایگزینی مثلا ی فارسی با ی عربی و برعکس درست در لحظهی تایپ آنها کار سادهای نیست و هر مرورگر روش خاص خودش را دارد و بعضیها هم اصلا اجازهی تغییر رخدادهای رسیده را نمیدهند.
اسکریپت زیر کار یک دست سازی ی و ک دریافتی در صفحات وب را انجام میدهد (برای مثال اگر کاربر ی تایپ کند به صورت خودکار به ی تبدیل میشود):
// <![CDATA[
function substituteCharInFireFox(charCode, e) {
var keyEvt = document.createEvent("KeyboardEvent");
keyEvt.initKeyEvent("keypress", true, true, null, false, false, false, false, 0, charCode);
e.target.dispatchEvent(keyEvt);
e.preventDefault();
}
function substituteCharInChrome(charCode, e) {
//it does not work yet! /*$.browser.webkit*/
//https://bugs.webkit.org/show_bug.cgi?id=16735
var keyEvt = document.createEvent("KeyboardEvent");
keyEvt.initKeyboardEvent("keypress", true, true, null, false, false, false, false, 0, charCode);
e.target.dispatchEvent(keyEvt);
e.preventDefault();
}
function insertAtCaret(myValue, e) {
var obj = e.target;
var startPos = obj.selectionStart;
var endPos = obj.selectionEnd;
var scrollTop = obj.scrollTop;
obj.value = obj.value.substring(0, startPos) + myValue + obj.value.substring(endPos, obj.value.length);
obj.focus();
obj.selectionStart = startPos + myValue.length;
obj.selectionEnd = startPos + myValue.length;
obj.scrollTop = scrollTop;
e.preventDefault();
}
$(document).ready(function () {
$(document).keypress(function (e) {
var keyCode = e.keyCode ? e.keyCode : e.which;
var arabicYeCharCode = 1610;
var persianYeCharCode = 1740;
var arabicKeCharCode = 1603;
var persianKeCharCode = 1705;
if ($.browser.msie) {
switch (keyCode) {
case arabicYeCharCode:
event.keyCode = persianYeCharCode;
break;
case arabicKeCharCode:
event.keyCode = persianKeCharCode;
break;
}
}
else if ($.browser.mozilla) {
switch (keyCode) {
case arabicYeCharCode:
substituteCharInFireFox(persianYeCharCode, e);
break;
case arabicKeCharCode:
substituteCharInFireFox(persianKeCharCode, e);
break;
}
}
else {
switch (keyCode) {
case arabicYeCharCode:
insertAtCaret(String.fromCharCode(persianYeCharCode), e);
break;
case arabicKeCharCode:
insertAtCaret(String.fromCharCode(persianKeCharCode), e);
break;
}
}
});
});
// ]]>
- دریافت این اسکریپت: (+)
- نسخهی فشرده شده آن: (+)
- یک پروژهی ساده ASP.NET نمونه در مورد استفاده از آن: (+)
این اسکریپت با IE، فایرفاکس، اپرا ، کروم گوگل و Safari شرکت اپل سازگار است و تفاوتی هم نمیکند که در یک html ساده استفاده شود یا در صفحات ASP ، PHP ، ASP.NET ، JSP یا هر چی!
مطالب مشابه:
//برای ذخیره مقادیر از ساختار نام و مقدار استفاده میکنیم که نامها را اینجا ثبت کرده ام var Variables={ posts:"posts", postsComments:"postsComments", shares:"shares", sharesComments:"sharesComments", } //برای ذخیره زمان آخرین تغییر سایت برای هر یک از مطالب به صورت جداگانه نیاز به یک ساختار نام و مقدار است که نامها را در اینجا ذخیره کرده ام var DateContainer={ posts:"dtposts", postsComments:"dtpostsComments", shares:"dtshares", sharesComments:"dtsharesComments", interval:"interval" } //برای نمایش پیامها به کاربر var Messages={ SettingsSaved:"تنظیمات ذخیره شد", SiteUpdated:"سایت به روز شد", PostsUpdated:"مطلب ارسالی جدید به سایت اضافه شد", CommentsUpdated:"نظری جدیدی در مورد مطالب سایت ارسال شد", SharesUpdated:"اشتراک جدید به سایت ارسال شد", SharesCommentsUpdated:"نظری برای اشتراکهای سایت اضافه شد" } //لینکهای فید سایت var Links={ postUrl:"https://www.dntips.ir/feeds/posts", posts_commentsUrl:"https://www.dntips.ir/feeds/comments", sharesUrl:"https://www.dntips.ir/feed/news", shares_CommentsUrl:"https://www.dntips.ir/feed/newscomments" } //لینک صفحات سایت var WebLinks={ Home:"https://www.dntips.ir", postUrl:"https://www.dntips.ir/postsarchive", posts_commentsUrl:"https://www.dntips.ir/commentsarchive", sharesUrl:"https://www.dntips.ir/newsarchive", shares_CommentsUrl:"https://www.dntips.ir/newsarchive/comments" }
chrome.runtime.onInstalled.addListener(function(details) { var now=String(new Date()); var params={}; params[Variables.posts]=true; params[Variables.postsComments]=false; params[Variables.shares]=false; params[Variables.sharesComments]=false; params[DateContainer.interval]=1; params[DateContainer.posts]=now; params[DateContainer.postsComments]=now; params[DateContainer.shares]=now; params[DateContainer.sharesComments]=now; chrome.storage.local.set(params, function() { if(chrome.runtime.lastError) { /* error */ console.log(chrome.runtime.lastError.message); return; } }); });
chrome.storage.local.set('mykey':myvalue,....
chrome.storage.local.set(mykey:myvalue,...
"background": { "scripts": ["const.js","init.js"] }
نکته:نمی توان در تعریف بک گراند هم فایل اسکریپت معرفی کرد و هم فایل html
"background": { "page": "background.htm" }
<html> <head> <script type="text/javascript" src="const.js"></script> <script type="text/javascript" src="https://www.google.com/jsapi"></script> <script type="text/javascript" src="init.js"></script> <script type="text/javascript" src="omnibox.js"></script> <script type="text/javascript" src="rssreader.js"></script> <script type="text/javascript" src="contextmenus.js"></script> </head> <body> </body> </html>
- کدنویسی راحتتر و خلاصهتر برای خواندن RSS
- استفاده اجباری از یک پروکسی به خاطر Content Security Policy و حتی CORS
"content_security_policy": "script-src 'self' https://*.google.com; object-src 'self'"
<script type="text/javascript" src="https://www.google.com/jsapi"></script>
استفاده از این Api در rssreader.js
google.load("feeds", "1"); google.setOnLoadCallback(alarmManager);
function alarmManager() { chrome.storage.local.get(DateContainer.interval,function ( items) { period_time==items[DateContainer.interval]; chrome.alarms.create('RssInterval', {periodInMinutes: period_time}); }); chrome.alarms.onAlarm.addListener(function (alarm) { console.log(alarm); if (alarm.name == 'RssInterval') { var boolposts,boolpostsComments,boolshares,boolsharesComments; chrome.storage.local.get([Variables.posts,Variables.postsComments,Variables.shares,Variables.sharesComments],function ( items) { boolposts=items[Variables.posts]; boolpostsComments=items[Variables.postsComments]; boolshares=items[Variables.shares]; boolsharesComments=items[Variables.sharesComments]; chrome.storage.local.get([DateContainer.posts,DateContainer.postsComments,DateContainer.shares,DateContainer.sharesComments],function ( items) { var Vposts=new Date(items[DateContainer.posts]); var VpostsComments=new Date(items[DateContainer.postsComments]); var Vshares=new Date(items[DateContainer.shares]); var VsharesComments=new Date(items[DateContainer.sharesComments]); if(boolposts){var result=RssReader(Links.postUrl,Vposts,DateContainer.posts,Messages.PostsUpdated);} if(boolpostsComments){var result=RssReader(Links.posts_commentsUrl,VpostsComments,DateContainer.postsComments,Messages.CommentsUpdated); } if(boolshares){var result=RssReader(Links.sharesUrl,Vshares,DateContainer.shares,Messages.SharesUpdated);} if(boolsharesComments){var result=RssReader(Links.shares_CommentsUrl,VsharesComments,DateContainer.sharesComments,Messages.SharesCommentsUpdated);} }); }); } }); }
- آدرس فیدی که قرار است از روی آن بخواند
- آخرین به روزسانی که از سایت داشته متعلق به چه تاریخی است.
- نام کلید ذخیره سازی تاریخ آخرین تغییر سایت که اگر بررسی شد و مشخص شد سایت به روز شده است، تاریخ جدید را روی آن ذخیره کنیم.
- در صورتی که سایت به روز شده باشد نیاز است پیامی را برای کاربر نمایش دهیم که این پیام را در اینجا قرار میدهیم.
function RssReader(URL,lastupdate,datecontainer,Message) { var feed = new google.feeds.Feed(URL); feed.setResultFormat(google.feeds.Feed.XML_FORMAT); feed.load(function (result) { if(result!=null) { var strRssUpdate = result.xmlDocument.firstChild.firstChild.childNodes[5].textContent; var RssUpdate=new Date(strRssUpdate); if(RssUpdate>lastupdate) { SaveDateAndShowMessage(datecontainer,strRssUpdate,Message) } } }); }
var strRssUpdate = result.xmlDocument.firstChild.firstChild.childNodes[5].textContent;
function SaveDateAndShowMessage(DateField,DateValue,Message) { var params={ } params[DateField]=DateValue; chrome.storage.local.set( params,function(){ var options={ type: "basic", title: Messages.SiteUpdated, message: Message, iconUrl: "icon.png" } chrome.notifications.create("",options,function(){ chrome.notifications.onClicked.addListener(function(){ chrome.tabs.create({'url': WebLinks.Home}, function(tab) { }); }); }); }); }
"permissions": [ "storage", "tabs", "alarms", "notifications" ]
chrome.notifications.create("",options,function(){ chrome.notifications.onClicked.addListener(function(){ chrome.tabs.create({'url': WebLinks.Home}, function(tab) { });}); });
"web_accessible_resources": [ "icon.png" ]
خوب؛ کار افزونه تمام شده است ولی اجازه دهید این بار امکانات افزونه را بسط دهیم:
"options_page": "popup.html"
جایزگزینی صفحات یا Override Pages
"chrome_url_overrides": { "newtab": "newtab.htm" }
ایجاد یک تب اختصاصی در Developer Tools
"devtools_page": "devtools.htm"
<script src="devtools.js"></script>
chrome.devtools.panels.create( "Dotnettips Updater Tools", "icon.png", "devtoolsui.htm", function(panel) { } );
- One-Time Requests یا درخواستهای تک مرتبهای
- Long-Lived Connections یا اتصالات بلند مدت یا مصر
درخواستهای تک مرتبه ای
window.addEventListener("load", function() { chrome.extension.sendMessage({ type: "dom-loaded", data: { myProperty : "value" } }); }, true);
chrome.extension.onMessage.addListener(function(request, sender, sendResponse) { switch(request.type) { case "dom-loaded": alert(request.data.myProperty ); break; } return true; });
var port = chrome.runtime.connect({name: "my-channel"}); port.postMessage({myProperty: "value"}); port.onMessage.addListener(function(msg) { // do some stuff here });
chrome.runtime.onConnect.addListener(function(port) { if(port.name == "my-channel"){ port.onMessage.addListener(function(msg) { // do some stuff here }); } });
نمونه کد
آپلود نهایی کار در Google web store
برای آپلود نهایی کار به google web store که در آن تمامی برنامهها و افزونههای کروم قرار دارند بروید. سمت راست آیکن تنظیمات را بزنید و گزینه developer dashboard را انتخاب کنید تا صفحهی آپلود کار برای شما باز شود. دایرکتوری محتویات اکستنشن را zip کرده و آپلود نمایید. توجه داشته باشید که محتویات و سورس خود را باید آپلود کنید نه فایل crx را. بعد از آپلود موفقیت آمیز، صفحهای ظاهر میشود که از شما آیکن افزونه را در اندازه 128 پیکسل میخواهد بعلاوه توضیحاتی در مورد افزونه، قیمت گذاری که به طور پیش فرض به صورت رایگان تنظیم شده است، لینک وب سایت مرتبط، لینک محل پرسش و پاسخ برای افزونه، اگر لینک یوتیوبی در مورد افزونه دارید، یک شات تصویری از افزونه و همینطور چند تصویر برای اسلایدشو سازی که در همان صفحه استاندارد آنها را توضیح میدهد و در نهایت گزینهی جالبتر هم اینکه اکستنشن شما برای چه مناطقی تهیه شده است که متاسفانه ایران را ندیدم که میتوان همه موارد را انتخاب کرد. به خصوص در مورد ایران که آی پیها هم صحیح نیست، انتخاب ایران چنان تاثیری ندارد و در نهایت گزینهی publish را میزنید که متاسفانه بعد از این صفحه درخواست میکند برای اولین بار باید 5 دلار آمریکا پرداخت شود که برای بسیاری از ما این گزینه ممکن نیست.
سورس پروژه را میتوانید از اینجا ببینید و خود افزونه را از اینجا دریافت کنید.
دسترسی رایگان به Pluralsight به مدت یک ماه
در برنامهی backend این سری (که از انتهای مطلب قابل دریافت است)، به Controllers\MoviesController.cs مراجعه کرده و متدهای Get/Delete/Create آنرا با فیلتر [Authorize] مزین میکنیم تا دسترسی به آنها، تنها به کاربران لاگین شدهی در سیستم، محدود شود. در این حالت اگر به برنامهی React مراجعه کرده و برای مثال سعی در ویرایش رکوردی کنیم، اتفاقی رخ نخواهد داد:
علت را نیز در برگهی network کنسول توسعه دهندگان مرورگر، میتوان مشاهده کرد. این درخواست از سمت سرور با Status Code: 401، برگشت خوردهاست. برای رفع این مشکل باید JSON web token ای را که در حین لاگین، از سمت سرور دریافت کرده بودیم، به همراه درخواست خود، مجددا به سمت سرور ارسال کنیم. این ارسال نیز باید به صورت یک هدر مخصوص با کلید Authorization و مقدار "Bearer jwt" باشد.
به همین جهت ابتدا به src\services\authService.js مراجعه کرده و متدی را برای بازگشت JWT ذخیره شدهی در local storage به آن اضافه میکنیم:
export function getLocalJwt(){ return localStorage.getItem(tokenKey); }
import * as auth from "./authService"; axios.defaults.headers.common["Authorization"] = "Bearer " + auth.getLocalJwt();
مشکل! اگر برنامه را در این حالت اجرا کنید، یک چنین خطایی را مشاهده خواهید کرد:
Uncaught ReferenceError: Cannot access 'tokenKey' before initialization
برای رفع این خطا باید ابتدا مشخص کنیم که کدامیک از این ماژولها، اصلی است و کدامیک باید وابستهی به دیگری باشد. در این حالت httpService، ماژول اصلی است و بدون آن و با نبود امکان اتصال به backend، دیگر authService قابل استفاده نخواهد بود.
به همین جهت به httpService مراجعه کرده و import مربوط به authService را از آن حذف میکنیم. سپس در همینجا متدی را برای تنظیم هدر Authorizationاضافه کرده و آنرا به لیست default exports این ماژول نیز اضافه میکنیم:
function setJwt(jwt) { axios.defaults.headers.common["Authorization"] = "Bearer " + jwt; } //... export default { // ... setJwt };
http.setJwt(getLocalJwt());
تا اینجا اگر تغییرات را ذخیره کرده و سعی در ویرایش یکی از رکوردهای فیلمهای نمایش داده شده کنیم، اینکار با موفقیت انجام میشود؛ چون اینبار درخواست ارسالی، دارای هدر ویژهی authorization است:
روش بررسی انقضای توکنها در سمت کلاینت
اگر JWT قدیمی و منقضی شدهی از روز گذشته را آزمایش کنید، باز هم از سمت سرور، Status Code: 401 دریافت خواهد شد. اما اینبار در لاگهای برنامهی سمت سرور، OnChallenge error مشخص است. در این حالت باید یکبار logout کرد تا JWT قدیمی حذف شود. سپس نیاز به لاگین مجدد است تا یک JWT جدید دریافت گردد. میتوان اینکار را پیش از ارسال اطلاعات به سمت سرور، در سمت کلاینت نیز بررسی کرد:
function checkExpirationDate(user) { if (!user || !user.exp) { throw new Error("This access token doesn't have an expiration date!"); } user.expirationDateUtc = new Date(0); // The 0 sets the date to the epoch user.expirationDateUtc.setUTCSeconds(user.exp); const isAccessTokenTokenExpired = user.expirationDateUtc.valueOf() < new Date().valueOf(); if (isAccessTokenTokenExpired) { throw new Error("This access token is expired!"); } }
محدود کردن حذف رکوردهای فیلمها به نقش Admin در Backend
تا اینجا تمام کاربران وارد شدهی به سیستم، میتوانند علاوه بر ویرایش فیلمها، آنها را نیز حذف کنند. به همین جهت میخواهیم دسترسی حذف را از کاربرانی که ادمین نیستند، بگیریم. برای این منظور، در سمت سرور کافی است در کنترلر MoviesController، ویژگی [Authorize(Policy = CustomRoles.Admin)] را به اکشن متد Delete، اضافه کنیم. به این ترتیب اگر کاربری در سیستم ادمین نبود و درخواست حذف رکوردی را صادر کرد، خطای 403 را از سمت سرور دریافت میکند:
در برنامهی مثال backend این سری، در فایل Services\UsersDataSource.cs، یک کاربر ادمین پیشفرض ثبت شدهاست. مابقی کاربرانی که به صورت معمولی در سایت ثبت نام میکنند، ادمین نیستند.
در این حالت اگر کاربری ادمین بود، چون در توکن او که در فایل Services\TokenFactoryService.cs صادر میشود، یک User Claim ویژهی از نوع Role و با مقدار Admin وجود دارد:
if (user.IsAdmin) { claims.Add(new Claim(ClaimTypes.Role, CustomRoles.Admin, ClaimValueTypes.String, _configuration.Value.Issuer)); }
{ // ... "http://schemas.microsoft.com/ws/2008/06/identity/claims/role": "Admin", // ... }
نکته 1: اگر در اینجا چندین بار یک User Claim را با مقادیر متفاوتی، به لیست claims اضافه کنیم، مقادیر آن در خروجی نهایی، به شکل یک آرایه ظاهر میگردند.
نکته 2: پیاده سازی سمت سرور backend این سری، یک باگ امنیتی مهم را دارد! در حین ثبت نام، کاربران میتوانند مقدار خاصیت isAdmin شیء User را:
public class User : BaseModel { [Required, MinLength(2), MaxLength(50)] public string Name { set; get; } [Required, MinLength(5), MaxLength(255)] public string Email { set; get; } [Required, MinLength(5), MaxLength(1024)] public string Password { set; get; } public bool IsAdmin { set; get; } }
راه حل اصولی مقابلهی با آن، داشتن یک DTO و یا ViewModel خاص قسمت ثبت نام و جدا کردن مدل متناظر با موجودیت User، از شیءای است که اطلاعات نهایی را از کاربر، دریافت میکند. شیءای که اطلاعات را از کاربر دریافت میکند، نباید دارای خاصیت isAdmin قابل تنظیم در حین ثبت نام معمولی کاربران سایت باشد. یک روش دیگر حل این مشکل، استفاده از ویژگی Bind و ذکر صریح نام خواصی است که قرار است bind شوند و نه هیچ خاصیت دیگری از شیء User:
[HttpPost] public ActionResult<User> Create( [FromBody] [Bind(nameof(Models.User.Name), nameof(Models.User.Email), nameof(Models.User.Password))] User data) {
نکته 3: اگر میخواهید در برنامهی React، با مواجه شدن با خطای 403 از سمت سرور، کاربر را به یک صفحهی عمومی «دسترسی ندارید» هدایت کنید، میتوانید از interceptor سراسری که در قسمت 24 تعریف کردیم، استفاده کنید. در اینجا status code = 403 را جهت history.push به یک آدرس access-denied سفارشی و جدید، پردازش کنید.
نمایش یا مخفی کردن المانها بر اساس سطوح دسترسی کاربر وارد شدهی به سیستم
میخواهیم در صفحهی نمایش لیست فیلمها، دکمهی new movie را که بالای صفحه قرار دارد، به کاربرانی که لاگین نکردهاند، نمایش ندهیم. همچنین نمیخواهیم اینگونه کاربران، بتوانند فیلمی را ویرایش و یا حذف کنند؛ یعنی لینک به صفحهی جزئیات ویرایشی فیلمها و ستونی که دکمههای حذف هر ردیف را نمایش میدهد، به کاربران وارد نشدهی به سیستم نمایش داده نشوند.
در قسمت قبل، در فایل app.js، شیء currentUser را به state اضافه کردیم و با استفاده از ارسال آن به کامپوننت NavBar:
<NavBar user={this.state.currentUser} />
<Route path="/movies" render={props => <Movies {...props} user={this.state.currentUser} />} />
پس از این تغییر به فایل src\components\movies.jsx مراجعه کرده و شیء user را در متد رندر، دریافت میکنیم:
class Movies extends Component { // ... render() { const { user } = this.props; // ...
{user && ( <Link to="/movies/new" className="btn btn-primary" style={{ marginBottom: 20 }} > New Movie </Link> )}
در این تصویر همانطور که مشخص است، کاربر هنوز به سیستم وارد نشدهاست؛ بنابراین به علت null بودن شیء user، دکمهی New Movie را مشاهده نمیکند.
روش دریافت نقشهای کاربر وارد شدهی به سیستم در سمت کلاینت
همانطور که پیشتر در مطلب جاری عنوان شد، نقشهای دریافتی از سرور، یک چنین شکلی را در jwtDecode نهایی (یا user در اینجا) دارند:
{ // ... "http://schemas.microsoft.com/ws/2008/06/identity/claims/role": "Admin", // ... }
function addRoles(user) { const roles = user["http://schemas.microsoft.com/ws/2008/06/identity/claims/role"]; if (roles) { if (Array.isArray(roles)) { user.roles = roles.map(role => role.toLowerCase()); } else { user.roles = [roles.toLowerCase()]; } } }
export function isAuthUserInRoles(user, requiredRoles) { if (!user || !user.roles) { return false; } if (user.roles.indexOf(adminRoleName.toLowerCase()) >= 0) { return true; // The `Admin` role has full access to every pages. } return requiredRoles.some(requiredRole => { if (user.roles) { return user.roles.indexOf(requiredRole.toLowerCase()) >= 0; } else { return false; } }); } export function isAuthUserInRole(user, requiredRole) { return isAuthUserInRoles(user, [requiredRole]); }
در این کدها، adminRoleName به صورت زیر تامین شدهاست:
import { adminRoleName, apiUrl } from "../config.json";
{ "apiUrl": "https://localhost:5001/api", "adminRoleName": "Admin" }
اکنون که امکان بررسی نقشهای کاربر لاگین شدهی به سیستم را داریم، میخواهیم ستون Delete ردیفهای لیست فیلمها را فقط به کاربری که دارای نقش Admin است، نمایش دهیم. برای اینکار نیاز به دریافت شیء user، در src\components\moviesTable.jsx وجود دارد. یک روش دریافت کاربر جاری وارد شدهی به سیستم، همانی است که تا به اینجا بررسی کردیم: شیء currentUser را به صورت props، از بالاترین کامپوننت، به پایینتر کامپوننت موجود در component tree ارسال میکنیم. روش دیگر اینکار، دریافت مستقیم کاربر جاری از خود src\services\authService.js است و ... اینکار سادهتر است! به علاوه اینکه همیشه بررسی تاریخ انقضای توکن را نیز به صورت خودکار انجام میدهد و در صورت انقضای توکن، کاربر را در قسمت catch متد getCurrentUser، از سیستم خارج خواهد کرد.
بنابراین در src\components\moviesTable.jsx، ابتدا authService را import میکنیم:
import * as auth from "../services/authService";
class MoviesTable extends Component { columns = [ ... ]; // ... deleteColumn = { key: "delete", content: movie => ( <button onClick={() => this.props.onDelete(movie)} className="btn btn-danger btn-sm" > Delete </button> ) };
constructor() { super(); const user = auth.getCurrentUser(); if (user && auth.isAuthUserInRole(user, "Admin")) { this.columns.push(this.deleteColumn); } }
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: sample-28-backend.zip و sample-28-frontend.zip