MVC एप्लिकेशन में डेटा को कैश कैसे करें


252

मैंने एमवीसी एप्लिकेशन में पेज कैशिंग और आंशिक पेज कैशिंग के बारे में बहुत सारी जानकारी पढ़ी है। हालाँकि, मैं जानना चाहूंगा कि आप डेटा को कैसे कैश करेंगे।

मेरे परिदृश्य में मैं LINQ से एंटिटीज़ (एंटिटी फ्रेमवर्क) का उपयोग करूंगा। GetNames (या जो भी तरीका है) की पहली कॉल पर मैं डेटाबेस से डेटा हड़पना चाहता हूं। मैं कैश में परिणाम सहेजना चाहता हूं और यदि मौजूद है तो कैश्ड संस्करण का उपयोग करने के लिए दूसरी कॉल पर।

क्या कोई इसका उदाहरण दिखा सकता है कि यह कैसे काम करेगा, इसे कहां लागू किया जाना चाहिए (मॉडल?) और यदि यह काम करेगा।

मैंने इसे पारंपरिक ASP.NET ऐप में देखा है, आमतौर पर बहुत स्थिर डेटा के लिए।


1
नीचे दिए गए उत्तरों की समीक्षा करने के लिए, इस बात पर विचार करना सुनिश्चित करें कि क्या आप चाहते हैं कि आपके नियंत्रक को डेटा एक्सेस और कैशिंग चिंताओं के लिए / जिम्मेदारी का ज्ञान हो। आम तौर पर आप इसे अलग करना चाहते हैं। ऐसा करने के अच्छे तरीके के लिए रिपॉजिटरी पैटर्न देखें: deviq.com/repository-pattern
ssmith

जवाबों:


75

अपने मॉडल में System.Web dll का संदर्भ लें और System.Web.Caching.Cache का उपयोग करें

    public string[] GetNames()
    {
      string[] names = Cache["names"] as string[];
      if(names == null) //not in cache
      {
        names = DB.GetNames();
        Cache["names"] = names;
      }
      return names;
    }

थोड़ा सरल लेकिन मुझे लगता है कि काम करेगा। यह MVC विशिष्ट नहीं है और मैंने हमेशा डेटा को कैशिंग करने के लिए इस पद्धति का उपयोग किया है।


89
मैं इस समाधान की अनुशंसा नहीं करता हूं: बदले में, आपको फिर से एक अशक्त वस्तु मिल सकती है, क्योंकि यह कैश में फिर से पढ़ रहा है और इसे कैश से पहले ही हटा दिया गया हो सकता है। मैं नहीं बल्कि: सार्वजनिक स्ट्रिंग [] GetNames () {स्ट्रिंग [] noms = कैश ["नाम"]; if (noms == null) {noms = DB.GetNames (); कैश ["नाम"] = noms; } return (noms); }
ओली

मैं ओली से सहमत हूं .. डीबी को वास्तविक कॉल से परिणाम प्राप्त करना कैश से उन्हें प्राप्त करने से बेहतर है
कोडकंबर

1
क्या यह DB.GetNames().AsQueryableक्वेरी में देरी की विधि के साथ काम करता है ?
चेस फ्लोरल

जब तक आप स्ट्रिंग से वापसी मूल्य नहीं बदलते [] IEnumerable <string>
terjetyl

12
यदि आप समय सीमा समाप्त नहीं करते हैं .. तो क्या डिफ़ॉल्ट रूप से कैश समाप्त हो जाता है?
चाका

403

यहाँ मैं एक अच्छा और सरल कैश हेल्पर क्लास / सेवा उपयोग कर रहा हूँ:

using System.Runtime.Caching;  

public class InMemoryCache: ICacheService
{
    public T GetOrSet<T>(string cacheKey, Func<T> getItemCallback) where T : class
    {
        T item = MemoryCache.Default.Get(cacheKey) as T;
        if (item == null)
        {
            item = getItemCallback();
            MemoryCache.Default.Add(cacheKey, item, DateTime.Now.AddMinutes(10));
        }
        return item;
    }
}

interface ICacheService
{
    T GetOrSet<T>(string cacheKey, Func<T> getItemCallback) where T : class;
}

उपयोग:

cacheProvider.GetOrSet("cache key", (delegate method if cache is empty));

कैश प्रदाता कैश में "कैश आईडी" के नाम से कुछ भी होने की जांच करेगा, और यदि ऐसा नहीं है, तो यह डेटा लाने और इसे कैश में संग्रहीत करने के लिए एक प्रतिनिधि विधि को कॉल करेगा।

उदाहरण:

var products=cacheService.GetOrSet("catalog.products", ()=>productRepository.GetAll())

3
मैंने इसे इसलिए अनुकूलित किया है कि इसके बजाय HttpContext.Current.Session का उपयोग करके उपयोगकर्ता सत्र के अनुसार कैशिंग तंत्र का उपयोग किया जाता है। मैंने अपने बेसकंट्रोलर वर्ग पर एक कैशे की संपत्ति भी डाल दी है ताकि इसकी आसान पहुंच और निर्माणकर्ता को यूनिट परीक्षण के लिए डीआई के लिए अनुमति दी जा सके। उम्मीद है की यह मदद करेगा।
वेस्टडिसकॉल्फ 12

1
आप अन्य नियंत्रकों के बीच पुन: प्रयोज्य के लिए भी इस वर्ग और विधि को स्थिर बना सकते हैं।
एलेक्स

5
यह वर्ग HttpContext पर निर्भर नहीं होना चाहिए। मैंने इसे केवल उदाहरण के उद्देश्य से यहाँ सरल बनाया है। कैश ऑब्जेक्ट को कंस्ट्रक्टर के माध्यम से डाला जाना चाहिए - इसे फिर अन्य कैशिंग तंत्रों से बदला जा सकता है। यह सब आईओसी / डीआई के साथ-साथ स्थैतिक (सिंगलटन) जीवन चक्र के साथ हासिल किया जाता है।
हिरोविज़ हुड जूल

3
@ बेंडन - और बदतर अभी भी, यह कैश कुंजी के लिए जगह में जादू के तार हैं, बजाय उन्हें विधि नाम और मापदंडों से संदर्भित करते हैं।
20

5
यह एक भयानक निम्न स्तर का समाधान है। जैसे अन्य लोगों के साथ गठबंधन किया है, आप इसे एक सुरक्षित, डोमेन-विशिष्ट वर्ग में लपेटना चाहेंगे। जादू नियंत्रकों की वजह से इसे सीधे अपने नियंत्रकों में एक्सेस करना एक बुरा सपना होगा।
जोश नं।

43

मैं टीटी की पोस्ट का जिक्र कर रहा हूं और निम्नलिखित दृष्टिकोण सुझाता हूं:

अपने मॉडल में System.Web dll का संदर्भ लें और System.Web.Caching.Cache का उपयोग करें

public string[] GetNames()
{ 
    var noms = Cache["names"];
    if(noms == null) 
    {    
        noms = DB.GetNames();
        Cache["names"] = noms; 
    }

    return ((string[])noms);
}

आपको कैश से फिर से पढ़ा गया मान वापस नहीं करना चाहिए, क्योंकि आप कभी नहीं जान पाएंगे कि क्या उस विशिष्ट क्षण में यह अभी भी कैश में है। यहां तक ​​कि अगर आपने इसे पहले भी बयान में डाला है, तो यह पहले से ही हो सकता है या कैश में कभी नहीं जोड़ा गया है - आपको अभी पता नहीं है।

इसलिए आप डेटाबेस से पढ़े गए डेटा को जोड़ते हैं और सीधे इसे वापस करते हैं, कैश से दोबारा नहीं पढ़ते हैं।


लेकिन क्या रेखा Cache["names"] = noms;कैश में नहीं डालती है?
उमर

2
@ बाडी हां यह करता है। लेकिन यह उदाहरण पहले ओली के लिए अलग है, क्योंकि वह फिर से कैश का उपयोग नहीं करता है - समस्या यह है कि बस कर: रिटर्न (स्ट्रिंग []) कैश ["नाम"]; .. COULD परिणाम एक शून्य मान में लौटाया जा रहा है, क्योंकि यह COULD समाप्त हो गया है। इसकी संभावना नहीं है, लेकिन ऐसा हो सकता है। यह उदाहरण बेहतर है, क्योंकि हम मेमोरी में डीबी से लौटे वास्तविक मूल्य को स्टोर करते हैं, उस मूल्य को कैश करते हैं, और फिर उस मूल्य को वापस करते हैं, न कि कैश से पढ़े गए मूल्य को फिर से पढ़ते हैं।
जेमीबारो

या ... कैश से फिर से पढ़ा गया मान, यदि यह अभी भी मौजूद है (! = Null)। इसलिए, कैशिंग के पूरे बिंदु। यह केवल कहने के लिए है कि यह शून्य मानों के लिए दोहरी जांच करता है, और जहां आवश्यक हो डेटाबेस पढ़ता है। बहुत स्मार्ट, धन्यवाद ओली!
सीन केंडल

क्या आप कृपया कुछ लिंक साझा कर सकते हैं जहाँ मैं मुख्य मूल्य आधारित अनुप्रयोग कैशिंग के बारे में पढ़ सकता हूँ। मुझे लिंक नहीं मिल पा रहे हैं।
अटूट

@ ओली, इस कैश रिकॉर्ड को CSHTML या HTML पेज से कैसे प्राप्त करें
दीपन राज

37

.NET 4.5+ फ्रेमवर्क के लिए

संदर्भ जोड़ें: System.Runtime.Caching

स्टेटमेंट का उपयोग करके जोड़ें: using System.Runtime.Caching;

public string[] GetNames()
{ 
    var noms = System.Runtime.Caching.MemoryCache.Default["names"];
    if(noms == null) 
    {    
        noms = DB.GetNames();
        System.Runtime.Caching.MemoryCache.Default["names"] = noms; 
    }

    return ((string[])noms);
}

.NET फ्रेमवर्क 3.5 और पुराने संस्करणों में, ASP.NET ने System.Web.Caching नाम स्थान में एक इन-मेमोरी कैश कार्यान्वयन प्रदान किया। .NET फ्रेमवर्क के पिछले संस्करणों में, कैशिंग केवल System.Web नामस्थान में उपलब्ध था और इसलिए ASP.NET वर्गों पर निर्भरता की आवश्यकता थी। .NET फ्रेमवर्क 4 में, System.Runtime.Caching नामस्थान में API होते हैं जो वेब और गैर-वेब अनुप्रयोगों दोनों के लिए डिज़ाइन किए गए हैं।

और जानकारी:


कहां से Db.GerNames()आ रहा है?
जूनियर

DB.GetNames DAL की एक विधि है जो डेटाबेस से कुछ नामों को लाती है। यह वह है जो आप सामान्य रूप से प्राप्त करेंगे।
जूफो

यह शीर्ष पर होना चाहिए क्योंकि इसका वर्तमान प्रासंगिक समाधान है
BYISHIMO Audace

2
धन्यवाद, System.Runtime.Caching नगेट पैकेज के रूप में अच्छी तरह से जोड़ने की जरूरत (v4.5)।
स्टीव ग्रीन

26

स्टीव स्मिथ ने दो महान ब्लॉग पोस्ट किए, जो प्रदर्शित करते हैं कि ASP.NET MVC में अपने कैश्ड रिपॉजिटरी पैटर्न का उपयोग कैसे करें। यह रिपॉजिटरी पैटर्न का प्रभावी ढंग से उपयोग करता है और आपको अपने मौजूदा कोड को बदलने के बिना कैशिंग प्राप्त करने की अनुमति देता है।

http://ardalis.com/Introducing-the-CachedRepository-Pattern

http://ardalis.com/building-a-cachedrepository-via-strategy-pattern

इन दो पदों में वह आपको दिखाता है कि इस पैटर्न को कैसे सेट किया जाए और यह भी बताया जाए कि यह उपयोगी क्यों है। इस पैटर्न का उपयोग करके आप अपने मौजूदा कोड के बिना किसी भी लॉजिक को देखकर कैशिंग प्राप्त कर सकते हैं। अनिवार्य रूप से आप कैश्ड रिपॉजिटरी का उपयोग करते हैं जैसे कि यह कोई अन्य रिपॉजिटरी था।


1
महान पोस्ट! साझा करने के लिए धन्यवाद!!
मार्क गुड

2013-08-31 तक लिंक मृत।
CBONo

3
सामग्री अब स्थानांतरित हो गई है। ardalis.com/Introducing-the-CachedRepository-Pattern और ardalis.com/building-a-cachedrepository-via-strategy-pattern
Uchitha

क्या आप कृपया कुछ लिंक साझा कर सकते हैं जहाँ मैं मुख्य मूल्य आधारित अनुप्रयोग कैशिंग के बारे में पढ़ सकता हूँ। मुझे लिंक नहीं मिल पा रहे हैं।
अटूट

4

AppFabric कैशिंग वितरित किया गया है और एक इन-मेमोरी कैशिंग टेक्निक है जो कई सर्वरों में भौतिक मेमोरी का उपयोग करके महत्वपूर्ण-मूल्य जोड़े में डेटा संग्रहीत करता है। AppFabric .NET फ्रेमवर्क अनुप्रयोगों के लिए प्रदर्शन और मापनीयता में सुधार प्रदान करता है। अवधारणाओं और वास्तुकला


यह Azure के लिए विशिष्ट है, सामान्य रूप से ASP.NET MVC के लिए नहीं।
हेनरी सी

3

@Hrvoje हूडो का जवाब बढ़ा ...

कोड:

using System;
using System.Runtime.Caching;

public class InMemoryCache : ICacheService
{
    public TValue Get<TValue>(string cacheKey, int durationInMinutes, Func<TValue> getItemCallback) where TValue : class
    {
        TValue item = MemoryCache.Default.Get(cacheKey) as TValue;
        if (item == null)
        {
            item = getItemCallback();
            MemoryCache.Default.Add(cacheKey, item, DateTime.Now.AddMinutes(durationInMinutes));
        }
        return item;
    }

    public TValue Get<TValue, TId>(string cacheKeyFormat, TId id, int durationInMinutes, Func<TId, TValue> getItemCallback) where TValue : class
    {
        string cacheKey = string.Format(cacheKeyFormat, id);
        TValue item = MemoryCache.Default.Get(cacheKey) as TValue;
        if (item == null)
        {
            item = getItemCallback(id);
            MemoryCache.Default.Add(cacheKey, item, DateTime.Now.AddMinutes(durationInMinutes));
        }
        return item;
    }
}

interface ICacheService
{
    TValue Get<TValue>(string cacheKey, Func<TValue> getItemCallback) where TValue : class;
    TValue Get<TValue, TId>(string cacheKeyFormat, TId id, Func<TId, TValue> getItemCallback) where TValue : class;
}

उदाहरण

एकल आइटम कैशिंग (जब प्रत्येक आइटम को उसकी आईडी के आधार पर कैश किया जाता है क्योंकि आइटम प्रकार के लिए संपूर्ण कैटलॉग को कैशिंग करना बहुत गहन होगा)।

Product product = cache.Get("product_{0}", productId, 10, productData.getProductById);

किसी चीज की कैशिंग करना

IEnumerable<Categories> categories = cache.Get("categories", 20, categoryData.getCategories);

टीआईडी ​​क्यों

दूसरा सहायक विशेष रूप से अच्छा है क्योंकि अधिकांश डेटा कुंजियाँ समग्र नहीं हैं। यदि आप समग्र कुंजियों का अक्सर उपयोग करते हैं तो अतिरिक्त विधियाँ जोड़ी जा सकती हैं। इस तरह आप कैश हेल्पर को पास करने के लिए कुंजी प्राप्त करने के लिए सभी प्रकार के स्ट्रिंग कॉन्फैक्शन या स्ट्रिंग करते हैं। यह डेटा एक्सेस विधि को पास करना भी आसान बना देता है क्योंकि आपको आईडी को रैपर विधि में पास नहीं करना पड़ता है ... उपयोग के अधिकांश मामलों के लिए पूरी बात बहुत ही जटिल और समरूप हो जाती है।


1
आपकी इंटरफ़ेस परिभाषाएँ "periodInMinutes" परम को याद कर रही हैं। ;-)
Tech0

3

यहाँ हिरोज़ा हूडो के जवाब में सुधार किया गया है। इस कार्यान्वयन में कुछ महत्वपूर्ण सुधार हुए हैं:

  • डेटा को अपडेट करने के लिए फ़ंक्शन के आधार पर कैश कुंजियां स्वचालित रूप से बनाई जाती हैं और इसमें पारित ऑब्जेक्ट निर्भरता को निर्दिष्ट करता है
  • किसी भी कैश अवधि के लिए समय अवधि में पास करें
  • थ्रेड सुरक्षा के लिए लॉक का उपयोग करता है

ध्यान दें कि इस पर निर्भरता ऑब्जेक्ट को क्रमबद्ध करने के लिए Newtonsoft.Json पर निर्भरता है, लेकिन इसे आसानी से किसी अन्य क्रमांकन विधि के लिए स्वैप किया जा सकता है।

ICache.cs

public interface ICache
{
    T GetOrSet<T>(Func<T> getItemCallback, object dependsOn, TimeSpan duration) where T : class;
}

InMemoryCache.cs

using System;
using System.Reflection;
using System.Runtime.Caching;
using Newtonsoft.Json;

public class InMemoryCache : ICache
{
    private static readonly object CacheLockObject = new object();

    public T GetOrSet<T>(Func<T> getItemCallback, object dependsOn, TimeSpan duration) where T : class
    {
        string cacheKey = GetCacheKey(getItemCallback, dependsOn);
        T item = MemoryCache.Default.Get(cacheKey) as T;
        if (item == null)
        {
            lock (CacheLockObject)
            {
                item = getItemCallback();
                MemoryCache.Default.Add(cacheKey, item, DateTime.Now.Add(duration));
            }
        }
        return item;
    }

    private string GetCacheKey<T>(Func<T> itemCallback, object dependsOn) where T: class
    {
        var serializedDependants = JsonConvert.SerializeObject(dependsOn);
        var methodType = itemCallback.GetType();
        return methodType.FullName + serializedDependants;
    }
}

उपयोग:

var order = _cache.GetOrSet(
    () => _session.Set<Order>().SingleOrDefault(o => o.Id == orderId)
    , new { id = orderId }
    , new TimeSpan(0, 10, 0)
);

2
if (item == null)ताला अंदर होना चाहिए। अब जब यह ifताला से पहले है, तो दौड़ की स्थिति हो सकती है। या इससे भी बेहतर, आपको ifलॉक से पहले रखना चाहिए , लेकिन अगर कैश अभी भी लॉक के अंदर पहली पंक्ति के रूप में खाली है, तो उसे रीचेक करें। क्योंकि अगर एक ही समय में दो धागे आते हैं, तो वे दोनों कैश को अपडेट करते हैं। आपका वर्तमान लॉक मददगार नहीं है।
अल कीप

3
public sealed class CacheManager
{
    private static volatile CacheManager instance;
    private static object syncRoot = new Object();
    private ObjectCache cache = null;
    private CacheItemPolicy defaultCacheItemPolicy = null;

    private CacheEntryRemovedCallback callback = null;
    private bool allowCache = true;

    private CacheManager()
    {
        cache = MemoryCache.Default;
        callback = new CacheEntryRemovedCallback(this.CachedItemRemovedCallback);

        defaultCacheItemPolicy = new CacheItemPolicy();
        defaultCacheItemPolicy.AbsoluteExpiration = DateTime.Now.AddHours(1.0);
        defaultCacheItemPolicy.RemovedCallback = callback;
        allowCache = StringUtils.Str2Bool(ConfigurationManager.AppSettings["AllowCache"]); ;
    }
    public static CacheManager Instance
    {
        get
        {
            if (instance == null)
            {
                lock (syncRoot)
                {
                    if (instance == null)
                    {
                        instance = new CacheManager();
                    }
                }
            }

            return instance;
        }
    }

    public IEnumerable GetCache(String Key)
    {
        if (Key == null || !allowCache)
        {
            return null;
        }

        try
        {
            String Key_ = Key;
            if (cache.Contains(Key_))
            {
                return (IEnumerable)cache.Get(Key_);
            }
            else
            {
                return null;
            }
        }
        catch (Exception)
        {
            return null;
        }
    }

    public void ClearCache(string key)
    {
        AddCache(key, null);
    }

    public bool AddCache(String Key, IEnumerable data, CacheItemPolicy cacheItemPolicy = null)
    {
        if (!allowCache) return true;
        try
        {
            if (Key == null)
            {
                return false;
            }

            if (cacheItemPolicy == null)
            {
                cacheItemPolicy = defaultCacheItemPolicy;
            }

            String Key_ = Key;

            lock (Key_)
            {
                return cache.Add(Key_, data, cacheItemPolicy);
            }
        }
        catch (Exception)
        {
            return false;
        }
    }

    private void CachedItemRemovedCallback(CacheEntryRemovedArguments arguments)
    {
        String strLog = String.Concat("Reason: ", arguments.RemovedReason.ToString(), " | Key-Name: ", arguments.CacheItem.Key, " | Value-Object: ", arguments.CacheItem.Value.ToString());
        LogManager.Instance.Info(strLog);
    }
}

3
कुछ स्पष्टीकरण जोड़ने पर विचार करें
माइक देबेला

2

मैंने इसे इस तरह से इस्तेमाल किया है और यह मेरे लिए काम करता है। https://msdn.microsoft.com/en-us/library/system.web.caching.cache.add(v=vs.110).aspx system.web.caching.cache.add के लिए पैरामीटर जानकारी।

public string GetInfo()
{
     string name = string.Empty;
     if(System.Web.HttpContext.Current.Cache["KeyName"] == null)
     {
         name = GetNameMethod();
         System.Web.HttpContext.Current.Cache.Add("KeyName", name, null, DateTime.Noew.AddMinutes(5), Cache.NoSlidingExpiration, CacheitemPriority.AboveNormal, null);
     }
     else
     {
         name = System.Web.HttpContext.Current.Cache["KeyName"] as string;
     }

      return name;

}

पूर्ण नाम स्थान के साथ पूरी तरह से योग्य सामान के लिए अतिरिक्त अपवोट !!
निंज़ानेल

1

मैं दो वर्गों का उपयोग करता हूं। सबसे पहले कैश कोर ऑब्जेक्ट:

public class Cacher<TValue>
    where TValue : class
{
    #region Properties
    private Func<TValue> _init;
    public string Key { get; private set; }
    public TValue Value
    {
        get
        {
            var item = HttpRuntime.Cache.Get(Key) as TValue;
            if (item == null)
            {
                item = _init();
                HttpContext.Current.Cache.Insert(Key, item);
            }
            return item;
        }
    }
    #endregion

    #region Constructor
    public Cacher(string key, Func<TValue> init)
    {
        Key = key;
        _init = init;
    }
    #endregion

    #region Methods
    public void Refresh()
    {
        HttpRuntime.Cache.Remove(Key);
    }
    #endregion
}

दूसरा एक कैश ऑब्जेक्ट्स की सूची है:

public static class Caches
{
    static Caches()
    {
        Languages = new Cacher<IEnumerable<Language>>("Languages", () =>
                                                          {
                                                              using (var context = new WordsContext())
                                                              {
                                                                  return context.Languages.ToList();
                                                              }
                                                          });
    }
    public static Cacher<IEnumerable<Language>> Languages { get; private set; }
}

0

मैं कहूंगा कि इस निरंतर डेटा समस्या पर सिंग्लटन को लागू करना इस मामले का एक समाधान हो सकता है जब आप पिछले समाधानों को बहुत जटिल पाते हैं

 public class GPDataDictionary
{
    private Dictionary<string, object> configDictionary = new Dictionary<string, object>();

    /// <summary>
    /// Configuration values dictionary
    /// </summary>
    public Dictionary<string, object> ConfigDictionary
    {
        get { return configDictionary; }
    }

    private static GPDataDictionary instance;
    public static GPDataDictionary Instance
    {
        get
        {
            if (instance == null)
            {
                instance = new GPDataDictionary();
            }
            return instance;
        }
    }

    // private constructor
    private GPDataDictionary() { }

}  // singleton

यह मेरे लिए पूरी तरह से काम करता है यही कारण है कि मैं हर किसी के लिए यह सलाह देता हूं जिसे इससे मदद मिल सकती है
गेरागमो


-8

आप ASP MVC में निर्मित कैशिंग का भी उपयोग कर सकते हैं:

कैश करने के लिए नियंत्रक विधि में निम्नलिखित विशेषता जोड़ें:

[OutputCache(Duration=10)]

इस स्थिति में इस का ActionResult 10 सेकंड के लिए कैश हो जाएगा।

यहाँ इस पर अधिक


4
OutputCache एक्शन के प्रतिपादन के लिए है, सवाल यह था कि कैशिंग डेटा के संबंध में पेज नहीं है।
कूलकोडर

यह विषय से बाहर है, लेकिन OutputCache डेटाबेस डेटा को भी कैश करता है
Muflix
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.