Why is my blazor app leaving so many ports open - signalr

I created a .net 6 app using server side Blazor and SignalR. The app was basically a single page with 10 different components. Each component was a client that looked something like this:
#code {
private HubConnection? hubConnection;
private ExampleViewModel data { get; set; } = new ExampleViewModel();
protected override async Task OnInitializedAsync()
{
hubConnection = new HubConnectionBuilder()
.WithUrl(NavigationManager.ToAbsoluteUri("/mainhub"))
.Build();
hubConnection.On<ExampleViewModel>("example", (Data) =>
{
data = Data;
StateHasChanged();
});
await hubConnection.StartAsync();
}
public async ValueTask DisposeAsync()
{
if (hubConnection is not null)
{
await hubConnection.DisposeAsync();
}
}
}
Each component has a "broadcaster" that runs on a timer and makes a call to the database using Mediator and Dapper. Example:
public class ExampleBroadcaster : IDataBroadcaster
{
private readonly IMediator _mediator;
private readonly ILogger<ExampleBroadcaster> _logger;
private readonly IHubContext<MainHub> _mainHub;
private readonly IMemoryCache _cache;
private const string Something = "example";
private Timer _timer;
public ExampleBroadcaster(IHubContext<MainHub> mainHub,
IMediator mediator, ILogger<ExampleBroadcaster> logger,
IMemoryCache cache)
{
_mainHub = mainHub;
_mediator = mediator;
_logger = logger;
_cache = cache;
}
public void Start()
{
_timer = new Timer(BroadcastData, null, 0, 30000);
}
private async void BroadcastData(object? state)
{
ExampleViewModel viewModel;
try
{
if (_cache.TryGetValue(Something, out ExampleViewModel data))
{
viewModel = data;
}
else
{
viewModel = _mediator.Send(new GetExampleData()).Result;
_cache.Set(Something, viewModel, TimeSpan.FromMinutes(10));
}
await _mainHub.Clients.All.SendAsync("example", viewModel);
}
catch (Exception ex)
{
_logger.LogError(ex, ex.Message);
}
}
}
The mediator handler simply uses Dapper to get data from the database:
public class GetExampleData : IRequest<ExampleViewModel>
{
}
public class GetExampleDataHandler : IRequestHandler<GetExampleData, ExampleViewModel>
{
private readonly IDbConnectionFactory _connectionFactory;
private string _storedProcedure = "some sproc name";
public GetExampleDataHandler(IDbConnectionFactory connectionFactory)
{
_connectionFactory = connectionFactory;
}
public async Task<ExampleViewModel> Handle(GetExampleData request, CancellationToken cancellationToken)
{
using (var connection = _connectionFactory.GetReadOnlyConnection())
{
return await connection.QueryFirstAsync<ExampleViewModel>(_storedProcedure, CommandType.StoredProcedure);
}
}
}
This is the main razor page that houses all the individual components:
#code {
private HubConnection? hubConnection;
protected override async Task OnInitializedAsync()
{
try
{
hubConnection = new HubConnectionBuilder()
.WithUrl(NavigationManager.ToAbsoluteUri("/mainhub"))
.Build();
await hubConnection.StartAsync();
await hubConnection.SendAsync("Init");
}
catch(Exception exception)
{
Logger.LogError(exception, exception.Message);
}
}
public async ValueTask DisposeAsync()
{
if (hubConnection is not null)
{
await hubConnection.DisposeAsync();
}
}
}
Finally, the MainHub.cs code:
public class MainHub : Hub
{
IEnumerable<IDataBroadcaster> _broadcasters;
private static bool _started;
public MainHub(IEnumerable<IDataBroadcaster> broadcasters)
{
_broadcasters = broadcasters;
}
public void Init()
{
if (!_started)
{
StartBroadcasting();
_started = true;
}
}
private void StartBroadcasting()
{
foreach (var broadcaster in _broadcasters)
{
broadcaster.Start();
}
}
}
This all worked fine locally, in our dev environment, and our test environment. In production, we found that the app was crashing after a number of hours. According to the server admins, the app is opening 100s or 1000s of ports and leaving them open until the number of allotted ports was hit, causing the app to crash.
What is the issue here? The broadcasters are registered as singletons. This app only runs on one web server.

Related

Asp.Net Core Web API entity Framework connect to two databases

I am doing an Asp.Net Core API and I am connecting to a two databases using EF setted in appsettings.json
"ConnectionStrings": {
"DBConnection": "Server=2679; Database=A; Trusted_Connection=true; MultipleActiveResultSets=true; Integrated Security=true;Encrypt=false;",
"DBConnection2": "Server= 2684; Database=B; Trusted_Connection=true; MultipleActiveResultSets=true; Integrated Security=true;Encrypt=false;"
}
In my Program.cs I have setted this two connections
var connectionString = (builder.Configuration.GetConnectionString("DBConnection") ?? String.Empty).Trim();
var connectionString2 = (builder.Configuration.GetConnectionString("DBConnectionAnthem") ?? String.Empty).Trim();
builder.Services.ConfigureServices(connectionString);
builder.Services.ConfigureServices(connectionString2);
I call ConfigureServices with both connections and looks like this
public static class Configure
{
public static void ConfigureServices(this IServiceCollection services, string connectionString)
{
services
.AddDbContext<CobraDbContext>(options => options.UseSqlServer(connectionString));
........
services.AddScoped<IUnitOfWork, UnitOfWork>();
}
}
}
I am using EF and I have defined my DbContext like this
public class CobraDbContext : DbContext
{
public CobraDbContext(DbContextOptions<CobraDbContext> options)
: base(options)
{
}
public DbSet<SearchResultModel> ParticipantSearch { get; set; } = null!;
....
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
}
}
}
From My Controller Method I call the Service.cs witch use UnitOfwork
public class ParticipantService : IParticipantService
{
private readonly ILogger<ParticipantService> _logger;
private readonly IUnitOfWork _iUnitOfwork;
public ParticipantService(ILogger<ParticipantService> logger, IUnitOfWork iUnitOfwork)
{
_logger = logger;
_iUnitOfwork = iUnitOfwork;
}
public async Task<HttpResponseMessage> Search(string participantId)
{
try
{
List<SearchResultModel>? search = await _iUnitOfwork.Participant.AAA(participantId);
return Request.CreateResponse(HttpStatusCode.OK, search);
}
catch (Exception ex)
{
}
}
From My Service I call the Repository that have a generic repository
public class ParticipantRepository : GenericRepository<SearchResultModel>, IParticipantRepository
{
private readonly CobraDbContext _db;
public ParticipantRepository(CobraDbContext db) : base(db)
{
_db = db;
}
public async Task<List<ParticipantPlanModel>?> AAA(string participantId)
{
Query participantGetByID = new();
Dictionary<string, string> dictionary = new Dictionary<string, string>();
participantGetByID.SelectFrom = " exec sp";
List<ParticipantPlanModel>? _return = await ExecuteGeneric(participantGetByID);
return _return;
}
}
I have my generic repo like this
public class GenericRepository<T> : IGenericRepository<T> where T : class
{
protected readonly CobraDbContext Context;
internal DbSet<T> dbSet;
public GenericRepository(CobraDbContext context)
{
Context = context;
dbSet = context.Set<T>();
}
public async Task<List<T>?> ExecuteGeneric(Query query)
{
// var defaultVal = default(T);
var cParameters = new SqlParameter[query.Parameters?.Count ?? 0];
if (query.Parameters != null)
{
int i = 0;
foreach (KeyValuePair<string, string> _param in query.Parameters)
{
cParameters[i] = new SqlParameter() { ParameterName = _param.Key, Value = _param.Value };
i++;
}
}
return await Context.Set<T>().FromSqlRaw(query.SelectFrom + query.Where + query.OrderBy, cParameters).ToListAsync();
}
Depending on the parameter I have to call a database or a the another. I know I can do this duplicating almost all the code... Having to DbContext and two generic Repo..
Is there a way to simplify it and not replicate most of the code?
Thanks

How to create Global Variable per hosted service in .Net core

How to create a global variable that can be unique per hosted service execution?
Complete Code:
https://github.com/matvi/dotnet-hosted-services
The problem:
When running hosted services is difficult to keep track of the execution without logs. In order to keep track of the execution of the hosted services I implemented logs with a unique traceId (GUID)
The problem is that the TraceLogId is being created per Task using Static Memory and when 2 task runs at the same time the first TraceLogId gets overridden by the second task.
Is there any way to avoid the traceLogId being overridden?
public static class GlobalVariables
{
public static Guid TraceLogId { get; set; }
}
public class Task1Service : ITask1Service
{
public async Task StartAsync(CancellationToken cancellationToken)
{
GlobalVariables.TraceLogId = Guid.NewGuid();
Console.WriteLine($"Task1 executing with traceLogId = {GlobalVariables.TraceLogId}");
Console.WriteLine($"Task1 will wait 5 seconds = {GlobalVariables.TraceLogId}");
await Task.Delay(5000, cancellationToken);
Console.WriteLine($"Task1 ending = {GlobalVariables.TraceLogId}");
}
}
public class Task2Service : ITask2Service
{
public async Task StartAsync(CancellationToken cancellationToken)
{
Console.WriteLine("Task2 executing");
GlobalVariables.TraceLogId = Guid.NewGuid();
Console.WriteLine($"Task2 executing with traceLogId = {GlobalVariables.TraceLogId}");
Console.WriteLine($"Task2 ending = {GlobalVariables.TraceLogId}");
}
}
When the code is executed Task1 gets a TraceLogId but when it finishes it has the traceLogId that was assigned in Task2.
using System;
using System.Threading;
using System.Threading.Tasks;
using Cronos;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
namespace HostedServicesPoc.Tasks
{
public abstract class CronJobServiceBase : IHostedService, IDisposable
{
private readonly ILogger _log;
private readonly HostedServiceTaskSettingsBase _hostedServiceTaskSettingsBase;
private System.Timers.Timer _timer;
private readonly CronExpression _expression;
private readonly TimeZoneInfo _timeZoneInfo;
protected CronJobServiceBase(IOptions<HostedServiceTaskSettingsBase> hostedServiceSettings, ILogger<CronJobServiceBase> log)
{
_log = log;
_hostedServiceTaskSettingsBase = hostedServiceSettings?.Value;
_expression = CronExpression.Parse(_hostedServiceTaskSettingsBase.CronExpressionTimer, CronFormat.Standard);
_timeZoneInfo = TimeZoneInfo.Local;
}
public virtual async Task StartAsync(CancellationToken cancellationToken)
{
_log.LogInformation($"{GetType()} is Starting");
if (_hostedServiceTaskSettingsBase.Active)
{
await ScheduleJob(cancellationToken);
}
}
public Task StopAsync(CancellationToken cancellationToken)
{
_log.LogInformation($"{GetType()} is Stopping");
return Task.CompletedTask;
}
private async Task ScheduleJob(CancellationToken cancellationToken)
{
var next = _expression.GetNextOccurrence(DateTimeOffset.Now, _timeZoneInfo);
if (next.HasValue)
{
var delay = next.Value - DateTimeOffset.Now;
if (delay.TotalMilliseconds <= 0) // prevent non-positive values from being passed into Timer
{
await ScheduleJob(cancellationToken);
}
_timer = new System.Timers.Timer(delay.TotalMilliseconds);
_timer.Elapsed += async (sender, args) =>
{
_timer.Dispose(); // reset and dispose timer
_timer = null;
if (!cancellationToken.IsCancellationRequested)
{
await ExecuteTaskAsync(cancellationToken);
}
if (!cancellationToken.IsCancellationRequested)
{
await ScheduleJob(cancellationToken); // reschedule next
}
};
_timer.Start();
}
await Task.CompletedTask;
}
protected virtual async Task ExecuteTaskAsync(CancellationToken cancellationToken)
{
await Task.Delay(5000, cancellationToken);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool dispose)
{
try
{
if (dispose)
{
_timer?.Dispose();
}
}
finally
{
}
}
}
}
TaskServices:
public class Task1HostedService : CronJobServiceBase
{
private readonly IServiceProvider _serviceProvider;
public Task1HostedService(
IOptions<Task1HostedServiceSettings> hostedServiceSettings,
ILogger<CronJobServiceBase> log,
IServiceProvider serviceProvider) : base(hostedServiceSettings, log)
{
_serviceProvider = serviceProvider;
}
protected override async Task ExecuteTaskAsync(CancellationToken cancellationToken)
{
using var scope = _serviceProvider.CreateScope();
var task1Service = scope.ServiceProvider.GetRequiredService<ITask1Service>();
await task1Service.StartAsync(cancellationToken);
}
}
I recommend a scoped value for this; AsyncLocal<T> fits the bill.
public static class GlobalVariables
{
private static AsyncLocal<Guid> _TraceLogId = new();
public static Guid TraceLogId => _TraceLogId.Value;
public static IDisposable SetTraceLogId(Guid value)
{
var oldValue = _TraceLogId.Value;
_TraceLogId.Value = value;
return Disposable.Create(() => _TraceLogId.Value = oldValue);
}
}
public class Task1Service : ITask1Service
{
public async Task StartAsync(CancellationToken cancellationToken)
{
using var traceIdScope = GlobalVariables.SetTraceLogId(Guid.NewGuid());
Console.WriteLine($"Task1 executing with traceLogId = {GlobalVariables.TraceLogId}");
Console.WriteLine($"Task1 will wait 5 seconds = {GlobalVariables.TraceLogId}");
await Task.Delay(5000, cancellationToken);
Console.WriteLine($"Task1 ending = {GlobalVariables.TraceLogId}");
}
}
This uses Disposable from my Disposables library.

Why is my hubconnection on but the method is not being fired?

I've set up signalr in my blazor server side application and for some reason this hubconnection is not being triggered, when the hubconnection is on, it completely ignores the BroadcastData method and doesnt even fire it:
private HubConnection hubConnection;
private string _hubUrl;
protected override async Task OnInitializedAsync()
{
string baseUrl = NavigationManager.BaseUri;
_hubUrl = baseUrl.TrimEnd('/') + SignalRHub.HubUrl;
_hubConnection = new HubConnectionBuilder()
.WithUrl(_hubUrl)
.Build();
hubConnection.On<ClientDTO>("BroadcastData", BroadcastData);
await hubConnection.StartAsync();
}
private void BroadcastData(ClientDTO payload)
{
dashboardData = payload;
StateHasChanged();
}
I have everything setup for this to be "working" but clearly it isn't working and I'm completely lost at what could be the problem... Please take a look at what I have so far and see if you can see what's going on:
Startup:
public Startup(IConfiguration configuration)
{
Configuration = configuration;
StartTimer();
}
private void StartTimer()
{
_timer = new System.Timers.Timer();
_timer.Interval = 5000;
_timer.Elapsed += TimerElapsed;
_timer.Start();
}
private void TimerElapsed(Object source, ElapsedEventArgs e)
{
Trigger();
}
public void Trigger()
{
try
{
using (HttpClient client = new HttpClient())
{
//Trigger on elapsed
var response = client.GetAsync(Configuration.GetConnectionString("ApiTriggerURL")).Result;
}
}
catch
{
Console.WriteLine("something terrible has happened...");
}
}
services.AddScoped(typeof(SignalRHub));
services.AddScoped<IHub, SignalRHub>();
services.AddScoped<HttpClient>();
services.AddSignalR();
services.AddResponseCompression(opts =>
{
opts.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(
new[] { "application/octet-stream" });
});
public void Configure(IApplicationBuilde app, IWebHostEnvironment env)
{
app.UseResponseCompression();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapBlazorHub();
endpoints.MapFallbackToPage("/_Host");
endpoints.MapHub<SignalRHub>(SignalRHub.HubUrl);
});
}
appsettings.json: (fyi, the trigger is working, the api endpoint is being hit as it returns a status 200 ok result)
"ConnectionStrings":
{
"ApiTriggerURL": "http://localhost:5000/api/SignalRHub/GetMyData"
}
Then we have my api controller: (here you can see the status 200 ok)
private readonly SignalRHub _signalRHub;
public SignalRHubController(SignalRHub signalRHub)
{
_signalRHub = signalRHub;
}
[HttpGet]
public ObjectResult GetMyData()
{
try
{
Task.WhenAll(_signalRHub.BroadcastData()); // Call hub broadcast method
return this.StatusCode((int)HttpStatusCode.OK, "trigger has been triggered");
}
catch
{
return this.StatusCode((int)HttpStatusCode.InternalServerError, "christ, the ting is broken fam");
}
}
When we look into the _signalRHub.BroadcastData(), we see this:
public class SignalRHub : Hub, IHub
{
private readonly ClientService _clientService;
readonly IHubContext<SignalRHub> _hubContext;
public const string HubUrl = "/chathub"; //this is being read in the startup in the endpoints
public SignalRHub(ClientService clientService, IHubContext<SignalRHub> hubContext)
{
_clientService = clientService;
_hubContext = hubContext;
}
public async Task BroadcastData()
{
var data = _clientService .GetDataAsync().Result;
await _hubContext.Clients.All.SendAsync("BroadcastData", data); //send data to all clients
}
}
And this in turn should basically do this signalrhub every x seconds (depending on timer)
I know my code is a whole load of madness, but please look pass this and help me to understand why this isn't working! Thank you in advance!
Try following:
hubConnection.On<ClientDTO>("BroadcastData", (payload)=>
BroadcastData(payload);
);
Instead of
hubConnection.On<ClientDTO>("BroadcastData", BroadcastData);

Cannot access a disposed object. with SignalR and Timer Manager

I wanna make my function send data as a real time (every 2 seconds or once there is change in the database table ) but the problem is there is Exception keep appread in my below code.
The exception details are:
'Cannot access a disposed object.
public class MyHub : Hub
{
private readonly IRepository<MyTable, long> _repository;
private readonly IUnitOfWorkManager _unitOfWorkManager;
public HCHub(IUnitOfWorkManager unitOfWorkManager,IRepository<MyTable, long> repository)
{
_repository = repository;
_unitOfWorkManager = unitOfWorkManager;
}
public void Get(TestDto testDto)
{
try {
using (var unitOfWork = _unitOfWorkManager.Begin())
{
var result= _repository.GetDbContext().Set<MyTable>()
.Include(x => x.list)
.ThenInclude(x => x.list2)
.ThenInclude(x => x.obj).ToList();
new TimerManager(async () =>
await Clients.All.SendAsync("listen", result) //<====== in this Line the exception occured
);
}
}
catch(Exception ex)
{
throw new UserFriendlyException(ex.InnerException.Message.ToString());
}
}
and TimerManager Code is
public class TimerManager
{
private Timer _timer;
private AutoResetEvent _autoResetEvent;
private Action _action;
public DateTime TimerStarted { get; }
public TimerManager(Action action)
{
_action = action;
_autoResetEvent = new AutoResetEvent(false);
_timer = new Timer(Execute, _autoResetEvent, 1000, 2000);
TimerStarted = DateTime.Now;
}
public void Execute(object stateInfo)
{
_action();
if ((DateTime.Now - TimerStarted).Seconds > 60)
{
_timer.Dispose();
}
}
}
So the problem is in Timer Manager or in myHub or the way that I'm simulate the realtime data by TimerManager is not acceptable ?!
Once you exit the hub method you aren't guaranteed to be able to access the Clients property. If you want to do something like that, you should inject an IHubContext<THub> into your Hubs constructor and use that instead. You can read more about IHubContext in https://learn.microsoft.com/aspnet/core/signalr/hubcontext?view=aspnetcore-3.1#get-an-instance-of-ihubcontext

Error in Redis Connection in ASP.NET Core App Hosted on Azure

We are facing problems with Redis caching and it's causing crashes in our site.
The following is how we implemented it:
We used the following connection string:
"*******.redis.cache.windows.net:6380,password=*****=,ssl=True,abortConnect=False"
We created a service class:
using Microsoft.Extensions.Options;
using SarahahDataAccessLayer;
using StackExchange.Redis;
using System;
namespace Sarahah.Services
{
public class RedisService
{
private static Lazy<ConnectionMultiplexer> lazyConnection;
private readonly ApplicationSettings _settings;
public RedisService(IOptions<ApplicationSettings> settings)
{
_settings = settings.Value;
lazyConnection = new Lazy<ConnectionMultiplexer>(() =>
{
return ConnectionMultiplexer.Connect(_settings.RedisConnection);
});
}
public ConnectionMultiplexer Connection
{
get
{
return lazyConnection.Value;
}
}
}
}
Then in Startup.cs I use the following:
services.AddSingleton<RedisService>();
Then in controllers we use dependency injection and we assign to a multiplexer:
connectionMultiplexer = redisService.Connection;
This is how we get from the cache:
private async Task<string> GetFromCache(string key)
{
if (connectionMultiplexer.IsConnected)
{
var cache = connectionMultiplexer.GetDatabase();
return await cache.StringGetAsync(key);
}
else
{
return null;
}
}
This is how we delete:
private async Task DeleteFromCache(string subdomain)
{
if (connectionMultiplexer.IsConnected)
{
var cache = connectionMultiplexer.GetDatabase();
await cache.KeyDeleteAsync(subdomain).ConfigureAwait(false);
}
}
This is how we add:
{
if (connectionMultiplexer.IsConnected)
{
var cache = connectionMultiplexer.GetDatabase();
TimeSpan expiresIn;
// Search Cache
if (key.Contains("-"))
{
expiresIn = new TimeSpan(0, GetMessagesCacheExpiryMinutes, 0);
}
// User info cache
else
{
expiresIn = new TimeSpan(GetProfileCacheExpiryHours, 0, 0);
}
await cache.StringSetAsync(key, serializedData, expiresIn).ConfigureAwait(false);
}
However, we get the following error:
No connection is available to service this operation
Although we have a lot of users, we only see few connections in Azure portal:
Please note that we hosted the redis cache in the same region of the web app.
Your support is appreciated.
Each time your dependency injection calls instantiates the RedisService class, your code ends up assigning a new Lazy<ConnectionMultiplexer> to lazyConnection, thus resulting in a new connection as well as a connection leak as you are not calling Close() or Dispose() on the old lazyConnection.
Try changing your code like this:
In Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
.........<whatever you have here>
services.AddSingleton<RedisService>();
services.Configure<ApplicationSettings>(options => Configuration.GetSection("ApplicationSettings").Bind(options));
}
RedisService.cs
public class RedisService
{
private readonly ApplicationSettings _settings;
private static Lazy<ConnectionMultiplexer> lazyConnection;
static object connectLock = new object();
public RedisService(IOptions<ApplicationSettings> settings)
{
_settings = settings.Value;
if (lazyConnection == null)
{
lock (connectLock)
{
if (lazyConnection == null)
{
lazyConnection = new Lazy<ConnectionMultiplexer>(() =>
{
return ConnectionMultiplexer.Connect(_settings.RedisConnection);
});
}
}
}
}
public static ConnectionMultiplexer Connection
{
get
{
return lazyConnection.Value;
}
}
}
ApplicationSettings.cs
public class ApplicationSettings
{
public string RedisConnection { get; set; }
}
appsettings.json
{
"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Default": "Debug",
"System": "Information",
"Microsoft": "Information"
}
},
"ApplicationSettings": {
"RedisConnection": "yourcachename.redis.cache.windows.net:6380,password=yourpassword,ssl=True,abortConnect=False,syncTimeout=4000"
}
}
HomeController.cs
public class HomeController : Controller
{
private RedisService redisService;
private ConnectionMultiplexer connectionMultiplexer;
public HomeController(IOptions<ApplicationSettings> settings)
{
redisService = new RedisService(settings);
connectionMultiplexer = RedisService.Connection;
}
public IActionResult Index()
{
AddToCache("foo1", "bar").GetAwaiter().GetResult();
return View();
}
private async Task<string> GetFromCache(string key)
{
if (connectionMultiplexer.IsConnected)
{
var cache = connectionMultiplexer.GetDatabase();
return await cache.StringGetAsync(key);
}
else
{
return null;
}
}
private async Task DeleteFromCache(string subdomain)
{
if (connectionMultiplexer.IsConnected)
{
var cache = connectionMultiplexer.GetDatabase();
await cache.KeyDeleteAsync(subdomain).ConfigureAwait(false);
}
}
private async Task AddToCache(string key, string serializedData)
{
var GetMessagesCacheExpiryMinutes = 5;
var GetProfileCacheExpiryHours = 1;
if (connectionMultiplexer.IsConnected)
{
var cache = connectionMultiplexer.GetDatabase();
TimeSpan expiresIn;
// Search Cache
if (key.Contains("-"))
{
expiresIn = new TimeSpan(0, GetMessagesCacheExpiryMinutes, 0);
}
// User info cache
else
{
expiresIn = new TimeSpan(GetProfileCacheExpiryHours, 0, 0);
}
await cache.StringSetAsync(key, serializedData, expiresIn).ConfigureAwait(false);
}
}

Resources