Generic implementation of System.Web.Cache to get or read and add or store items in the cache

by Heathesh 29. November 2010 21:48

I have a bunch of settings stored in the database that I know may never change. In order to read these settings I would prefer not to do a database call everytime I want to get a setting value. So in order to minimize the database calls, I decided to implement a simple manager class to get and add items to the cache.

To start off with, I created my manager class (as static) and called it CacheManager:

    /// <summary>
    /// Cache manager to store and read items in the cache
    /// </summary>
    public static class CacheManager
    {
    }


Next, I added the usings I knew I would need to the class:

using System.Diagnostics;
using System.Reflection;
using System.Text;
using System.Web.Caching;


I'm adding System.Diagnostics and System.Reflection to dynamically create the "keys" I'll be using to cache my items. I created a method that would dynamically generate this key as follows:

        /// <summary>
        /// Gets the cache key for a particular method
        /// </summary>
        /// <param name="parameters"></param>
        /// <returns></returns>
        private static string getCacheKey(params object[] parameters)
        {
            StackTrace stackTrace = new StackTrace();
            MethodBase methodBase = stackTrace.GetFrame(2).GetMethod();
            StringBuilder cacheKey = new StringBuilder();

            //the start of the key will be the full method namespace underscore the method name
            cacheKey.AppendFormat("{0}_{1}", methodBase.ReflectedType.FullName, methodBase.Name);

            //add any parameters as trailing values in the key
            if (parameters != null && parameters.Length > 0)
                foreach (object parameter in parameters)
                    cacheKey.AppendFormat("_{0}", parameter);

            //return the key we've generated
            return cacheKey.ToString();
        }


The "key" you use must be unique for each unique "object" you store in the cache. So the above method will determine the name of the calling method and allows you to pass in all the parameters of that method to create a unique key for every method you have. This should become clearer later on.

The next thing I wanted to do was create the relevant methods to get and add items to the cache, so I created the following two. The comments should be self explanatory:

        /// <summary>
        /// Gets an item from the cache
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="parameters"></param>
        /// <returns></returns>
        public static T Get<T>(params object[] parameters)
        {
            //generate the "key" for the cached item
            string key = getCacheKey(parameters);

            //set the return item to it's default
            T returnValue = default(T);

            //make sure the current http context is not null
            if (HttpContext.Current != null)
                if (HttpContext.Current.Cache.Get(key) != null)
                    returnValue = (T)HttpContext.Current.Cache.Get(key);

            //return the value
            return returnValue;
        }

        /// <summary>
        /// Adds the relevant item to the cache and returns it as well
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="itemToCache"></param>
        /// <param name="hoursToCache"></param>
        /// <param name="parameters"></param>
        /// <returns></returns>
        public static T Add<T>(T itemToCache, int hoursToCache, params object[] parameters)
        {
            //generate the "key" for the cached item
            string key = getCacheKey(parameters);

            //make sure the current http context is not null
            if (HttpContext.Current != null && itemToCache != null)
                HttpContext.Current.Cache.Add(key, itemToCache, null, DateTime.Now.AddHours(hoursToCache), TimeSpan.Zero, CacheItemPriority.BelowNormal, null);

            //return the cached item
            return itemToCache;
        }


As you can see each method allows you to pass in an array of parameters and that array is then passed to the getCacheKey method and used to generate the unique key. Now to implement my caching, I have a method called "ReadAll" in my SettingsManager class. And this is how I implemented the caching:

        /// <summary>
        /// Reads all the settings and returns a list of them
        /// </summary>
        /// <returns></returns>
        public static List<Setting> ReadAll()
        {
            //try and get the list of items from the cache
            List<Setting> settings = CacheManager.Get<List<Setting>>();

            //if we found something in the cache, return it
            if (settings != null && settings.Count > 0)
                return settings;

            //if we get here, we haven't found anything so connect to the database and populate the list of settings from there, but first initialize the list
            settings = new List<Setting>();

            //now populate from the database
            using (SqlConnection connection = new SqlConnection(ConfigurationManager.ConnectionStrings["EngineDatabase"].ConnectionString))
            {
                connection.Open();

                using (SqlCommand command = new SqlCommand("SettingReadAll", connection))
                {
                    command.CommandType = System.Data.CommandType.StoredProcedure;

                    using (SqlDataReader dataReader = command.ExecuteReader())
                        while (dataReader.Read())
                            settings.Add(populateSettingFromReader(dataReader));
                }

                connection.Close();
            }

            //now add the list to the cache for 24 hours, and return it
            return CacheManager.Add<List<Setting>>(settings, 24);
        }


The "populateSettingFromReader" is a simple method I created to return a "Setting" object from a data reader as follows:

        /// <summary>
        /// Populates a setting object from the data reader
        /// </summary>
        /// <param name="dataReader"></param>
        /// <returns></returns>
        private static Setting populateSettingFromReader(SqlDataReader dataReader)
        {
            return new Setting
            {
                Id = Convert.ToInt32(dataReader["Id"]),
                Name = Convert.ToString(dataReader["Name"]),
                Value = Convert.ToString(dataReader["Value"])
            };
        }


My "Setting" class is a simple entity class that looks like so:

    /// <summary>
    /// Setting entity
    /// </summary>
    public class Setting
    {
        /// <summary>
        /// Gets or sets the Id
        /// </summary>
        public int Id { get; set; }
        /// <summary>
        /// Gets or sets the Name
        /// </summary>
        public string Name { get; set; }
        /// <summary>
        /// Gets or sets the Value
        /// </summary>
        public string Value { get; set; }
    }


As you can see the "ReadAll" method has no parameters so you don't get to see how the getCacheKey method works with parameters. However the cache key generated for this method would be something like: "[Namespace].SettingsManager_ReadAll".

To illustrate what the getCacheKey would return for a method with parameters, I created a method that would only retrieve one setting at a time using the setting's name as a parameter:

        /// <summary>
        /// Reads the setting for a specified setting name
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public static Setting ReadByName(string name)
        {
            //get the value from the cache passing in the name as a parameter to generate the unique key
            Setting setting = CacheManager.Get<Setting>(name);

            if (setting != null)
                return setting;

            //if we get here, we haven't found anything so connect to the database and populate the list of settings from there
            using (SqlConnection connection = new SqlConnection(ConfigurationManager.ConnectionStrings["EngineDatabase"].ConnectionString))
            {
                connection.Open();

                using (SqlCommand command = new SqlCommand("SettingReadByName", connection))
                {
                    command.CommandType = System.Data.CommandType.StoredProcedure;
                    command.Parameters.Add(new SqlParameter("@Name", name));

                    using (SqlDataReader dataReader = command.ExecuteReader())
                        if (dataReader.Read())
                            setting = populateSettingFromReader(dataReader);
                }

                connection.Close();
            }

            //here I call the cache method passing in the "name" variable passed into this method as a parameter
            //which will be used as part of the cache key
            return CacheManager.Add<Setting>(setting, 24, name);
        }


I called this method like so:

            //get the theme setting value
            Setting setting = SettingsManager.ReadByName("Theme");

Now as you can see the value of the parameter I'm passing in is "Theme". So for this method the cache key would be something like: "[Namespace].SettingsManager_ReadByName_Theme". Note the trailing "_Theme".

Happy caching!

Tags: , , , , , , , ,

Development | .Net | Visual Studio 2010 | VS2010

Reading the DescriptionAttribute of enumerators and returning the corresponding enum based on the description

by Heathesh 24. May 2010 01:36

There are two things I wanted to achieve using the DescriptionAttribute for my enumerators. The first was to retrieve the description of an enumerator using an extension method, and the second was to return the relevant enumerator based on the description.

To begin with I created a static class I called EnumExtensionManager. I wanted to add my two methods as extension methods so hence the need for the static class. The very first thing I did was apply the description attribute to my enum definitions as follows:

    /// <summary>
    /// Enum of the regions
    /// </summary>
    public enum Region
    {
        [Description("All")]
        AllRegions = 0,
        [Description("Gauteng")]
        Gauteng = 1,
        [Description("Free State")]
        FreeState = 2,
        [Description("Eastern Cape")]
        EasternCape = 3,
        [Description("Western Cape")]
        WesternCape = 4,
        [Description("Mpumalanga")]
        Mpumalanga = 5,
        [Description("Northern Cape")]
        NorthernCape = 6,
        [Description("North West")]
        NorthWest = 7,
        [Description("KwaZulu-Natal")]
        KwaZuluNatal = 8,
        [Description("Limpopo")]
        Limpopo = 9
    }


Now, in order to retrieve the description of the enumerator I created the following static method in my EnumExtensionManager class:

        /// <summary>
        /// Gets the description of an enumerator
        /// </summary>
        /// <param name="enumerator"></param>
        /// <returns></returns>
        public static string GetDescription(this Enum enumerator)
        {
            //get the enumerator type
            Type type = enumerator.GetType();

            //get the member info
            MemberInfo[] memberInfo = type.GetMember(enumerator.ToString());

            //if there is member information
            if (memberInfo != null && memberInfo.Length > 0)
            {
                //we default to the first member info, as it's for the specific enum value
                object[] attributes = memberInfo[0].GetCustomAttributes(typeof(DescriptionAttribute), false);

                //return the description if it's found
                if (attributes != null && attributes.Length > 0)
                    return ((DescriptionAttribute)attributes[0]).Description;
            }

            //if there's no description, return the string value of the enum
            return enumerator.ToString();
        }


The method will return the description text for an enumerator or return the enumerator name as a string if it can't find the description. Since I created it as an extension method, it was easy enough to call it in code simply by invoking .GetDescription() on any enum value.

            Region regionEnumerator = Region.KwaZuluNatal;
            string regionDescription = regionEnumerator.GetDescription();


Next I wanted to be able to use the description of an enum and return the corresponding enum from it. I achieved this by adding the following extension method to me EnumExtensionManager class:

        /// <summary>
        /// Gets the enumerator from the description passed in
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="description"></param>
        /// <returns></returns>
        public static T GetEnumFromDescription<T>(this string description)
        {
            //get the member info of the enum
            MemberInfo[] memberInfos = typeof(T).GetMembers();

            if (memberInfos != null && memberInfos.Length > 0)
            {
                //loop through the member info classes
                foreach (MemberInfo memberInfo in memberInfos)
                {
                    //get the custom attributes of the member info
                    object[] attributes = memberInfo.GetCustomAttributes(typeof(DescriptionAttribute), false);

                    //if there are attributes
                    if (attributes != null && attributes.Length > 0)
                        //if the description attribute is equal to the description, return the enum
                        if (((DescriptionAttribute)attributes[0]).Description == description)
                            return (T)Enum.Parse(typeof(T), memberInfo.Name);
                }
            }

            //this means the enum was not found from the description, so return the default
            return default(T);
        }


Calling the method was easily done and it could be invoked on any string value like so:

            string regionDescription = "Northern Cape";
            Region regionEnumerator = regionDescription.GetEnumFromDescription<Region>();


That was it. Happy enumerating!

 

Tags: , , , , , ,

Development | .Net

A simple way to read an RSS feed using C#

by Heathesh 10. May 2010 22:26

I found a decent RSS news feed that was free (http://freenewsfeed.newsfactor.com/rss) and was looking at integrating it into my new blog design. In order to do this I wanted to create a simple class that would read an RSS feed based on a URL passed in, and I thought the best way to do this would be to first create a class I could use to store the RSS feed items. So I created the following:

    /// <summary>
    /// RSS feed item entity
    /// </summary>
    public class RssFeedItem
    {
        /// <summary>
        /// Gets or sets the title
        /// </summary>
        public string Title { get; set; }
       
        /// <summary>
        /// Gets or sets the description
        /// </summary>
        public string Description { get; set; }

        /// <summary>
        /// Gets or sets the link
        /// </summary>
        public string Link { get; set; }

        /// <summary>
        /// Gets or sets the item id
        /// </summary>
        public int ItemId { get; set; }

        /// <summary>
        /// Gets or sets the publish date
        /// </summary>
        public DateTime PublishDate { get; set; }

        /// <summary>
        /// Gets or sets the channel id
        /// </summary>
        public int ChannelId { get; set; }
    }


Next I needed a manager class that would read any RSS feed and populate a list of the above rss feed item object with the relevant data. To achieve this I created the following static class:

    /// <summary>
    /// RSS manager to read rss feeds
    /// </summary>
    public static class RssManager
    {
        /// <summary>
        /// Reads the relevant Rss feed and returns a list off RssFeedItems
        /// </summary>
        /// <param name="url"></param>
        /// <returns></returns>
        public static List<RssFeedItem> ReadFeed(string url)
        {
            //create a new list of the rss feed items to return
            List<RssFeedItem> rssFeedItems = new List<RssFeedItem>();

            //create an http request which will be used to retrieve the rss feed
            HttpWebRequest rssFeed = (HttpWebRequest)WebRequest.Create(url);

            //use a dataset to retrieve the rss feed
            using (DataSet rssData = new DataSet())
            {
                //read the xml from the stream of the web request
                rssData.ReadXml(rssFeed.GetResponse().GetResponseStream());

                //loop through the rss items in the dataset and populate the list of rss feed items
                foreach (DataRow dataRow in rssData.Tables["item"].Rows)
                {
                    rssFeedItems.Add(new RssFeedItem
                    {
                        ChannelId = Convert.ToInt32(dataRow["channel_Id"]),
                        Description = Convert.ToString(dataRow["description"]),
                        ItemId = Convert.ToInt32(dataRow["item_Id"]),
                        Link = Convert.ToString(dataRow["link"]),
                        PublishDate = Convert.ToDateTime(dataRow["pubDate"]),
                        Title = Convert.ToString(dataRow["title"])
                    });
                }
            }

            //return the rss feed items
            return rssFeedItems;
        }
    }


I was all set, all I had to do now was pass in a URL of an RSS feed, and I would be returned a list of rss feed items...

Happy rss-ing!

Tags: , , ,

Development | .Net | RSS



Powered by BlogEngine.NET 1.5.0.7 (with enhancements by Heathesh)
Theme by Mads Kristensen (with tweeks by Heathesh)

Certifications

Microsoft Certified Professional

Microsoft Certified Technology Specialist

Answer Questions

 

Tag cloud

Calendar

<<  July 2017  >>
MoTuWeThFrSaSu
262728293012
3456789
10111213141516
17181920212223
24252627282930
31123456

View posts in large calendar

http://heathesh.com