Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ASP.net Cache + Singleton Pattern

I have a huge XML document which I have to parse it to generate domain objects.

Because the document is huge, i don't want to parse it every time a user requests it, but only first time, then saving all the objects into cache.

public List<Product> GetXMLProducts()
{
    if (HttpRuntime.Cache.Get("ProductsXML") != null)
    {
        return (List<Product>)(HttpRuntime.Cache.Get("ProductsXML"));
    }

    string xmlPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Content\\Products.xml");
    XmlReader reader = XmlReader.Create(xmlPath);
    XDocument doc = XDocument.Load(reader);

    List<Product> productsList = new List<Product>();
    // Parsing the products element

    HttpRuntime.Cache.Insert("ProductsXML", productsList);
    return productsList;
}

How is the best way i can make this function working in singleton and be thread-safe?

Fixed the saving an object into cache method (was a copy-paste mistake)

like image 545
Catalin Avatar asked Jul 16 '12 10:07

Catalin


2 Answers

Create a Lazy static and keep in memory for the lifetime of the application. And don't forget the "true" part, that's what makes it thread safe.

public static readonly Lazy<List<Product>> _product = new Lazy<List<Products>>(() => GetProducts(), true);

To add this to your model, just make it private and return _product.Value;

public MyModel
{
    ... bunch of methods/properties

    private static readonly Lazy<List<Product>> _products = new Lazy<List<Products>>(() => GetProducts(), true);

    private static List<Product> GetProducts()
    {
        return DsLayer.GetProducts();

    }

    public List<Product> Products { get { return _products.Value; } }
}

To create a singleton using Lazy<>, use this pattern.

public MyClass
{
    private static readonly Lazy<MyClass> _myClass = new Lazy<MyClass>(() => new MyClass(), true);

    private MyClass(){}

    public static MyClass Instance { get { return _myClass.Value; } }
}

Update/Edit:

Another lazy pattern for use within a context (i.e. Session)

Some Model that is saved in Session:

public MyModel
{
   private List<Product> _currentProducts = null;
   public List<Product> CurrentProducts 
   {
      get
      {
         return this._currentProducts ?? (_currentProducts = ProductDataLayer.GetProducts(this.CurrentCustomer));
      }
   }
}
like image 171
Chris Gessler Avatar answered Sep 24 '22 01:09

Chris Gessler


For the record - a lazy static (Chris Gessler's answer, which gets a +1 from me) is a good solution; in this case because you always want the data in memory. This answer looks specifically at the use of Cache (addressing your somewhat confusing code) and bringing another way to initialise a website.

The traditional way to do this is in an Application_Start handler in Global.asax(.cs). However I'm going to show another nice way:

Add the WebActivator package to your website with Nuget.

Then add the following code to a new .cs file in a new folder you create in your project, called App_Start:

[assembly: WebActivator.PreApplicationStartMethod(typeof(*your_namespace*.MyInit), 
  "Start", Order = 0)]
namespace *your_namespace*
{

  public static class MyInit {
    public static void Start() {
      string xmlPath = HostingEnvironment.MapPath("~/Content/Products.xml");
      using(var reader = XmlReader.Create(xmlPath)) {
       XDocument doc = XDocument.Load(reader);  

       List<Product> productsList = new List<Product>();  
       // Parsing the products element

       HttpRuntime.Cache.Insert("ProductsXML", productsList); 
      }
    }
  }
}

Note - the correct use of Cache.Insert, and a using for the stream reader. Your original code has other strange logic and semantic errors - such trying to assign to the result of a function and returning the value from the cache if it's null.

Note also you need to bring some namespaces in the above code - and look out for *your_namespace*.

like image 29
Andras Zoltan Avatar answered Sep 24 '22 01:09

Andras Zoltan