In this post, we will discuss output caching using Azure Redis Cache with Azure SQL database in Blazor projects. We will create an Indian Post Office application using which we can get the state-wise post office details.
In computing, cache is a hardware or software component that stores the data so that the future requests for that data can be served faster; the data stored in a cache might be the result of an earlier computation or a copy of the data stored elsewhere. Cache hits are served by reading data from the cache, which is faster than recomputing a result or reading from a slower data store. Thus, more requests can be served from the cache and the system performs fast. A cache hit occurs when the requested data can be found in a cache, while a cache miss occurs when it cannot be found.
Azure Redis Cache
Azure Redis Cache is based on the popular open source Redis Cache. Users get the best of both worlds, the rich Redis feature set and ecosystem, and reliable hosting and monitoring from Microsoft. It gives users access to a secure, dedicated Redis Cache, managed by Microsoft.
Microsoft Azure Redis Cache is available in the following tiers,
- Basic – Single node, multiple sizes, ideal for development/test and non-critical workloads. The basic tier has no SLA.
- Standard – A replicated cache in a two-node Primary/Secondary configuration managed by Microsoft, with a high availability SLA.
- Premium - The new Premium tier includes all the Standard-tier features and more, such as better performance compared to Basic or Standard-tier Caches, bigger workloads, data persistence, and enhanced network security.
Blazor is still an experimental .NET web framework from Microsoft using C#/Razor and HTML that runs in the browser with Web Assembly. Blazor provides all the benefits of a client-side web UI framework using .NET on the client and optionally on the server.
I have already written many articles on Blazor in C# Corner. If you are new to Blazor, please refer to below articles to get started with Blazor.
Search for “Redis Cache” and choose it.
Click “Create” button to start the next steps. You can give a valid name to Redis Cache. Please choose an appropriate pricing plan.
As I mentioned earlier in this post, there are 3 types of pricing tiers available in Azure. I have chosen “Basic C0” plan. This is the lowest plan and we can use it for our testing purposes. We can choose a better plan for the production stage. You can see the pricing details of various plans by clicking the “View full pricing details” link. In “Basic C0” plan I will get only 250 MB cache. This is enough for our testing purposes.
Please copy the connection string from the “Access Keys” section and keep in a secure place. We will use this key later in our Blazor project.
Create Blazor project in Visual Studio 2017
We can create a new Blazor project using Visual Studio 2017 (I am using free community edition). Currently, there are three types of templates available for Blazor. We can choose Blazor (ASP.NET Core hosted) template.
Our solution will be ready shortly. Please note that there are three projects created in our solution - “Client”, “Server”, and “Shared”.
By default, Blazor created many files in these three projects. We can remove all the unwanted files like “Counter.cshtml”, “FetchData.cshtml”, “SurveyPrompt.cshtml” from Client project and “SampleDataController.cs” file from Server project and remove “WeatherForecast.cs” file from shared project too.
Create a “Models” folder in the “Shared” project. We will create three classes inside this folder.
Before that, we must install “Newtonsoft.Json” NuGet package in the shared project. We will use “JsonProperty” attribute to change the property names in some classes. This will be used for JSON serialization.
We can create a class file “Classes” and create three classes inside this file.
Classes.cs
- using Newtonsoft.Json;
- using System.Collections.Generic;
-
- namespace BlazorRedisCache.Shared.Models
- {
- public class IndiaPO
- {
- [JsonProperty(PropertyName = "id")]
- public long Id { get; set; }
- [JsonProperty(PropertyName = "officeName")]
- public string OfficeName { get; set; }
- [JsonProperty(PropertyName = "pinCode")]
- public string PinCode { get; set; }
- [JsonProperty(PropertyName = "taluk")]
- public string Taluk { get; set; }
- [JsonProperty(PropertyName = "districtName")]
- public string DistrictName { get; set; }
- [JsonProperty(PropertyName = "stateName")]
- public string StateName { get; set; }
- [JsonProperty(PropertyName = "telephone")]
- public string Telephone { get; set; }
- }
-
- public class PODetails
- {
- public double TimeTaken { get; set; }
- public IEnumerable<IndiaPO> IndiaPOs { get; set; }
- public long RecordCount { get; set; }
- }
-
- public class State
- {
- [JsonProperty(PropertyName = "id")]
- public int Id { get; set; }
- [JsonProperty(PropertyName = "stateName")]
- public string StateName { get; set; }
- }
- }
We have created “IndiaPO”, “PODetails” and “State” classes inside this file.
The IndiaPO class will be used for getting post office details and PODetails class will hold the post office details along with the time taken (in seconds) to fetch the data information and total post office count. State class will be used to get the state names from SQL database.
We can add “appsettings.json” file in “Server” project. We will keep Azure SQL and Redis Cache connection strings in this file.
appsettings.json
- {
- "MyConfigurations": {
- "SqlConnection": "Server=tcp:sarathsqlserver.database.windows.net,1433;Initial Catalog=sarathsqldb;Persist Security Info=False;
- User ID=sarathlal;Password={Your Password};MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;",
- "RedisKey": "sarathlal.redis.cache.windows.net:6380,password={Your Redis Key Password},ssl=True,abortConnect=False"
- }
- }
We must modify the “Starup.cs” class to read Configuration values from appsettings.json file.
- using BlazorRedisCache.Server.DataAccess;
- using Microsoft.AspNetCore.Blazor.Server;
- using Microsoft.AspNetCore.Builder;
- using Microsoft.AspNetCore.Hosting;
- using Microsoft.AspNetCore.ResponseCompression;
- using Microsoft.EntityFrameworkCore;
- using Microsoft.Extensions.Configuration;
- using Microsoft.Extensions.DependencyInjection;
- using Newtonsoft.Json.Serialization;
- using System.Linq;
- using System.Net.Mime;
-
- namespace BlazorRedisCache.Server
- {
- public class Startup
- {
- public Startup(IHostingEnvironment env)
- {
- var builder = new ConfigurationBuilder()
- .SetBasePath(env.ContentRootPath)
- .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true);
-
- Configuration = builder.Build();
- }
-
- public IConfigurationRoot Configuration { get; set; }
-
- public void ConfigureServices(IServiceCollection services)
- {
- services.AddMvc();
-
- services.AddResponseCompression(options =>
- {
- options.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(new[]
- {
- MediaTypeNames.Application.Octet,
- WasmMediaTypeNames.Application.Wasm,
- });
- });
-
- var sqlConnectionString = Configuration.GetSection("MyConfigurations").GetSection("SqlConnection").Value;
-
- services.AddDbContext<MsSqlServerContext>(options =>
- options.UseSqlServer(sqlConnectionString)
- );
-
- services.AddScoped<IDataAccessProvider, DataAccessProvider>();
- }
-
- public void Configure(IApplicationBuilder app, IHostingEnvironment env)
- {
- app.UseResponseCompression();
-
- if (env.IsDevelopment())
- {
- app.UseDeveloperExceptionPage();
- }
-
- app.UseMvc(routes =>
- {
- routes.MapRoute(name: "default", template: "{controller}/{action}/{id?}");
- });
-
- app.UseBlazor<Client.Program>();
- }
- }
- }
Please note, we have injected “MsSqlServerContext” class inside this class. This class file will be used for database connection using entity framework. Also note that we have injected “DataAccessProvider” class with “IDataAccessProvider” interface. These files will be used to perform CRUD actions in Web API controller. In this post, we will not cover all CRUD actions. We will use only READ method.
We can create a “DataAccess” folder and create above files inside this folder.
Create “MsSqlServerContext” class inside “DataAccess” folder.
MsSqlServerContext.cs
- using BlazorRedisCache.Shared.Models;
- using Microsoft.EntityFrameworkCore;
-
- namespace BlazorRedisCache.Server.DataAccess
- {
- public class MsSqlServerContext : DbContext
- {
- public MsSqlServerContext(DbContextOptions<MsSqlServerContext> options) : base(options)
- { }
-
- public DbSet<IndiaPO> IndiaPO { get; set; }
- public DbSet<State> POStates { get; set; }
- }
- }
We have added two DbSet properties inside this class. These properties will be used for getting data from SQL database.
Create “IDataAccessProvider” interface and add below properties.
IDataAccessProvider.cs
- using BlazorRedisCache.Shared.Models;
- using System.Collections.Generic;
-
- namespace BlazorRedisCache.Server.DataAccess
- {
- public interface IDataAccessProvider
- {
- PODetails GetIndiaPOs(string stateName);
- IEnumerable<State> GetStateNames();
- }
- }
We can create “DataAccessProvider” class and implement above interface.
We will implement the logic for Redis Cache in this class. We must install “Microsoft.Extensions.Caching.Redis.Core” NuGet package in “Server” project.
Add below code to “DataAccessProvider” class
- using BlazorRedisCache.Shared.Models;
- using Microsoft.Extensions.Configuration;
- using Newtonsoft.Json;
- using StackExchange.Redis;
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Threading.Tasks;
-
- namespace BlazorRedisCache.Server.DataAccess
- {
- public class DataAccessProvider : IDataAccessProvider
- {
- private readonly MsSqlServerContext _context;
- private IConfiguration _configuration;
-
- public DataAccessProvider(MsSqlServerContext context, IConfiguration Configuration)
- {
- _context = context;
- _configuration = Configuration;
- }
-
- public IEnumerable<State> GetStateNames()
- {
- return _context.POStates.OrderBy(o => o.StateName).ToList();
- }
-
- public PODetails GetIndiaPOs(string stateName)
- {
- var redisConnectionString = _configuration.GetSection("MyConfigurations").GetSection("RedisKey").Value;
- PODetails pODetails = new PODetails();
- var listPOs = new List<IndiaPO>();
-
- var redisConnect = ConnectionMultiplexer.Connect(redisConnectionString);
-
- IDatabase Rediscache = redisConnect.GetDatabase();
-
- var redisValue = Rediscache.StringGetAsync("stateDetails-" + stateName);
-
- if (string.IsNullOrEmpty(redisValue.Result))
- {
- listPOs = _context.IndiaPO.Where(w => w.StateName == stateName).ToList();
- Rediscache.StringSetAsync("stateDetails-" + stateName, JsonConvert.SerializeObject(listPOs), TimeSpan.FromMinutes(5));
- }
- else
- {
- listPOs = JsonConvert.DeserializeObject<List<IndiaPO>>(redisValue.Result);
- }
- pODetails.RecordCount = listPOs.Count();
- pODetails.IndiaPOs = listPOs.Take(100);
- return pODetails;
- }
- }
- }
We have defined two methods in this class. “GetStateNames” method will be used to get state names from the database and return as a list.
Logic of Caching mechanism
In “GetIndiaPOs” method, we have established a connection to Azure Redis Cache using “ConnectionMultiplexer.Connect” method and read the value from Cache. As we know, the first time there will be no value available in the cache. So, it will return a null value and automatically get the value from database and store in a list variable. We also set the value to Redis Cache along with a key. Key will be “stateDetails-” + statename. We will store separate cache values for each state user selects. We have also set an expiry time of 5 minutes for each cache. After 5 minutes, the cache will be automatically expired.
If the user chooses the same state name and executes the method within the expiry time, the cache value will be returned. This time, the system will not get the value from the database; instead, it will use the cache value.
Create “POsController” API controller and implement methods.
- using BlazorRedisCache.Server.DataAccess;
- using BlazorRedisCache.Shared.Models;
- using Microsoft.AspNetCore.Mvc;
- using System.Collections.Generic;
- using System.Diagnostics;
-
- namespace BlazorRedisCache.Server.Controllers
- {
- [Route("api/[controller]")]
- public class POsController : Controller
- {
- private readonly IDataAccessProvider _dataAccessProvider;
-
- public POsController(IDataAccessProvider dataAccessProvider)
- {
- _dataAccessProvider = dataAccessProvider;
- }
-
- [HttpGet]
- public IEnumerable<State> GetStates()
- {
- return _dataAccessProvider.GetStateNames();
- }
-
- [HttpGet("{stateName}")]
- public PODetails Get(string stateName)
- {
- Stopwatch clock = Stopwatch.StartNew();
- var data = _dataAccessProvider.GetIndiaPOs(stateName);
- clock.Stop();
-
- data.TimeTaken = clock.Elapsed.TotalSeconds;
- return data;
-
- }
-
- }
- }
We have implemented the methods for getting State names and Post office details. Please note, I have added a stopwatch to calculate the time between request start and end so that we can find the time difference between SQL server request and Cache.
We can now go to “Client” project and add a “GetPOs.cshtml” razor view file.
Please add the below code to this file.
GetPOs.cshtml
We have called the API method for getting state names in “OnInitAsync” event and we have added a new method to call API method for getting post office details.
Modify the “NavMenu.cshtml” file in “Shared” folder with below code. This file will be used to navigate the menu.
NavMenu.cshtml
- <div class="top-row pl-4 navbar navbar-dark">
- <a class="navbar-brand" href="">Azure Redis Cache in Blazor</a>
- <button class="navbar-toggler" onclick=@ToggleNavMenu>
- <span class="navbar-toggler-icon"></span>
- </button>
- </div>
-
- <div class=@(collapseNavMenu ? "collapse" : null) onclick=@ToggleNavMenu>
- <ul class="nav flex-column">
- <li class="nav-item px-3">
- <NavLink class="nav-link" href="" Match=NavLinkMatch.All>
- <span class="oi oi-home" aria-hidden="true"></span> Home
- </NavLink>
- </li>
- <li class="nav-item px-3">
- <NavLink class="nav-link" href="/getpos">
- <span class="oi oi-list-rich" aria-hidden="true"></span> Get Pincode Details
- </NavLink>
- </li>
- </ul>
- </div>
-
- @functions {
- bool collapseNavMenu = true;
-
- void ToggleNavMenu()
- {
- collapseNavMenu = !collapseNavMenu;
- }
- }
Modify the “Index.cshtml” file in “Pages” folder too.
Index.cshtml
- @page "/"
-
- <h3>Azure Redis Cache with Azure SQL and Blazor</h3>
- <hr />
- <p>
- We will see the caching with Azure Redis Cache and Azure SQL database in Blazor project
- </p>
We have completed all the coding part. We can run the application now.
Click the “Get Pincode Details” link. It will display all the state names. You can select any state and click “Get PO Details”. I have chosen “KERALA” as the state.
After some time, we will get the PO details of Kerala state. Please note, this is from the database. It took 8.5 seconds.
We can click the “Get PO Details” button again. Please note, this is from the cache. It took only 4.7 seconds. We can see the time difference.
We can change the state to “DELHI”. Again, data will be fetched from SQL database because we choose Delhi the first time.
We can click “Get PO Details” again within 5 minutes, we will get data from the cache.
Conclusion
Please note, we are using the lowest Redis Cache plan only. If we upgrade to a higher plan, we will get more results.
In this post, we have seen how to create Azure Redis Cache and what caching is. We have created a Blazor app to fetch Indian post office data using Redis Cache. We have stored the post office data in Azure SQL, and we have kept the cache in 5 minutes. We have seen the time difference between cached and non-cached data. For testing purposes, I have limited the maximum rows in the grid to 100.
I have deployed the Blazor app to Azure. If you are interested to check the application, please run this URL. I have also added the table creation script to attached Zip file along with source code. I am expecting your valuable feedback and critiques so that I can improve more in the next articles.
We will see more exciting features on Blazor in upcoming posts.