ورود

View Full Version : کش کردن Authentication



resident
شنبه 02 شهریور 1392, 11:33 صبح
سلام دوستان.
به نظرم اینکه به ازاء هر کنترلر و اکشن یکبار بریم بانک و سطح دسترسی کاربر به اون اکشن و کنترلر رو چک کنیم درست نیست. مثلا ممکنه یک فرد با یک کنترلر و اکشن زیاد کار داشته باشه. چک کردن هر باره درس

اینکه اول برنامه دسترسی ها رو بخونیم و تو یه سشن بریزیم خوبه یا نه؟
برای کش کردنش پیشنهادتون چیه؟

younesdoost
شنبه 02 شهریور 1392, 14:31 عصر
سلام.می تونید از Memory Caching استفاده کنید.معمولا تو 2 حالت از این راه استفاده میکنن:
یکی اینکه قراره دیتای زیادی از دیتابیس بکشیم بیرون.
دوم اینکه اندازه ی دیتا مساله ای نیست اما قراره به تعداد زیادی مراجعه کنیم.
شما می تونید از Memory cache استفاده کنید.هر وقت هم که سطح دسترسی ها تغییر کرد اطلاعات Cache رو عوض کنید اگر نه بذارید همونطور بمونه.
یک Class Library به پروژتون اضافه کنید با نام Infrastructure.داخلش کلاسی با نام CacheClass با محتویات زیر هستش:

public Object AddTocache(object cachedObject, string key)
{
var cache = MemoryCache.Default;
var expiration = DateTimeOffset.UtcNow.AddMinutes(5);
var @object = cachedObject;
cache.Add(key, @object, expiration);
return cache.Get(key,null);
}


قراره از این به بعد هر بار که می خوایم از دیتابیس بخونیم بریم تو کش سرور بگردیم ببینیم اطلاعاتی که می خوایم اونجا هست یا نه.اگه هست که از اونجا بخونیم.اگه نه از دیتابیس بخونیم و با کلاس بالا بریزیمش تو کش که از دفعه ی بعد از کش بخونیم.مثل همون registry تو ویندوز.

مثلا تابع زیر رو می ذارم،شما بسته به پروژه ی خودتون تغییرش بدید:


ProductRepository productRepo = new ProductRepository();
CacheClass cacheClass = new CacheClass();
public Product GetProductByName(string name)
{
var cache = MemoryCache.Default;
var cacheProduct = cache.Get("product/" + name, null);
if (cacheProduct == null)
{
var product = productRepo.GetProductByName(name);
cacheProduct = cacheClass.AddTocache(product, "Product/" + name);
}
Product cachedProduct = (Product)cacheProduct;
return cachedProduct;
}


همونطور که میبینید اول میرم تو کشی که با کلید product/ هستش دنبال اطلاعاتم میگردم.اگه null بود میرم از دیتابیس می خونم ولی کش رو هم برای دفعه ی بعد پر می کنم.آخر سر هم به مدلی که می خوام Cast می کنمش و بر می گردونمش.شما هم دیگه همه ی رجوع هاتون اول میشه به کش بعد اگه نبود دیتابیس.
ضمنان به کتابخانه ی زیر هم احتیاج دارید:

using System.Runtime.Caching;

resident
شنبه 02 شهریور 1392, 21:53 عصر
var cacheProduct = cache.Get("product/" + name, null);



دوست عزیز باز هم تشکر می کنم ازت بابت پاسخ های کاملت.
تو خط بالا میشه بر اساس چند کلید مستقیما رکورد مدنظر رو پیدا کرد یا اینکه باید روی کش خونده شده سرچ کرد؟ مثلا تو کاری که من دارم انجام میدم میخوام مقدار یک ستون رو بر اساس نام کنترلر و اکشن برگردونه. من احساس کردم شما هونجا در ورودی Get پارامتر مدنظرت برای سرچ رو هم بهش دادی. نمیدونم درست متوجه شدم یا نه....

younesdoost
یک شنبه 03 شهریور 1392, 15:51 عصر
کلید فقط یه string میتونه باشه دیگه.یعنی تو مورد شما می تونه مثلا "Controller/Action" باشه.
آره اونجا تو Get من کلید رو بهش دادم ببینم چی بهم بر می گردونه.در حقیقت تو اون خونه با اون کلید فقط به object در هر زمان می تونه باشه دیگه.مگه اینکه خالی باشه.

resident
یک شنبه 03 شهریور 1392, 16:45 عصر
مشکل من تو پر کردن کش برای اولین باره.
وقتی فرد لاگین کرد میریم و کل دسترسی هاشو میخونیم میاریم. یه لیست شامل Controller , Action , Access
. حالا برای اضافه کردن لیست به کش باید به ازاء هر آبجکت از اون لیست یکبار تابع AddTocache رو صدا بزنیم و کلیدش رو هم تعیین کنیم یا روشی دیگه ای باید کار کرد.
من وقتی کل لیست رو میدم دیگه نمی تونم به ازاء هر رکورد لیست یه کلید براش تعریف کنم.

younesdoost
یک شنبه 03 شهریور 1392, 16:59 عصر
آهان خب نه دیگه.شما هر چیزی که بخواید رو می تونید تو کش بریزید فقط آخر سر باید به همون نوعی که می خواید Cast بشه.ببینید من همون تابع قبلی رو می نویسم منتها این دفعه یه لیستی از Product ها رو یکدفعه تو کش میریزم.اینطوری میشه:

public List<Product> GetAllProducts()
{
var cache = MemoryCache.Default;
var cacheProducts = cache.Get("products");
if (cacheProducts == null)
{
var products = productRepo.GetAllProducts();
cacheProducts = cacheClass.AddTocache(products, "Products");
}
List<Product> cachedProducts = (List<Product>)cacheProducts;
return cachedProducts;
}

همونطور که می بینید دونه دونه Product ها رو اضافه نکردم.چون ورودی تابع AddToCache یه object هستش پس می تونم یه لیستی از Product ها رو هم بهش بدم.بعد آخر سر هم موقع خوندن Cast میکنمش به همون List<Product> و به همه ی اجزا و پراپرتیاش دسترسی پیدا می کنم.

resident
یک شنبه 03 شهریور 1392, 17:40 عصر
آهان خب نه دیگه.شما هر چیزی که بخواید رو می تونید تو کش بریزید فقط آخر سر باید به همون نوعی که می خواید Cast بشه.ببینید من همون تابع قبلی رو می نویسم منتها این دفعه یه لیستی از Product ها رو یکدفعه تو کش میریزم.اینطوری میشه:

public List<Product> GetAllProducts()
{
var cache = MemoryCache.Default;
var cacheProducts = cache.Get("products");
if (cacheProducts == null)
{
var products = productRepo.GetAllProducts();
cacheProducts = cacheClass.AddTocache(products, "Products");
}
List<Product> cachedProducts = (List<Product>)cacheProducts;
return cachedProducts;
}

همونطور که می بینید دونه دونه Product ها رو اضافه نکردم.چون ورودی تابع AddToCache یه object هستش پس می تونم یه لیستی از Product ها رو هم بهش بدم.بعد آخر سر هم موقع خوندن Cast میکنمش به همون List<Product> و به همه ی اجزا و پراپرتیاش دسترسی پیدا می کنم.

من سوالمو بد مطرح می کنم. البته تو متن هم نوشتن یه کم سخته....
تا اینجا که شما نوشتید ok
ببینید اینجا شما لیستی از محصولات رو ریختید تو کش و کلیدش رو هم گذاشتید Products. هر جا هم خواستید کلش رو بخونید از کش ، با دستور cache.Get("products") اینکار رو کردید.
حالا زمانیکه میخواهید ببینید یه محصول هست یا نه، اومدید و با یه کلید (با دستور زیر) این کار رو کردید.


var cacheProduct = cache.Get("product/" + name, null);

منم برای سطوح دسترسی اومدم اطلاعات مربوط به سطوح دسترسی کاربر رو خوندم و ریختم تو کش و کلید Permission رو هم براش تعریف کردم. حالا زمانیکه میخوام بدونم به یه اکشن تو کنترلر خاصی دسترسی داره یا نه چطور باید این کار رو انجام بدم . شما فرمودید با کلید کنترلر/اکشن این کار رو بکنم. یه چیزی مثل دستور زیر:

cache.Get("Permission/" + controller+"/"+action, null);
ok ولی من میگم یه جوری به کش باید بگیم وقتی می گیم controller+"/"+action کدوم اطلاعات لیستی که بهش دادیم رو با هم ترکیب کنه و اگه معادل چیزی بود که ما میخواستیم اونو برای ما برگردونه؟؟؟؟


اگه در مورد مثال خودتون بخوام بگم اینطور میشه: شما فرض کن Product شما شامل Code,name,price است.
وقتی شما میگی :

var cacheProduct = cache.Get("product/" + name, null);
از کجا می فهمه منظور شما از این name ای که شما چسبوندی به "product/" کدوم فیلد است؟ code است؟ price است؟ باید یه جا براش تعریف کنیم....

امیدوارم تونسته باشم منظورمو برسونم

younesdoost
دوشنبه 04 شهریور 1392, 13:56 عصر
خب پارامتر ارسالی از لایه های بالاتر میاد دیگه.این چیزی که برای شما نوشتم لایه ی Repository بود.لایه ی Application و UI رو ببینید:
Application:

CacheProductRepository cacheProductReop = new CacheProductRepository();
public Product GetProductByName(string name)
{
return cacheProductReop.GetProductByName(name);
}

UI:

ProductApplication ProductApp = new ProductApplication();
public ActionResult Show(string name)
{
return View(Mapper.Map<ProductViewModel>(ProductApp.GetProductByName(name)));
}


داخل View هم که پارامتر ارسالی برای این Action نام محصول هستش.
البته اینم بگم اینکه شما داری داخل مموری سرور کش می کنی اونوقت نفر A که بیاد و سطح دسترسی هاش خونده بشه و کش بشه،نفر B هم که بیاد چون کش همون منقضی نشده همون سطح دسترسی های نفر A بهش اختصاص داده میشه.برای جلوگیری از این کار باید کلیدتون رو اختصاصی کنید.یعنی مثلا UserId هر کاربر رو هم تو اسم کیلیدتو بیارید.
بعدم که همه ی این کارارو کردید با ابزارها و وبسایت های مختلفی که وجود داره تست کنید ببینید اگه دیتا رو از دیتابیس بخونید سرعت بالاتره یا از کش.چون بالاخره مموری سرور هم یه اندازه ای داره دیگه.معمولا با اطلاعاتی که هر لحظه در حال تغییر هستش این کار رو نمی کنن.برای اطلاعاتی مثل اطلاعات شما که اختصاصی هستش از این روش استفاده می کنن.برای اطلاعاتی هم که متغیر نیست از اتریبیوت هایی که خود MVC داره استفاده میشه.
اینطوری. (http://www.asp.net/mvc/tutorials/older-versions/controllers-and-routing/improving-performance-with-output-caching-cs)

resident
سه شنبه 05 شهریور 1392, 14:48 عصر
دوست عزیز ممنون از توضیحات تکمیلی ولی باز منظور من این نبود.

ببینبد key تو تابع AddTocache یه string است. درسته؟ فرض کنید من یه select زدم و یه لیستی(با 8 فیلد) از یه object دارم.
ولی تو مثال ما باید 3 تا از ستونهای اون لیست رو به عنوان key قرار بدم(کنترلر/اکشن و آیدی یوزر)
با فرض اینکه میخوام برای اولین بار کش رو پر کنم چطور باید دستور پر کردن cache با AddTocache رو بنویسم؟
*** در حقیقت مشکل من با تعیین کلید(متشکل از 3 فیلد) برای AddTocache است. بقیه قسمتهایی که شما فرمودید رو متوجه شدم.
امیدوارم خستتون نکرده باشم.

resident
سه شنبه 05 شهریور 1392, 15:19 عصر
البته اینم بگم اینکه شما داری داخل مموری سرور کش می کنی اونوقت نفر A که بیاد و سطح دسترسی هاش خونده بشه و کش بشه،نفر B هم که بیاد چون کش همون منقضی نشده همون سطح دسترسی های نفر A بهش اختصاص داده میشه.برای جلوگیری از این کار باید کلیدتون رو اختصاصی کنید.یعنی مثلا UserId هر کاربر رو هم تو اسم کیلیدتو بیارید.

اگه اینطوره نظرتون چیه که یکبار به ازاء همه کاربرها سطح دسترسی ها رو کش کنیم و همیشه از اون بخونیم؟ منظورم اینه که به ازاء تک تک کاربرها یکبار نخونیم تا تو حافظه مصرفی هم صرفه جویی کنیم. چون ممکنه تعداد کاربرانی که سطح دسترسی مشترک دارن زیاد باشه و با یکبار خوندن حافظه کمتری اشغال میشه.

younesdoost
چهارشنبه 06 شهریور 1392, 18:04 عصر
نه دوست من.بهر حال مبحث باید به یه نتیجه ای برسه که دیگران هم بتونن استفاده کنن.
توی پست اولی که نوشتم داخل تابع GetProductByName رو ببینید.اگه داخل کش خالی باشه (if (cacheProduct == null)) میره از لایه ی Repository می خونه و لیست اشیا رو میاره(var product = productRepo.GetProductByName(name);).همونجا هم می تونید کلید مناسب رو براش انتخاب کنید دیگه(cacheProduct = cacheClass.AddTocache(product, "Product/" + name);).
امیدوارم منظورتون رو درست فهمیده باشم.

در مورد سوال دومتون هم: بیشتر مهم اینه که حجم اطلاعاتی که میریزید تو مموری زیاد نباشه.باید به ازاء هر کاربری که الان دارید باهاش کار می کنید بخونید دیگه.یعنی راه دیگه ای ندارید.چون اطلاعات کاربر A رو که نمیتونید برای B هم استفاده کنید.حتی اگه مشترک باشن.سرو کار ما با کاربر فعلیه.

resident
چهارشنبه 06 شهریور 1392, 18:19 عصر
نه دوست من.بهر حال مبحث باید به یه نتیجه ای برسه که دیگران هم بتونن استفاده کنن.

تبریک میگم بهتون به خاطر این همه صبر و حوصله :)


نه دوست من.بهر حال مبحث باید به یه نتیجه ای برسه که دیگران هم بتونن استفاده کنن.
توی پست اولی که نوشتم داخل تابع GetProductByName رو ببینید.اگه داخل کش خالی باشه (if (cacheProduct == null)) میره از لایه ی Repository می خونه و لیست اشیا رو میاره(var product = productRepo.GetProductByName(name);).همونجا هم می تونید کلید مناسب رو براش انتخاب کنید دیگه(cacheProduct = cacheClass.AddTocache(product, "Product/" + name);).
امیدوارم منظورتون رو درست فهمیده باشم.

باز هم در مورد این قسمت جوابمو نگرفتم....


یه سوال دیگه.
فرض کنید کاربر دکمه خروج رو زده. حالا چطور می گید کش مربوط به این کاربر رو حذف کنه؟ با توجه به اینکه مثلا در مورد مثال شما هر محصولی که تو رم کش می شه یه key منحصر به فرد داره .میشه همه رو یه جا حذف کرد؟ در مورد مثال دسترسی ها که کلید شامل سه قسمت نام کنترلر/نام اکشن / آیدی کاربره چطور میشه؟ باید بگیم کش مربوط به یه آیدی خاص رو حذف کنه.

younesdoost
پنج شنبه 07 شهریور 1392, 21:15 عصر
خواهش می کنم دوست من.
ببینید من یه بار سناریویی که از پست شما متوجه شدم رو میگم ببینید درست متوجه شدم یا نه:
شما میگید که مثلا اون بار اولی که می خواید از روی کش بخونید(که قطعا خالی هم هستش و به نتیجه ای نمی رسید) چطور اون رو پر بکنید.

جواب من برای این جمله ای که متوجه شدم اینه که اگه خالی بود از دیتابیس بخونید و بریزید داخل کش.
بازم اگه درست متوجه نشدم جمله ای که فهمیدم رو اصلاح کنید بگید تا در موردش حرف بزنیم.

در مورد سوال دوم هم اصلا چرا باید کش پاک بشه.خب این کش با کلید مربوط به این کاربر همونجا بمونه که دفعه های بعد هم از روش خونده بشه دیگه.تا زمانی که خودش منقضی بشه.اما اگه برای تست برنامه نیاز به پاک کردن کش دارید کافیه یه تغییری در web.config (مثل گذاشتن یه space و دوباره پاک کردن آن space) بوجود بیارید و دوباره پروزه رو Build کنید.

resident
دوشنبه 11 شهریور 1392, 16:32 عصر
در مورد سوال دوم هم اصلا چرا باید کش پاک بشه.خب این کش با کلید مربوط به این کاربر همونجا بمونه که دفعه های بعد هم از روش خونده بشه دیگه.تا زمانی که خودش منقضی بشه.اما اگه برای تست برنامه نیاز به پاک کردن کش دارید کافیه یه تغییری در web.config (مثل گذاشتن یه space و دوباره پاک کردن آن space) بوجود بیارید و دوباره پروزه رو Build کنید.

اگه هنگام خروج پاک نکنیم حافظه بیخودی پر نمیشه؟ چون ممکنه کاربر به محض اینکه خارج شد ، قبل از این که Cache اش منقضی بشه دوباره وارد سیستم بشه و به این ترتیب دوباره اطلاعات رو وارد حافظه می کنیم و این ممکنه بارها اتفاق بیفته.

younesdoost
چهارشنبه 13 شهریور 1392, 17:17 عصر
اینکه حافظه رو چطور مدیریت کنید بستگی به خودتون داره.تصمیمتون رو بر اساس تعداد کاربرا و اطلاعاتی که می خواید ذخیره کنید بگیرید اما معمولا پاک نمیکنن اطلاعات رو که هر چه کمتر برای دستیابی به اطلاعات به سراغ دیتابیس برن.اما اگه اصرار به پاک کردنش دارید می تونید با دادن کلید کش رو پاک کنید(تو همون کلاس کش).

cache.Remove(key);