I am working on an ASP.NET Core Web API that is deployed to AWS. I am trying to integrate Elastic Cache using Valkey and working locally at the moment. This is my Program.cs:
using Amazon;
using Amazon.S3;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.EntityFrameworkCore;
using Microsoft.IdentityModel.Tokens;
using Microsoft.OpenApi.Models;
using smart_qualify_api.Entities;
using smart_qualify_api.Services.ResumeTemplates;
using StackExchange.Redis;
using System.Security.Authentication;
using System.Text;
var builder = WebApplication.CreateBuilder(args);
var config = builder.Configuration;
// In Program.cs, after other service configurations
builder.Services.AddSingleton<IConnectionMultiplexer>(sp =>
{
var config = builder.Configuration;
var valkeyEndpoint = config["ElastiCache:ValkeyEndpoint"]!;
var useSsl = bool.Parse(config["ElastiCache:UseSsl"] ?? "false");
var redisConfig = new ConfigurationOptions
{
EndPoints = { valkeyEndpoint },
Ssl = useSsl,
AbortOnConnectFail = false, // Prevent app from crashing if connection fails
ConnectTimeout = 30000, // Timeout in milliseconds
SyncTimeout = 10000, // 10 seconds for commands
SslProtocols = SslProtocols.Tls12 | SslProtocols.Tls13, // Explicitly allow modern TLS versions
};
return ConnectionMultiplexer.Connect(redisConfig);
});
builder.Services.AddSingleton<IAmazonS3>(sp => new AmazonS3Client(RegionEndpoint.AFSouth1));
// Add services to the container.
builder.Services.AddControllers();
// Add IHttpClientFactory
builder.Services.AddHttpClient();
// Add Template Registry
builder.Services.AddSingleton<TemplateRegistry>();
// Add CV Analysis Service
builder.Services.AddScoped<smart_qualify_api.Services.CVAnalysisService>();
// Add Push Notification Service
builder.Services.AddScoped<smart_qualify_api.Services.PushNotificationService>();
builder.Services.AddCors(options =>
{
options.AddPolicy("AllowNextJsFrontend", builder =>
{
builder.AllowAnyOrigin()
.AllowAnyHeader()
.AllowAnyMethod();
});
});
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options =>
{
options.RequireHttpsMetadata = false;
options.SaveToken = true;
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = config["Jwt:Issuer"],
ValidAudience = config["Jwt:Audience"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(config["Jwt:Token"]!))
};
});
builder.Services.AddAuthorization();
builder.Services.AddSwaggerGen(swagger =>
{
swagger.SwaggerDoc("v1", new OpenApiInfo { Version = "v1" });
swagger.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme()
{
Name = "Authorization",
Type = SecuritySchemeType.Http,
Scheme = JwtBearerDefaults.AuthenticationScheme,
BearerFormat = "JWT",
In = ParameterLocation.Header,
Reference = new OpenApiReference
{
Id = JwtBearerDefaults.AuthenticationScheme,
Type = ReferenceType.SecurityScheme,
}
});
swagger.AddSecurityRequirement(new OpenApiSecurityRequirement
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "Bearer"
}
}, Array.Empty<string>()
}
});
});
builder.Services.AddAuthorizationBuilder().AddPolicy("AdminUserPolicy", options =>
{
options.RequireAuthenticatedUser();
options.RequireRole("admin", "user", "student");
}).AddPolicy("AdminPolicy", options =>
{
options.RequireAuthenticatedUser();
options.RequireRole("admin");
}).AddPolicy("UserPolicy", options =>
{
options.RequireAuthenticatedUser();
options.RequireRole("user", "student");
});
builder.Services.AddDbContext<SMQDbContext>(options =>
{
options.UseNpgsql(builder.Configuration.GetConnectionString("DefaultConnection"));
});
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
//app.UseHttpsRedirection();
app.UseCors("AllowNextJsFrontend");
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.Run();
And this is my controller to access the templates:
using Amazon.S3;
using Amazon.S3.Model;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using QuestPDF.Infrastructure;
using smart_qualify_api.Entities;
using smart_qualify_api.Models.Resume;
using smart_qualify_api.Services.ResumeTemplates;
using StackExchange.Redis;
using System.Security.Claims;
using System.Text.Json;
namespace smart_qualify_api.Controllers.Core
{
[Route("api/[controller]")]
[ApiController]
[ApiExplorerSettings(IgnoreApi = false)]
public class ResumeController : ControllerBase
{
private readonly SMQDbContext _dbContext;
private readonly IConfiguration _config;
private readonly TemplateRegistry _templateRegistry;
private readonly IHttpClientFactory _httpClientFactory;
private readonly IAmazonS3 _s3Client;
private readonly IConnectionMultiplexer _redis; // Add Redis connection
private readonly string _bucketName = "smart-qualify-assets";
private readonly string _resumeFolder = "user-resumes/";
private readonly string _cacheKey = "resume_templates"; // Cache key for templates
private readonly TimeSpan _cacheTTL = TimeSpan.FromMinutes(30); // Cache expiration
public ResumeController(
SMQDbContext dbContext,
IConfiguration config,
TemplateRegistry templateRegistry,
IHttpClientFactory httpClientFactory,
IAmazonS3 s3Client,
IConnectionMultiplexer redis // Inject Redis
)
{
_dbContext = dbContext;
_config = config;
_templateRegistry = templateRegistry;
_httpClientFactory = httpClientFactory;
_s3Client = s3Client;
_redis = redis; // Initialize Redis
}
[HttpGet("getResumeTemplates")]
[Authorize]
public IActionResult GetResumeTemplates()
{
try
{
// Get Redis database
var db = _redis.GetDatabase();
// Try to get templates from cache
var cachedTemplates = db.StringGet(_cacheKey);
if (cachedTemplates.HasValue)
{
// Deserialize cached templates
var templates = JsonSerializer.Deserialize<List<object>>(cachedTemplates!);
return Ok(new { success = true, templates });
}
// Cache miss: Fetch templates from TemplateRegistry
var templatesFromRegistry = _templateRegistry.GetAllTemplates()
.Select(t => new { templateId = t.TemplateId, name = t.Name, templateImageUrl = t.TemplateImageUrl })
.ToList();
// Serialize and store in cache with TTL
var serializedTemplates = JsonSerializer.Serialize(templatesFromRegistry);
db.StringSet(_cacheKey, serializedTemplates, _cacheTTL);
return Ok(new { success = true, templates = templatesFromRegistry });
}
catch (RedisException ex)
{
// Handle Redis errors (e.g., connection issues)
// Fallback to fetching from TemplateRegistry without caching
var templates = _templateRegistry.GetAllTemplates()
.Select(t => new { templateId = t.TemplateId, name = t.Name, templateImageUrl = t.TemplateImageUrl })
.ToList();
return Ok(new { success = true, templates });
}
catch (Exception ex)
{
// Handle other errors
return StatusCode(500, new { success = false, message = "Error retrieving resume/cv templates.", Error = ex.Message });
}
}
}
These are my appsettings:
{
"ConnectionStrings": {
//"DefaultConnection": "Server=.\\SQLEXPRESS; Database=SmartQualifyDb; Trusted_Connection=True; TrustServerCertificate=true;",
//"DefaultConnection": "Server=awseb-e-dimv4pgcpg-stack-awsebrdsdatabase-t9atez32kzzv.cxgqqkemg9gv.af-south-1.rds.amazonaws.com; Database=SmartQualifyDEV; User Id=smartqualifydev; Password={mypassword}; TrustServerCertificate=true; Encrypt=False;"
"DefaultConnection": "Host=awseb-e-tmpi2juhkc-stack-awsebrdsdatabase-xnd8m1nnlxta.cxgqqkemg9gv.af-south-1.rds.amazonaws.com;Port=5432;Database=SmartQualifyDEV;Username=postgres;Password={mypassword};SslMode=Require;"
},
"Jwt": {
"Token": "{mytoken}",
"Issuer": "https://localhost:7292/",
"Audience": "https://localhost:7292/"
},
"OpenAI": {
"ApiKey": "your-openai-api-key-here"
},
"Firebase": {
"ServerKey": "your-firebase-server-key-here"
},
"ElastiCache": {
"ValkeyEndpoint": "smartqualif-esmt7q.serverless.afs1.cache.amazonaws.com:6379",
"UseSsl": true
},
"MailOptions": {
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}
I get an error when I run the endpoint:
RedisConnectionException: It was not possible to connect to the redis server(s) smartqualify-esmt7q.serverless.afs1.cache.amazonaws.com:6379/Interactive. ConnectTimeout
StackExchange.Redis.RedisConnectionException: 'The message timed out in the backlog attempting to send because no connection became available (10000ms) - Last Connection Exception: It was not possible to connect to the redis server(s) smartqualify-esmt7q.serverless.afs1.cache.amazonaws.com:6379/Interactive. ConnectTimeout, command=GET, timeout: 10000, inst: 0, qu: 1, qs: 0, aw: False, bw: SpinningDown, rs: NotStarted, ws: Initializing, in: 0, last-in: 0, cur-in: 0, sync-ops: 3, async-ops: 0, serverEndpoint: smartqualify-esmt7q.serverless.afs1.cache.amazonaws.com:6379, conn-sec: n/a, aoc: 0, mc: 1/1/0, mgr: 10 of 10 available, clientName: SIYANDA(SE.Redis-v2.9.17.8862), IOCP: (Busy=0,Free=1000,Min=1,Max=1000), WORKER: (Busy=1,Free=32766,Min=12,Max=32767), POOL: (Threads=5,QueuedItems=0,CompletedItems=1681,Timers=5), v: 2.9.17.8862 (Please take a look at this article for some common client-side issues that can cause timeouts: https://stackexchange.github.io/StackExchange.Redis/Timeouts)'
What I expect is being able to access Valkey and store the cached data there from access, and then retrieve the data when I run the getAllTemplates endpoint again.Any possible solution or help would be greatly appreciated
-
Hey There!. Have you checked that your program has access to the elasticache via IAM permission ? Maybe that is the problem.Saleh– Saleh2025年09月11日 20:56:09 +00:00Commented Sep 11 at 20:56
-
Hi @Saleh I have been debugging and what I found is that when I deployed my api to elasticbeanstalk the endpoint works well with ElasticCache, and it only doesn't connect when I am running localhost. Is there a way I could run this with localhost? On a production environment it works and it caches well, I also want to do so on a dev environment with localhostSmart Qualilfy– Smart Qualilfy2025年09月11日 22:59:42 +00:00Commented Sep 11 at 22:59
1 Answer 1
I managed to come up with a solution, AWS ElastiCache seems like it doesn't support localhost so I ran the api with a docker container so we can setup valkey, and it works like a charm, it also didn't affect the deployed api which is great.
Comments
Explore related questions
See similar questions with these tags.