Filtered by Tag (caching)
PostSharp is a .NET library which gives you ability to program in a declarative style and allows you perform many cross-cutting concerns with a minimum amount of code by abstracting away the complexity from you. In this post, I will be looking into how PostSharp helps us for caching to speed up the performance of our applications drastically.
@ 05-04-2019
by Tugberk Ugurlu


One of the first criteria of effective code is that it does its job with as few lines of code as possible. Effective code does not repeat itself. Less code in our codebases increases our chances of having less bugs. So, how do we avoid repeating ourselves? We apply our intelligence and abstraction skills to generalize behaviors into methods and classes, the constructs offered by C# to implement abstraction which we call encapsulation. However, some features such as logging or caching cannot be properly encapsulated into a class or method. That’s why you end up having code repetition. C# alone is simply not able to properly encapsulate features like logging, caching, security, INotifyPropertyChanged, undo/redo, etc.

I have been meaning to look into Aspect-oriented programming for a while to help my code to be less noisy without sacrificing the application's acceptable performance and observability. This would help cut right to the business logic, allowing me to care about what's more important. When the topic is Aspect-oriented programming, first software comes to my mind is obviously PostSharp in .NET world and in this post, I will be looking at how PostSharp can help us cut the noise out of our code and showcase this with a sample on data caching.

Getting Started with PostSharp

First of all, let's create our project structure and install PostSharp. I have .NET Core SDK 2.2.202 installed and ran the below commands to create the empty project structure.

dotnet new web --no-https
dotnet new sln
dotnet sln 1-sample-web.sln add 1-sample-web.csproj
dotnet new globaljson

In order to give you an idea about the value proposition of PostSharp, I created this little ASP.NET Core sample which exposes HTTP APIs to read, write and modify the Cars in our system. Some of the code here is contrived such as sleeping for half a second, etc. but we will see why this will be useful for us to see the PostSharp in action.

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;

namespace _1_sample_web
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc();
        }

        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            app.UseMvcWithDefaultRoute();
        }
    }

    public class CarsController : Controller
    {
        private static readonly CarsContext _carsCtx = new CarsContext();

        [HttpGet("cars")]
        public IEnumerable Get()
        {
            return _carsCtx.GetAll();
        }

        [HttpGet("cars/{id}")]
        public IActionResult GetCar(int id) 
        {
            var carTuple = _carsCtx.GetSingle(id);
            if (!carTuple.Item1) 
            {
                return NotFound();
            }

            return Ok(carTuple.Item2);
        }

        [HttpPost("cars/{id}")]
        public IActionResult PostCar(Car car) 
        {
            var createdCar = _carsCtx.Add(car);
            return CreatedAtAction(nameof(GetCar), 
                new { id = createdCar.Id }, 
                createdCar);
        }

        [HttpPut("cars/{id}")]
        public IActionResult PutCar(int id, Car car) 
        {
            car.Id = id;
            if (!_carsCtx.TryUpdate(car)) 
            {
                return NotFound();
            }

            return Ok(car);
        }

        [HttpDelete("cars/{id}")]
        public IActionResult DeleteCar(int id) 
        {
            if (!_carsCtx.TryRemove(id)) 
            {
                return NotFound();
            }

            return NoContent();
        }
    }

    public class Car 
    {
        public int Id { get; set; }

        [Required]
        [StringLength(20)]
        public string Make { get; set; }

        [Required]
        [StringLength(20)]
        public string Model { get; set; }

        public int Year { get; set; }

        [Range(0, 500000)]
        public float Price { get; set; }
    }

    public class CarsContext
    {
        private int _nextId = 9;
        private object _idLock = new object();

        private readonly ConcurrentDictionary _database = new ConcurrentDictionary(new HashSet> 
        { 
            new KeyValuePair(1, new Car { Id = 1, Make = "Make1", Model = "Model1", Year = 2010, Price = 10732.2F }),
            new KeyValuePair(2, new Car { Id = 2, Make = "Make2", Model = "Model2", Year = 2008, Price = 27233.1F }),
            new KeyValuePair(3, new Car { Id = 3, Make = "Make3", Model = "Model1", Year = 2009, Price = 67437.0F }),
            new KeyValuePair(4, new Car { Id = 4, Make = "Make4", Model = "Model3", Year = 2007, Price = 78984.2F }),
            new KeyValuePair(5, new Car { Id = 5, Make = "Make5", Model = "Model1", Year = 1987, Price = 56200.89F }),
            new KeyValuePair(6, new Car { Id = 6, Make = "Make6", Model = "Model4", Year = 1997, Price = 46003.2F }),
            new KeyValuePair(7, new Car { Id = 7, Make = "Make7", Model = "Model5", Year = 2001, Price = 78355.92F }),
            new KeyValuePair(8, new Car { Id = 8, Make = "Make8", Model = "Model1", Year = 2011, Price = 1823223.23F })
        });
        
        public IEnumerable GetAll()
        {
            Thread.Sleep(500);
            return _database.Values;
        }

        public IEnumerable Get(Func predicate) 
        {
            Thread.Sleep(500);
            return _database.Values.Where(predicate);
        }

        public Tuple GetSingle(int id) 
        {
            Thread.Sleep(500);

            Car car;
            var doesExist = _database.TryGetValue(id, out car);
            return new Tuple(doesExist, car);
        }

        public Car GetSingle(Func predicate) 
        {
            Thread.Sleep(500);
            return _database.Values.FirstOrDefault(predicate);
        }

        public Car Add(Car car) 
        {
            Thread.Sleep(500);
            lock(_idLock) 
            {
                car.Id = _nextId;
                _database.TryAdd(car.Id, car);
                _nextId++;
            }

            return car;
        }

        public bool TryRemove(int id) 
        {
            Thread.Sleep(500);

            Car removedCar;
            return _database.TryRemove(id, out removedCar);
        }

        public bool TryUpdate(Car car) 
        {
            Thread.Sleep(500);

            Car oldCar;
            if (_database.TryGetValue(car.Id, out oldCar)) {

                return _database.TryUpdate(car.Id, car, oldCar);
            }

            return false;
        }
    }
}

Before going further, let's install PostSharp through NuGet. The first thing you want to install is PostSharp NuGet package which magically hooks into the compilation step thanks to its custom MSBuild scripts. The other package here will be PostSharp.Patterns.Diagnostics as I want to show you a logging example first.

dotnet add package PostSharp
dotnet add package PostSharp.Patterns.Diagnostics

Let's get the sample code from the logging documentation.

using PostSharp.Patterns.Diagnostics;
using PostSharp.Extensibility;

[assembly: Log(AttributePriority = 1, AttributeTargetMemberAttributes = MulticastAttributes.Protected | MulticastAttributes.Internal | MulticastAttributes.Public)]
[assembly: Log(AttributePriority = 2, AttributeExclude = true, AttributeTargetMembers = "get_*" )]

When you run the application now, you will be impressed and probably also be blown away by how much value and observability you get with a very little work!



PostSharp Caching Example

The main reason for me to explore PostSharp is for caching and this is where PostSharp Caching shines really. Let's run our sample application again and perform a mini load test on it.

1..10 | foreach {write-host "$([Math]::Round((Measure-Command -Expression { Invoke-WebRequest -Uri http://localhost:5000/cars }).TotalMilliseconds, 1))"}

You will notice that each call to the "/cars" endpoint takes more than 500ms, which is fair due to us sleeping that amount of time on purpose. However, this could well be the case when you connect to a data store in a real world example. Even if your data store is performant and gets the result instantly, we are still wasting resources here because the data hasn't changed and we would be doing an unnecessary trip to the database to get the data which we have already retrieved previously.

Caching is the solution to this problem. However, it's not really easy to get right on your own in a web application which is multithreaded in its nature. You can use built-in APIs such as the ones come from ASP.NET Core but you then need to express your caching requirements in code, in a verbose way which will make it hard to understand the business logic behind a cluttered codebase and suddenly, you will be struggling to add or modify functionality in an existing software.

Let's see how PostSharp can help us here. First, we need to add the caching support by installing PostSharp.Patterns.Caching NuGet package.

dotnet add package PostSharp.Patterns.Caching

Then, we need to make some changes to our code to enable caching. Here is the git patch which shows you what exactly I have changed:

From a20fc8e95ffd9bf5d424467e0e1283ae5891454a Mon Sep 17 00:00:00 2001
From: Tugberk Ugurlu
Date: Tue, 9 Apr 2019 23:38:32 +0100
Subject: [PATCH] add caching

---
 postsharp/0-caching/1-sample-web/1-sample-web.csproj | 1 +
 postsharp/0-caching/1-sample-web/Program.cs          | 3 +++
 postsharp/0-caching/1-sample-web/Startup.cs          | 4 +++-
 3 files changed, 7 insertions(+), 1 deletion(-)

diff --git a/postsharp/0-caching/1-sample-web/1-sample-web.csproj b/postsharp/0-caching/1-sample-web/1-sample-web.csproj
index bd55b6c..008c486 100644
--- a/postsharp/0-caching/1-sample-web/1-sample-web.csproj
+++ b/postsharp/0-caching/1-sample-web/1-sample-web.csproj
@@ -10,6 +10,7 @@
     
     
     
+    
     
   
 
diff --git a/postsharp/0-caching/1-sample-web/Program.cs b/postsharp/0-caching/1-sample-web/Program.cs
index 3dcae2c..9d241eb 100644
--- a/postsharp/0-caching/1-sample-web/Program.cs
+++ b/postsharp/0-caching/1-sample-web/Program.cs
@@ -7,6 +7,8 @@ using Microsoft.AspNetCore;
 using Microsoft.AspNetCore.Hosting;
 using Microsoft.Extensions.Configuration;
 using Microsoft.Extensions.Logging;
+using PostSharp.Patterns.Caching;
+using PostSharp.Patterns.Caching.Backends;
 using PostSharp.Patterns.Diagnostics;
 using PostSharp.Patterns.Diagnostics.Backends.Console;
 
@@ -18,6 +20,7 @@ namespace _1_sample_web
         public static void Main(string[] args)
         {
             LoggingServices.DefaultBackend = new ConsoleLoggingBackend();
+            CachingServices.DefaultBackend = new MemoryCachingBackend();
             CreateWebHostBuilder(args).Build().Run();
         }
 
diff --git a/postsharp/0-caching/1-sample-web/Startup.cs b/postsharp/0-caching/1-sample-web/Startup.cs
index 18b3dbc..bed37ca 100644
--- a/postsharp/0-caching/1-sample-web/Startup.cs
+++ b/postsharp/0-caching/1-sample-web/Startup.cs
@@ -10,6 +10,7 @@ using Microsoft.AspNetCore.Hosting;
 using Microsoft.AspNetCore.Http;
 using Microsoft.AspNetCore.Mvc;
 using Microsoft.Extensions.DependencyInjection;
+using PostSharp.Patterns.Caching;
 
 namespace _1_sample_web
 {
@@ -115,7 +116,8 @@ namespace _1_sample_web
             new KeyValuePair(7, new Car { Id = 7, Make = "Make7", Model = "Model5", Year = 2001, Price = 78355.92F }),
             new KeyValuePair(8, new Car { Id = 8, Make = "Make8", Model = "Model1", Year = 2011, Price = 1823223.23F })
         });

+        [Cache]
         public IEnumerable GetAll()
         {
             Thread.Sleep(500);
-- 
2.15.2 (Apple Git-101.1)

Couple of things we have done here:

  • In our entry point, we configured the cache backend we wanted to use which in our case is the MemoryCache.
  • We marked the CarContext.GetAll method with the CacheAttribute.

Believe it or not, this is pretty much it! When we run the sample mini load test, you will see the dramatic difference even if we are seeing a higher response time on the first load.


Again, very little work but tremendous gain in terms of value!

We have improved our performance drastically but introduced a very nasty problem now: serving stale data. Thankfully, PostSharp has a solution to cache invalidation out of the box without losing our declarative nature for simple cases. For this, we need to use InvalidateCacheAttribute aspect. When this attribute is applied to a method, it causes any call to this method to remove from the cache the value of one or more other methods. It’s worth noting that the cached methods are matched, by type and name, against the parameters of the invalidating method. PostSharp compilation takes care of the rest during the build step to set up all the invalidation logic.

For example, the below changes makes it possible for us to invalidate the cache of a single car entity for example when it’s updated.

From f0889e68e55298e43360e01dd3b0e8b1cf6468e3 Mon Sep 17 00:00:00 2001
From: Tugberk Ugurlu
Date: Tue, 30 Apr 2019 09:40:21 +0100
Subject: [PATCH] cache invalidation, declarative

---
 postsharp/0-caching/1-sample-web/Startup.cs | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/postsharp/0-caching/1-sample-web/Startup.cs b/postsharp/0-caching/1-sample-web/Startup.cs
index bed37ca..ec95d1e 100644
--- a/postsharp/0-caching/1-sample-web/Startup.cs
+++ b/postsharp/0-caching/1-sample-web/Startup.cs
@@ -62,7 +62,7 @@ namespace _1_sample_web
         public IActionResult PutCar(int id, Car car) 
         {
             car.Id = id;
-            if (!_carsCtx.TryUpdate(car)) 
+            if (!_carsCtx.TryUpdate(id, car)) 
             {
                 return NotFound();
             }
@@ -130,6 +130,7 @@ namespace _1_sample_web
             return _database.Values.Where(predicate);
         }
 
+        [Cache]
         public Tuple GetSingle(int id) 
         {
             Thread.Sleep(500);
@@ -166,7 +167,8 @@ namespace _1_sample_web
             return _database.TryRemove(id, out removedCar);
         }
 
-        public bool TryUpdate(Car car) 
+        [InvalidateCache(nameof(GetSingle))]
+        public bool TryUpdate(int id, Car car) 
         {
             Thread.Sleep(500);
 
-- 
2.20.1 (Apple Git-117)

However, this only invalidates the GetSingle method and we still have problem of serving stale data from GetAll method. There is also an ability out of the box to to imperatively invalidate an item from the cache which is very handy for cases where we cannot simply invalidate the cache purely based on method signature. You can see below an example of how this looks like. 

From f629b295fc8f9bbd44904284cb0ec832d51185be Mon Sep 17 00:00:00 2001
From: Tugberk Ugurlu
Date: Tue, 30 Apr 2019 09:55:44 +0100
Subject: [PATCH] cache invalidation, imperatively

---
 postsharp/0-caching/1-sample-web/Startup.cs | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/postsharp/0-caching/1-sample-web/Startup.cs b/postsharp/0-caching/1-sample-web/Startup.cs
index ec95d1e..8ee6652 100644
--- a/postsharp/0-caching/1-sample-web/Startup.cs
+++ b/postsharp/0-caching/1-sample-web/Startup.cs
@@ -67,6 +67,10 @@ namespace _1_sample_web
                 return NotFound();
             }
 
+            CachingServices.Invalidation.Invalidate(
+                typeof(CarsContext).GetMethod(nameof(CarsContext.GetAll)), 
+                _carsCtx);
+                
             return Ok(car);
         }
 
-- 
2.20.1 (Apple Git-117)

We Invalidate the GetAll method cache on the given CarsContext instance when we have an update on any of the items.

This is all I want to cover on this post in terms of the API surface area of PostSharp and I hope this gives you taste of how simple it’s to get going with PostSharp. PostSharp Caching documentation is also very comprehensive and I recommend you to check that out for further details.

Limitations

The biggest limitation I have seen with PostSharp is its lack of .NET Core compilation support outside of Windows at the time of writing (you may check the current status here). You can run PostSharp on .NET Core, even outside of Windows. However, you first need a Windows machine to be able to compile your code.

Apart from this, there is also a trade off for you to make with PostSharp which is the increased build time. However, with incremental builds, this additional increase can become noticeable. Besides this, compared to the value you got from the tool, I think this is trade-off which is well worth to be made.

Conclusion

This post just touches the surface on what you can achieve with PostSharp. In terms of caching for example, there is even a support for Redis which is very suitable for horizontally scaled web applications where multiple nodes serve HTTP requests.

PostSharp provides help on many other various patterns such as mutithreading. You can get started with PostSharp with PostSharp Essentials, the free but project-size-limited edition.

This blog post demonstrates how to implement Donut Hole Caching in ASP.NET MVC by Using Child Actions and OutputCacheAttribute
@ 10-30-2011
by Tugberk Ugurlu


homer-and-donut

One of the common issues of web applications is performance and serving the same content over and over again for hours, days, even months is certainly effecting the performance of our web applications. This is where server side caching comes in handy. But sometimes the whole page we are scripting on the server side is going to be static for hours and sometimes some portion of it.

The first one, caching the whole page, is sort of easy with almost any web development frameworks. The second one, caching a portion of your web page, is the tricky one.

Phil Haack blogged about Donut Hole Caching in ASP.NET MVC a while back but it is a little outdated. In this quick blog post, I will try to show you the easiest way of implementing this feature with ASP.NET MVC.

Use Cases

So, where might we need this Donut Hole Caching thing? For example, if you are listing categories on the side bar, you probably gathering those categories from a database. Add the fact that you render this part on every single page of your web application, it is a waste of time if your category list is not so dynamic. So, you may want to cache the category list part of the page so that you don’t go to your database every single time.

How to Do

In ASP.NET MVC framework, we can cache the whole controller action with OutputCacheAttribute which represents an attribute that is used to mark an action method whose output will be cached according to MSDN. This is perfect but wait a second. We do not want to cache the entire action, we just want to cache the portion of it. This is where ChildActionExtensions.Action method, which invokes a child action method and returns the result as an HTML string, plays a huge role. Let me show you how these parts of the framework play nice together.

Have a look at the below controller action :

[ChildActionOnly]
[OutputCache(Duration=60)]
public ActionResult sampleChildAction() {

    //Put the thread at sleep for 3 seconds to see the difference.
    System.Threading.Thread.Sleep(3000);

    //Also pass the date time from here just to see that it is comming from here.
    ViewBag.DateTime = DateTime.Now.ToString("dd.MM.yyyy HH:mm.ss");

    return View();
}

A simple controller action method which returns ActionResult, nothing fancy going on except for ChildActionOnlyAttribute which represents an attribute that is used to indicate that an action method should be called only as a child action. Let’s look at the sampleChildAction view and I will try to explain ChildActionOnlyAttribute function after that.

@{
    Layout = null;
}

<p>
    This portion of the web page was scripted on <strong>@ViewBag.DateTime</strong> and I will be cached for <strong>60</strong> seconds.
</p>

This html will be a part of our web page which will be cached. It doesn’t mean anything by itself but we have created an action for this view which means that we can call this page directly from a browser. ChildActionOnlyAttribute exactly prevent users to call this kind of actions. You do not need to implement this attribute there but it is nice to know that it is there for us.

The controller action which will render the whole page is so simple as below and doesn’t require any special thing for us to implement in order caching to work.

public ActionResult Index() {

    return View();
}

Let’s also look at the view implementation :

@{
    ViewBag.Title = "Donut Hole Caching Sample";
}

<h2>Donut Hole Caching Sample</h2>

<h3>Cached</h3>

<div>
    @Html.Action("sampleChildAction", 
      new { controller = "Sample", Area = "DonutHoleCaching" }
    )
</div>

<h3>Normal</h3>

<div>
    <p>
        This portion of the web page was scripted on <strong>@DateTime.Now.ToString("dd.MM.yyy HH:mm.ss")</strong>
    </p>
</div>

What we are doing here is that rather than putting the part, which we would like to cache, directly here, we are calling it as child action. So, the framework will treat the child action as it does for normal action methods.

When we first call it, we will see something like below :

image

While I was calling this page, I was on hold for 3 seconds because we have put the thread at sleep for 3 seconds on our child action method to feel the difference as you can see on above code in order.

When I make the second call, I got something like this and I wasn’t on hold for 3 seconds :

image

Did you notice the time difference? This proves that if the cache is valid, our child action method won’t be rendered again. It will serve from the cache. Awesome, ha?

I decided to create an ASP.NET MVC project called MvcMiracleWorker for this kind of small samples. You can find the complete source code from GitHub : https://github.com/tugberkugurlu/MvcMiracleWorker

Behave well, use ASP.NET MVC Winking smile



Hi πŸ™‹πŸ»β€β™‚οΈ I'm Tugberk Ugurlu.
Coder πŸ‘¨πŸ»β€πŸ’», Speaker πŸ—£, Author πŸ“š, Microsoft MVP πŸ•Έ, Blogger πŸ’», Software Engineering at Deliveroo πŸ•πŸœπŸŒ―, F1 fan πŸŽπŸš€, Loves travelling πŸ›«πŸ›¬
Lives in Cambridge, UK 🏑