Filtered by Tag (net-core)
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.

.NET Core Runtime RC2 has been released a few days ago along with .NET Core SDK Preview 1. At the same time of .NET Core release, ASP.NET Core RC2 has also been released. While I am migrating my projects to RC2, I wanted to write about how I am getting each stage done. In this post, I will show you the tooling aspect of the changes.
@ 05-22-2016
by Tugberk Ugurlu


.NET Core Runtime RC2 has been released a few days ago along with .NET Core SDK Preview 1. At the same time of .NET Core release, ASP.NET Core RC2 has also been released. Today, I started doing the transition from RC1 to RC2 and I wanted to write about how I am getting each stage done. Hopefully, it will be somewhat useful to you as well. In this post, I want to talk about the tooling aspect of the transition.

Get the dotnet CLI Ready

One of the biggest shift from RC1 and RC2 is the tooling. Before, we had DNVM, DNX and DNU as command line tools. All of them are now gone (RIP). Instead, we have one command line tool: dotnet CLI. First, I installed dotnet CLI on my Ubuntu 14.04 VM by running the following script as explained here:

sudo sh -c 'echo "deb [arch=amd64] https://apt-mo.trafficmanager.net/repos/dotnet/ trusty main" > /etc/apt/sources.list.d/dotnetdev.list'
sudo apt-key adv --keyserver apt-mo.trafficmanager.net --recv-keys 417A0893
sudo apt-get update
sudo apt-get install dotnet-dev-1.0.0-preview1-002702

This installed dotnet-dev-1.0.0-preview1-002702 package on my machine and I am off to go:

image

You can also use apt-cache to see all available versions:

image

Just to make things clear, I deleted ~/.dnx folder entirely to get rid of all RC1 garbage.

Get Visual Studio Code Ready

At this stage, I had the C# extension installed on my VS Code instance on my Ubuntu VM which was only working for DNX based projects. So, I opened up VS Code and updated my C# extension to latest version (which is 1.0.11). After the upgrade, I opened up a project which was on RC1 and VS Code started downloading .NET Core Debugger.

image

image

That was a good experience, I didn't dig into how to do that but I am not sure at this point why it didn't come inside the extension itself. There is probably a reason for that but not my priority to dig into right now :)

Try out the Setup

Now, I am ready to blast off with .NET Core. I used dotnet CLI to create a sample project by typing "dotnet new --type=console" and opened up project folder with VS Code. As soon as VS Code is launched, it asked me to set the stage.

image

Which got me a few new files under .vscode folder.

image

I jumped into the debug pane, selected the correct option and hit the play button after putting a breakpoint inside the Program.cs file.

image

Boom! I am in business.

image

Now, I am moving to code changes which will involve more hair pulling I suppose.

Resources

Two weeks ago, I had an amazing opportunity to be at Microsoft Build Conference in San Francisco and I would like to share my experience about the conference with you in this post by highlighting what has happened and giving you my personal takeaways.
@ 04-09-2016
by Tugberk Ugurlu


Two weeks ago, I had an amazing opportunity to be at Microsoft Build Conference in San Francisco as an attendee thanks to my amazing company Redgate. The experience was truly unique and amount of people I have met there was huge. A bit late but I would like to share my experience about the conference with you in this post by highlighting what has happened and giving you my personal takeaways. You can also check out my tweets for the Build conference.

CeznGcRWAAE_QMw

Announcements

There were bunch of big and small announcements throughout the conference from Microsoft. Some of these were highlighted during two keynotes and some other announcements were spread to three days. I tried to capture all of them here but it's very likely I missed some of them (mostly the small ones):

Ce46IwXUsAA0Wil

Sessions

2016-03-31 15.28.14

Here is the list of sessions I have attended:

As much as I wanted to attend some other sessions, I missed some of them mostly due to clashes with other sessions. Luckily recordings for all Build 2016 sessions are available up on Channel 9. Here is my list of sessions to catch up:

There were also many good Channel 9 Live interviews. You can find them here. Here is a personal list of a few which are worth listening to:

IMG_1870

Personal Takeaways

All in all it has been a great conference and as stated, I am still catching up on the stuff that I have missed. Throughout the conference, I have picked up a few key points and I want to end the post with those:

  • I have seen more from Microsoft to make developers lives easier and more productive by enabling new tools (Bash on Ubuntu on Windows), supporting multiple platforms (Service Fabric to run on every environment including AWS, on-premises, Azure Stack and preview of Service Fabric on Linux), open sourcing more (some parts of Xamarin have gone open source) and making existing paid tools available for free (Xamarin is now free).
  • Microsoft is more focused on getting their existing services together and trying to give a cohesive ecosystem for developers. Service Fabric, Cognitive Services, Data Lake is a few examples of this.
  • .NET Core and CoreCLR is approaching to finalization for v1. After RC2, I don't suppose there will be much more features added or concepts changing.
  • I think this is the first time I have seen stabilization on client Apps story for Microsoft. Universal Windows Platform (UWP) was the focus on this area this year and it was the same on previous year.
  • I am absolutely happy to see Microsoft abandoning Windows Phone day by day. There was no direct sessions on it during the conference.
  • There were more steps towards making software to manage people's lives in a better way. Skype Bot Framework was one of these steps.
  • Microsoft (mostly Azure group) invests on IoT solutions heavily. Azure Functions and new updates on Azure IoT Suite are just a few signs of this.
  • Azure Resource Manager (ARM) and ARM templates are getting a lot of love from Microsoft and it's the way they push forward. They even build new services on Azure on top of this.


Hi 🙋🏻‍♂️ I'm Tugberk Ugurlu.
Coder 👨🏻‍💻, Speaker 🗣, Author 📚, Microsoft MVP 🕸, Blogger 💻, Software Engineering at Deliveroo 🍕🍜🌯, F1 fan 🏎🚀, Loves travelling 🛫🛬
Lives in Cambridge, UK 🏡