.NET: Caching & gRPC
Caching Strategies
// IMemoryCache — in-process, single server
builder.Services.AddMemoryCache();
public class ProductService(IMemoryCache cache, IProductRepository repo)
{
public async Task<Product?> GetByIdAsync(int id)
{
var key = $"product:{id}";
if (cache.TryGetValue(key, out Product? cached))
return cached;
var product = await repo.GetByIdAsync(id);
cache.Set(key, product, new MemoryCacheEntryOptions {
AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(10),
SlidingExpiration = TimeSpan.FromMinutes(2),
Priority = CacheItemPriority.Normal,
});
return product;
}
public void Invalidate(int id) => cache.Remove($"product:{id}");
}
// IDistributedCache — Redis (shared across multiple servers)
builder.Services.AddStackExchangeRedisCache(opts => {
opts.Configuration = builder.Configuration.GetConnectionString("Redis");
opts.InstanceName = "MyApp:";
});
public class DistributedProductService(IDistributedCache cache, IProductRepository repo)
{
public async Task<Product?> GetByIdAsync(int id, CancellationToken ct = default)
{
var key = $"product:{id}";
var cached = await cache.GetStringAsync(key, ct);
if (cached is not null)
return JsonSerializer.Deserialize<Product>(cached);
var product = await repo.GetByIdAsync(id, ct);
if (product is not null)
await cache.SetStringAsync(key, JsonSerializer.Serialize(product),
new DistributedCacheEntryOptions { AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(10) }, ct);
return product;
}
}
// Output Cache (ASP.NET Core 7+) — cache full HTTP responses
builder.Services.AddOutputCache(opts => {
opts.AddBasePolicy(b => b.Expire(TimeSpan.FromMinutes(5)));
opts.AddPolicy("Products", b => b.Expire(TimeSpan.FromMinutes(10)).Tag("products"));
});
app.UseOutputCache();
[HttpGet]
[OutputCache(PolicyName = "Products")]
public async Task<IActionResult> GetAll() { }
// Invalidate tagged cache entries
await _outputCache.EvictByTagAsync("products", ct);gRPC
gRPC uses HTTP/2 and Protocol Buffers (binary). ~5x faster than JSON over REST for internal services. Supports streaming. Ideal for microservice-to-microservice communication.
// Protos/users.proto
syntax = "proto3";
option csharp_namespace = "MyApi.Protos";
package users;
service UsersService {
rpc GetUser (GetUserRequest) returns (UserReply);
rpc ListUsers (ListUsersRequest) returns (stream UserReply); // server streaming
rpc CreateUser (CreateUserRequest) returns (UserReply);
}
message GetUserRequest { int32 id = 1; }
message ListUsersRequest { int32 page = 1; int32 page_size = 2; }
message CreateUserRequest { string name = 1; string email = 2; }
message UserReply { int32 id = 1; string name = 2; string email = 3; }// Server — Services/UsersGrpcService.cs
// dotnet add package Grpc.AspNetCore
public class UsersGrpcService : UsersService.UsersServiceBase
{
private readonly IUserRepository _repo;
public UsersGrpcService(IUserRepository repo) => _repo = repo;
public override async Task<UserReply> GetUser(GetUserRequest request, ServerCallContext context)
{
var user = await _repo.GetByIdAsync(request.Id)
?? throw new RpcException(new Status(StatusCode.NotFound, $"User {request.Id} not found"));
return new UserReply { Id = user.Id, Name = user.Name, Email = user.Email };
}
public override async Task ListUsers(ListUsersRequest request,
IServerStreamWriter<UserReply> responseStream, ServerCallContext context)
{
var users = await _repo.GetPagedAsync(request.Page, request.PageSize);
foreach (var user in users)
{
await responseStream.WriteAsync(new UserReply { Id = user.Id, Name = user.Name });
await Task.Delay(10); // simulate streaming pace
}
}
}
// Program.cs
builder.Services.AddGrpc();
app.MapGrpcService<UsersGrpcService>();
// Client
// dotnet add package Grpc.Net.Client Google.Protobuf Grpc.Tools
using var channel = GrpcChannel.ForAddress("https://localhost:5001");
var client = new UsersService.UsersServiceClient(channel);
var user = await client.GetUserAsync(new GetUserRequest { Id = 1 });Keep your own version of these notes — editable, searchable, and organised by your stack.
Start free