C
C#8mo ago
Dachi

✅ How Scope lifetime works

I have a web api where when requesting a create endpoint, it afterwards sends an email to that person about that creation. I send email with html and for it i first check if that html was read before and then store it in a variable to improve efficiency (to not read every single time). I have that as AddTransiant, which means that besides i store that in a variable, it still has to be lost, because of a lifetime. But it doesn't, it still knows that. Can someone explain how that lifetime works?
11 Replies
Dachi
DachiOP8mo ago
c#
private async Task<string> GetHtml(string username, string message)
{
if (_cachedHtml != null) return _cachedHtml.Replace("[username]", username).Replace("[message]", message);
var path = GetPathToEmailHtml();
using var reader = new StreamReader(path);
_cachedHtml = await reader.ReadToEndAsync();

return _cachedHtml.Replace("[username]", username).Replace("[message]", message);
}
c#
private async Task<string> GetHtml(string username, string message)
{
if (_cachedHtml != null) return _cachedHtml.Replace("[username]", username).Replace("[message]", message);
var path = GetPathToEmailHtml();
using var reader = new StreamReader(path);
_cachedHtml = await reader.ReadToEndAsync();

return _cachedHtml.Replace("[username]", username).Replace("[message]", message);
}
Here's full code if needed
c#
public class EmailSender : IEmailSender
{
private readonly EmailConfig _emailConfig;
private string? _cachedHtml;

public EmailSender(IOptions<Config> emailConfig)
{
_emailConfig = emailConfig.Value.EmailConfig;
}

public async Task SendEmail(string subject, string toEmail, string username, string message)
{
var apiKey = _emailConfig.ApiKey;
var client = new SendGridClient(apiKey);
var from = new EmailAddress(_emailConfig.FromEmail, "FeatureVotingSystem");
var to = new EmailAddress(toEmail, username);
var plainTextContent = message;
var htmlContent = await GetHtml(username, message);
var msg = MailHelper.CreateSingleEmail(from, to, subject, plainTextContent, htmlContent);

await client.SendEmailAsync(msg);
}

private async Task<string> GetHtml(string username, string message)
{
if (_cachedHtml != null) return _cachedHtml.Replace("[username]", username).Replace("[message]", message);
var path = GetPathToEmailHtml();
using var reader = new StreamReader(path);
_cachedHtml = await reader.ReadToEndAsync();

return _cachedHtml.Replace("[username]", username).Replace("[message]", message);
}

private static string GetPathToEmailHtml()
{
var baseDirectoryPath = Directory.GetParent(Directory.GetCurrentDirectory())!.FullName;
var parentDirectoryPath = Directory.GetParent(Directory.GetCurrentDirectory())!.Name;
var workerPath = string.Concat(parentDirectoryPath, ".Worker");
var path = Path.Combine(baseDirectoryPath, workerPath, "EmailContext", "index.html");

return path;
}
}
c#
public class EmailSender : IEmailSender
{
private readonly EmailConfig _emailConfig;
private string? _cachedHtml;

public EmailSender(IOptions<Config> emailConfig)
{
_emailConfig = emailConfig.Value.EmailConfig;
}

public async Task SendEmail(string subject, string toEmail, string username, string message)
{
var apiKey = _emailConfig.ApiKey;
var client = new SendGridClient(apiKey);
var from = new EmailAddress(_emailConfig.FromEmail, "FeatureVotingSystem");
var to = new EmailAddress(toEmail, username);
var plainTextContent = message;
var htmlContent = await GetHtml(username, message);
var msg = MailHelper.CreateSingleEmail(from, to, subject, plainTextContent, htmlContent);

await client.SendEmailAsync(msg);
}

private async Task<string> GetHtml(string username, string message)
{
if (_cachedHtml != null) return _cachedHtml.Replace("[username]", username).Replace("[message]", message);
var path = GetPathToEmailHtml();
using var reader = new StreamReader(path);
_cachedHtml = await reader.ReadToEndAsync();

return _cachedHtml.Replace("[username]", username).Replace("[message]", message);
}

private static string GetPathToEmailHtml()
{
var baseDirectoryPath = Directory.GetParent(Directory.GetCurrentDirectory())!.FullName;
var parentDirectoryPath = Directory.GetParent(Directory.GetCurrentDirectory())!.Name;
var workerPath = string.Concat(parentDirectoryPath, ".Worker");
var path = Path.Combine(baseDirectoryPath, workerPath, "EmailContext", "index.html");

return path;
}
}
cap5lut
cap5lut8mo ago
in asp.net a scope is created for request, thus for caching between different requests u would need something that has a singleton lifetime
Dachi
DachiOP8mo ago
no, sure i added that as transiant to learn difference but it works regardless of that type i add it
cap5lut
cap5lut8mo ago
with the transient lifetime its a bit different, its used when everything that gets it injected needs its own instance. eg if u have a transient service T and the services A and B and both get T injected, they dont share the same instance of T singleton = one per application lifetime scoped = one per scope the application created (in asp.net core thats per request, in another application u might create scopes differently) transient = one per injection target
Dachi
DachiOP8mo ago
that's what i am tyring to prove but making it transient by that login in the GetHtml this has to be always false if (_cachedHtml != null) return _cachedHtml.Replace("[username]", username).Replace("[message]", message); because that instanse is being renewed every single request but it doesn't
cap5lut
cap5lut8mo ago
so EmailSender is registered as transient? how do u use that instance?
Dachi
DachiOP8mo ago
c#
public class EmailWorker : BackgroundService
{
private readonly WorkerConfig _workerConfig;
private readonly IGetQueuedEmailsRepository _getQueuedEmailsRepository;
private readonly IUpdateEmailQueueStatusRepository _updateEmailQueueStatusRepository;
private readonly IEmailSender _emailSender;

public EmailWorker(
IOptions<Config> config,
IGetQueuedEmailsRepository getQueuedEmailsRepository,
IUpdateEmailQueueStatusRepository updateEmailQueueStatusRepository,
IEmailSender emailSender
)
{
_workerConfig = config.Value.WorkerConfig;
_getQueuedEmailsRepository = getQueuedEmailsRepository;
_updateEmailQueueStatusRepository = updateEmailQueueStatusRepository;
_emailSender = emailSender;
}

protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
var queue = await _getQueuedEmailsRepository.GetQueuedEmails();

foreach (var item in queue)
{
try
{
await _emailSender.SendEmail(item.SubjectName, item.Email, item.UserName, item.EmailText);
}
catch (Exception ex)
{
throw new Exception(ex.Message);
}

await _updateEmailQueueStatusRepository.UpdateEmailQueueStatus(item.Id);
}

await Task.Delay(_workerConfig.DelayInSeconds, stoppingToken);
}
}
}
c#
public class EmailWorker : BackgroundService
{
private readonly WorkerConfig _workerConfig;
private readonly IGetQueuedEmailsRepository _getQueuedEmailsRepository;
private readonly IUpdateEmailQueueStatusRepository _updateEmailQueueStatusRepository;
private readonly IEmailSender _emailSender;

public EmailWorker(
IOptions<Config> config,
IGetQueuedEmailsRepository getQueuedEmailsRepository,
IUpdateEmailQueueStatusRepository updateEmailQueueStatusRepository,
IEmailSender emailSender
)
{
_workerConfig = config.Value.WorkerConfig;
_getQueuedEmailsRepository = getQueuedEmailsRepository;
_updateEmailQueueStatusRepository = updateEmailQueueStatusRepository;
_emailSender = emailSender;
}

protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
var queue = await _getQueuedEmailsRepository.GetQueuedEmails();

foreach (var item in queue)
{
try
{
await _emailSender.SendEmail(item.SubjectName, item.Email, item.UserName, item.EmailText);
}
catch (Exception ex)
{
throw new Exception(ex.Message);
}

await _updateEmailQueueStatusRepository.UpdateEmailQueueStatus(item.Id);
}

await Task.Delay(_workerConfig.DelayInSeconds, stoppingToken);
}
}
}
this class calls that and i think i know what's the problem, since EmailWorker class isn't injected, it uses the same IEmailSender thus it doesn't create new one right? @cap5lut
cap5lut
cap5lut8mo ago
sorry was quick smoking yep thats it transient basically means its as long alive as the service u inject it into and background services are singletons if u would have injected the EmailSender directly into the controller u would have observed that _cachedHtml is always null because controllers are by default transient as well or if u would have injected the IServiceProvider and each time u want to use EmailSender u would request it again.
var es1 = serviceProvider.GetRequiredService<EmailSender>();
var es2 = serviceProvider.GetRequiredService<EmailSender>();
Console.WriteLine(object.ReferenceEquals(es1, es2)); // prints false
var es1 = serviceProvider.GetRequiredService<EmailSender>();
var es2 = serviceProvider.GetRequiredService<EmailSender>();
Console.WriteLine(object.ReferenceEquals(es1, es2)); // prints false
Dachi
DachiOP8mo ago
sure thanks
cap5lut
cap5lut8mo ago
if this answers ur question, please dont forget to $close the thread
MODiX
MODiX8mo ago
Use the /close command to mark a forum thread as answered
Want results from more Discord servers?
Add your server