Post

C# | Optimizing Your Web API Performance - Caching Techniques

When building Web APIs in C#, performance is a critical consideration. One effective way to enhance performance is by implementing caching techniques. Caching reduces the load on your servers, decreases response times, and improves the overall user experience. In this post, we will explore various caching strategies you can implement in your C# Web API along with practical examples.

What is Caching?

Caching involves storing copies of files or data in a temporary storage location for quick access. When a client requests data, the API can serve it from the cache instead of querying the database or performing other costly operations, thus speeding up the response time.

Caching Techniques in C#

  1. In-Memory Caching
  2. Distributed Caching
  3. Output Caching
  4. HTTP Caching

Let’s dive into each of these techniques with examples.

1. In-Memory Caching

In-memory caching is a simple and effective way to store data in the server’s memory. ASP.NET Core provides a built-in in-memory caching feature.

Example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddControllers();
        services.AddMemoryCache(); // Add in-memory caching services
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        app.UseRouting();
        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllers();
        });
    }
}

[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
    private readonly IMemoryCache _cache;
    private static readonly string CacheKey = "WeatherData";

    public WeatherForecastController(IMemoryCache memoryCache)
    {
        _cache = memoryCache;
    }

    [HttpGet]
    public ActionResult<IEnumerable<WeatherForecast>> Get()
    {
        // Try to get the cached data
        if (!_cache.TryGetValue(CacheKey, out IEnumerable<WeatherForecast> weatherData))
        {
            // Data not in cache, generate new data
            weatherData = GenerateWeatherData();

            // Set cache options
            var cacheOptions = new MemoryCacheEntryOptions
            {
                AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(5) // Cache for 5 minutes
            };

            // Save data in cache
            _cache.Set(CacheKey, weatherData, cacheOptions);
        }

        return Ok(weatherData);
    }

    private IEnumerable<WeatherForecast> GenerateWeatherData()
    {
        // Simulate data generation
        return new List<WeatherForecast>
        {
            new WeatherForecast { Date = DateTime.Now, TemperatureC = 20 },
            new WeatherForecast { Date = DateTime.Now.AddDays(1), TemperatureC = 22 }
        };
    }
}

2. Distributed Caching

For applications that run on multiple servers or need to share cached data between instances, distributed caching is ideal. You can use caching providers like Redis or SQL Server.

Example (Using Redis)

First, install the Microsoft.Extensions.Caching.StackExchangeRedis package:

1
dotnet add package Microsoft.Extensions.Caching.StackExchangeRedis

Configure Redis in Startup.cs:

1
2
3
4
5
6
7
public void ConfigureServices(IServiceCollection services)
{
    services.AddStackExchangeRedisCache(options =>
    {
        options.Configuration = "localhost:6379"; // Redis server configuration
    });
}

Use the Redis cache in your controller:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
[ApiController]
[Route("[controller]")]
public class ProductsController : ControllerBase
{
    private readonly IDistributedCache _distributedCache;

    public ProductsController(IDistributedCache distributedCache)
    {
        _distributedCache = distributedCache;
    }

    [HttpGet("{id}")]
    public async Task<ActionResult<Product>> GetProduct(int id)
    {
        var cacheKey = $"Product-{id}";
        var cachedProduct = await _distributedCache.GetStringAsync(cacheKey);

        if (cachedProduct != null)
        {
            return Ok(JsonConvert.DeserializeObject<Product>(cachedProduct));
        }

        // Simulate fetching product from the database
        var product = FetchProductFromDatabase(id);
        
        // Cache the product data
        await _distributedCache.SetStringAsync(cacheKey, JsonConvert.SerializeObject(product), new DistributedCacheEntryOptions
        {
            AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(10)
        });

        return Ok(product);
    }

    private Product FetchProductFromDatabase(int id)
    {
        // Simulate database access
        return new Product { Id = id, Name = "Sample Product" };
    }
}

3. Output Caching

Output caching stores the result of a request and serves it for subsequent requests, which is especially useful for API endpoints with no changing data.

Example

To enable output caching, you need to install the following package:

1
dotnet add package Microsoft.AspNetCore.OutputCaching

Add output caching in Startup.cs:

1
2
3
4
5
6
7
8
public void ConfigureServices(IServiceCollection services)
{
    services.AddOutputCache(options =>
    {
        options.AddBasePolicy(builder => builder
            .Expire(TimeSpan.FromMinutes(1))); // Default cache duration
    });
}

Use output caching in your controller:

1
2
3
4
5
6
7
8
9
10
11
12
[ApiController]
[Route("[controller]")]
public class MoviesController : ControllerBase
{
    [HttpGet]
    [OutputCache(Duration = 60)] // Cache for 60 seconds
    public ActionResult<IEnumerable<Movie>> Get()
    {
        var movies = FetchMoviesFromDatabase(); // Simulate database access
        return Ok(movies);
    }
}

4. HTTP Caching

HTTP caching can be configured using cache control headers to instruct clients and intermediary proxies about caching policies.

Example

Set cache headers in your controller:

1
2
3
4
5
6
7
8
9
10
11
12
13
[ApiController]
[Route("[controller]")]
public class ArticlesController : ControllerBase
{
    [HttpGet]
    public ActionResult<IEnumerable<Article>> Get()
    {
        var articles = FetchArticlesFromDatabase();
        
        Response.Headers.Add("Cache-Control", "public,max-age=300"); // Cache for 5 minutes
        return Ok(articles);
    }
}

What Next?

Caching is a powerful technique for optimizing your Web API’s performance. By implementing in-memory caching, distributed caching, output caching, and HTTP caching, you can significantly reduce response times and server load. Choose the caching strategy that best fits your application needs and start enjoying the benefits of enhanced performance!

References

This post is licensed under CC BY 4.0 by the author.