.NET
08 / 08

Caching & gRPC

.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