0

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

asked Sep 11 at 17:14
2
  • Hey There!. Have you checked that your program has access to the elasticache via IAM permission ? Maybe that is the problem. Commented 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 localhost Commented Sep 11 at 22:59

1 Answer 1

0

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.

answered Sep 12 at 12:24
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.