The best VPN 2024

The Best VPS 2024

The Best C# Book

How to refactor asp.net cache code?

In this article, I will share with you my experience with how to refactor asp.net cache code, for a website, and improve performance’s best way is to make full use of the cache. For the back-end technology, in order to make the website have better throughput and carry more users, in addition to the reasonable allocation of server resources, caching has always been an eternal topic.

Because our technical system still uses ASP.NET WebForm, and it also provides us with the corresponding API. Caching in ASP.NET is a great API that lets you store frequently accessed data in memory so that it can be retrieved faster in the future. As a result, you can get data almost instantly, rather than making relatively slow calls to the database or accessing other APIs.

Caching Conundrum

How to refactor asp.net cache code?
How to refactor asp.net cache code?

However, when using caches, cache expiration control is a big problem. Cache invalidation is one of only two problems in computer science. Cache invalidation simply means determining when stored data needs to be refreshed. But it’s difficult because we can’t predict the future, data may need to be refreshed in 2 minutes, or it may be valid for a whole week.

For the problem of cache refresh, ASP.NET’s Cache interface provides us with corresponding APIs, but these are far from enough. We don’t want to call them manually every time. It is better to automatically refresh the cache for us to keep the cached data up-to-date.

unlimitchance
How to refactor asp.net cache code?

Cache Code in the Project

The cache interface that comes with ASP.NET can store any type of object. Let’s take a look at the sample code from Microsoft’s official website, and then show the decaying cache code in our real project:

public DataTable GetCustomers(bool BypassCache)
{
   string cacheKey = "CustomersDataTable";
   object cacheItem = Cache[cacheKey] as DataTable;
   if((BypassCache) || (cacheItem == null))
   {
      cacheItem = GetCustomersFromDataSource();
      Cache.Insert(cacheKey, cacheItem, null,
      DateTime.Now.AddSeconds(GetCacheSecondsFromConfig(cacheKey), 
      TimeSpan.Zero);
   }
   return (DataTable)cacheItem;
}

How to refactor asp.net cache code?

The above code is the code that most people will write. The logic is very simple. First, take out the data representing the customer collection from the cache and convert it into a DataTable. If there is data in the cache, return it directly, otherwise, enter the if logic and execute from The operation of the database query returns data and writes it to the cache.

Is the above code a classic piece of code? But have you ever wondered, does it look bad when we have thousands of such codes in our project? As the project went on and on, the mountain of shit just piled up.

Let’s take a look at the example of the Redis cache used in our real project. Of course, the main point of this article is to discuss the asp.net cache. In fact, you understand the practice recommended in this article, which can be extended to similar business scenario codes.

public class ServiceRep : BaseRep<Service>
    {
        private readonly string CacheKey = typeof(Service).FullName;
      
        public new IEnumerable<Service> Query()
        {
            if (CacheValues.Enabled)
            {
                List<Service> services = CacheManager.GetCache<List<Service>>(CacheKey);
                if (services != null && services.Count > 0)
                {
                    return services;
                }

                services = base.Query().ToList();
                CacheManager.SetCache(CacheKey, services);
                return services;
            }
            return base.Query();
        }
        
        //other codes....
       
        public new int Save(Service service)
        {
            try
            {
                int res = base.Save(service);
                RemoveCache();
                return res;
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
                throw e;
            }
        }

        public new int Delete(Guid id)
        {
            int res = base.Delete(id);
            if (CacheValues.Enabled)
            {
                CacheManager.RemoveCache(CacheKey);
                Query();
            }
            return res;
        }
       
        private void RemoveCache()
        {
            if (!CacheValues.Enabled)
            {
                return;
            }
            CacheManager.RemoveCache(CacheKey);
        }
    }

How to refactor asp.net cache code?

It is not difficult to find methods such as Query(), Save(), Delete(), and RemoveCache() from the sample code above, which correspond to the write and remove operations of cache operations, etc.; if there are hundreds of tables in the project, all If you write a data access class like this and cache the data, then this kind of cookie-cutter code will be a test of your physical strength.

For the above sample code, we can use the SRP[Single Responsibility Principle] in coding practice to carry out encapsulation and reconstruction, so that the data from the database can be put into the cache without writing the same logic code in related methods, that is, a large number of if-else, etc. logic code.

How to refactor asp.net cache code?
How to refactor asp.net cache code?

How to refactor asp.net cache code?

As the project grows, the number of class files increases. If you rely on physical strength to write this kind of repetitive code, is it a waste of the title of engineer? So how should we encapsulate to make the cached code disappear from our business code? The answer is to allow the required data to be automatically loaded into the cache. When you want to remove the cache, it is also automatically processed and OK.

Let’s take a look at the final effect after packaging first, and then introduce how to do it.

Final Code

Code for caching data:

[AutoCache(CacheKey = nameof(GlobalRuntimeCache.znlive.com), IsNullSaveDefault = true)]
public new IEnumerable<Service> Query() => base.Query();

How to refactor asp.net cache code?

Code to remove cache:

[AutoRemoveCache(CacheKey = nameof(GlobalRuntimeCache.znlive.com))]
public new int Save(Service service) => base.Save(service);

How to refactor asp.net cache code?

Through the ultimate encapsulation shown above, it is realized that the query data only focuses on the query data, the saving function code only focuses on saving the data, and the logical processing of the cache is implemented by two features, AutoCache and AutoRemoveCache respectively. When a method needs to apply the cache, just add the corresponding feature directly, so that the business method can only focus on the business, which satisfies the [Single Responsibility Principle] and is quite friendly to unit testing.

Start refactoring

When you mark the AutoCache feature on a method with a return value, you can store the return value of the method in the cache, and when you call the method next time, the cached value will be returned automatically, without the need for real Business processing, such as querying the database, calling the network interface, etc.

The processing of the cache key is also automatically generated within this feature. The bottom layer will generate a cache key through MD5 according to the parameter name and parameter value of the called method (that is, call the GetKey method). Write the AutoCacheAttribute class and implement the IMethodAdvice interface. This interface needs to reference the [MrAdvice] class library through NuGet. This library is an open-source and free aspect-oriented component. This article will briefly introduce what aspect-oriented programming is and what it can do for you later in this article. What problems do we solve?

Automatic cache implementation:

    /// <summary>
    /// Using AOP to Realize Automatic Caching
    /// </summary>
    public class AutoCacheAttribute : Attribute, IMethodAdvice
    {
        /// <summary>
        /// cache key
        /// </summary>
        public string CacheKey { get; set; }

        /// <summary>
        /// In the cache area, mark which module/business cache belongs to, which is convenient for quick clearing
        /// </summary>
        public string CacheArea { get; set; }

        /// <summary>
        /// sliding expiration
        /// </summary>
        public bool EnableSliding { get; set; }

        /// <summary>
        /// Cache time, minutes
        /// </summary>
        public int CacheMinutes { get; set; }

        /// <summary>
        /// If the cache value is empty, automatically initialize the object of this type and store it in the cache
        /// </summary>
        public bool IsNullSaveDefault { get; set; }

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="cacheKey">cache key</param>
        /// <param name="cacheArea">In the cache area, mark which module/business cache belongs to, which is convenient for quick clearing</param>
        /// <param name="cacheMinutes">Cache time, minutes, default 0 minutes (represents permanent cache)</param>
        /// <param name="enableSliding">Use the sliding expiration cache control strategy</param>
        /// <param name="isNullSaveDefault">If empty, cache the default value</param>
        public AutoCacheAttribute(string cacheKey = "", string cacheArea = "", int cacheMinutes = 0, bool enableSliding = false, bool isNullSaveDefault = false)
        {
            CacheKey = cacheKey;
            CacheArea = cacheArea;
            EnableSliding = enableSliding;
            CacheMinutes = cacheMinutes;
            IsNullSaveDefault = isNullSaveDefault;
        }

        /// <summary>
        /// AOP component interception method, used to realize automatic caching, and return directly when there is caching;
        /// When there is no cache, after calling the intercepted method, if there is a return value, the data will be automatically cached
        /// </summary>
        /// <param name="context"></param>
        public void Advise(MethodAdviceContext context)
        {
            var key = CacheKey.IsNullOrEmpty() ? GetKey(context) : CacheKey;
            if (!CacheArea.IsNullOrEmpty()) key = $"{CacheArea}_{key}";
            int cacheMinutes = 0 >= CacheMinutes ? 0 : CacheMinutes;

            if (context.HasReturnValue && key.TryGetCache(out object m))
            {
                context.ReturnValue = m;
            }
            else
            {
                context.Proceed();

                if (context.HasReturnValue)
                {
                    if (IsNullSaveDefault && context.ReturnValue == null)
                    {
                        var type = context.TargetType;
                        var d = Activator.CreateInstance(type);
                        if (cacheMinutes == 0)
                            d.SetCache(key);
                        else
                            d.SetCache(key, cacheMinutes, EnableSliding);
                    }
                    if (context.ReturnValue != null)
                    {
                        if (cacheMinutes == 0)
                            context.ReturnValue.SetCache(key);
                        else
                            context.ReturnValue.SetCache(key, cacheMinutes, EnableSliding);
                    }
                }
            }
        }

        /// <summary>
        /// To obtain the cache key, the key rule is: md5 (full class name | method name | parameter list split array | json array of parameter values), which can ensure uniqueness
        /// </summary>
        /// <param name="context"></param>
        /// <returns></returns>
        private string GetKey(MethodAdviceContext context)
        {
            var array = context.TargetMethod.GetParameters();
            var key = array.Select(x => { return context.Arguments[x.Position].ToJson(); });
            var cacheKey = $"{context.Target}|{context.TargetName}|{string.Join("_", array.Select(x => x.Name))}|{string.Join("_", key)}".ToMd5Hash();
            return cacheKey;
        }
}

How to refactor asp.net cache code?

Automatically remove cache implementation:

    /// <summary>
    /// Automatically remove cache
    /// </summary>
    public class AutoRemoveCacheAttribute : Attribute, IMethodAdvice
    {
        /// <summary>
        /// Cache key to be removed
        /// </summary>
        public string CacheKey { get; set; }

        /// <summary>
        /// Cache area to be removed
        /// </summary>
        public string CacheArea { get; set; }

        /// <summary>
        /// Automatically remove cache
        /// </summary>
        /// <param name="cacheKey">cache key</param>
        /// <param name="cacheArea">cache area</param>
        public AutoRemoveCacheAttribute(string cacheKey = "", string cacheArea = "")
        {
            CacheKey = cacheKey;
            CacheArea = cacheArea;
        }

        /// <summary>
        /// remove cache
        /// </summary>
        /// <param name="context"></param>
        public void Advise(MethodAdviceContext context)
        {
            context.Proceed();
            if (!CacheKey.IsNullOrEmpty()) CacheKey.RemoveCache();
            if (!CacheArea.IsNullOrEmpty()) CacheArea.RemoveAreaCache();
            CacheKey.RemoveRemoteCache(CacheArea);
        }
}

How to refactor asp.net cache code?

The above two classes that inherit from the Attribute class and implement the IMethodAdvice interface have implemented elegant encapsulation and caching-related operations for us. When we need to call the cache or refresh, we only need to mark the corresponding characteristics. This can be done Keep the other methods single-responsible and just do what they need to do.

Cache & AOP

The implementation of the encapsulation cache in this paper uses the idea of aspect-oriented programming, avoids a lot of repetitive code, and enhances the readability of the code. So what is aspect-oriented programming?

Aspect-oriented programming (Aspect-oriented programming, AOP, also translated as aspect-oriented programming, section-oriented programming), is a programming idea in computer science, which aims to further integrate cross-cutting concerns with business entities. Separation to improve the modularity of the program code.

The .net platform has the famous Postsharp (commercial component), but we choose the open source and free MrAdvice class library; java has spring, which can also implement AOP.

How to refactor asp.net cache code?

Conclusion

We use AOP to encapsulate cache operations. In fact, common non-business core functions such as logs, permissions, and monitoring can all be encapsulated using this idea to reduce a lot of repetitive code. How to refactor asp.net cache code?

When we are working hard and are entangled in all kinds of complicated business, we occasionally look up at the starry sky and think for a while, and a good idea comes out; it is very interesting to develop a component and a function. The code realizes the most stable function, why bother with Ctrl+C and Ctrl+V every day?

Leave a Comment