Skip to content

Commit c94edf9

Browse files
committed
Replace Background MQ with Background Jobs
1 parent 53c6934 commit c94edf9

File tree

7 files changed

+132
-106
lines changed

7 files changed

+132
-106
lines changed

MyApp.ServiceInterface/EmailServices.cs

+19-11
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
using System.Net.Mail;
22
using Microsoft.Extensions.Logging;
33
using ServiceStack;
4-
using MyApp.ServiceModel;
4+
using ServiceStack.Jobs;
55

66
namespace MyApp.ServiceInterface;
77

@@ -45,16 +45,26 @@ public class SmtpConfig
4545
public string? Bcc { get; set; }
4646
}
4747

48-
/// <summary>
49-
/// Uses a configured SMTP client to send emails
50-
/// </summary>
51-
public class EmailServices(SmtpConfig config, ILogger<EmailServices> log)
52-
// TODO: Uncomment to enable sending emails with SMTP
53-
// : Service
48+
public class SendEmail
49+
{
50+
public string To { get; set; }
51+
public string? ToName { get; set; }
52+
public string Subject { get; set; }
53+
public string? BodyText { get; set; }
54+
public string? BodyHtml { get; set; }
55+
}
56+
57+
[Worker("smtp")]
58+
public class SendEmailCommand(ILogger<SendEmailCommand> logger, IBackgroundJobs jobs, SmtpConfig config)
59+
: SyncCommand<SendEmail>
5460
{
55-
public object Any(SendEmail request)
61+
private static long count = 0;
62+
protected override void Run(SendEmail request)
5663
{
57-
log.LogInformation("Sending email to {Email} with subject {Subject}", request.To, request.Subject);
64+
Interlocked.Increment(ref count);
65+
var log = Request.CreateJobLogger(jobs, logger);
66+
log.LogInformation("Sending {Count} email to {Email} with subject {Subject}",
67+
count, request.To, request.Subject);
5868

5969
using var client = new SmtpClient(config.Host, config.Port);
6070
client.Credentials = new System.Net.NetworkCredential(config.Username, config.Password);
@@ -80,7 +90,5 @@ public object Any(SendEmail request)
8090
}
8191

8292
client.Send(msg);
83-
84-
return new EmptyResponse();
8593
}
8694
}

MyApp.ServiceModel/Emails.cs

-15
This file was deleted.

MyApp/Configure.BackgroundJobs.cs

+91
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
using Microsoft.AspNetCore.Identity;
2+
using ServiceStack;
3+
using ServiceStack.Jobs;
4+
using MyApp.Data;
5+
using MyApp.ServiceInterface;
6+
7+
[assembly: HostingStartup(typeof(MyApp.ConfigureBackgroundJobs))]
8+
9+
namespace MyApp;
10+
11+
public class ConfigureBackgroundJobs : IHostingStartup
12+
{
13+
public void Configure(IWebHostBuilder builder) => builder
14+
.ConfigureServices((context,services) => {
15+
var smtpConfig = context.Configuration.GetSection(nameof(SmtpConfig))?.Get<SmtpConfig>();
16+
if (smtpConfig is not null)
17+
{
18+
services.AddSingleton(smtpConfig);
19+
}
20+
// Lazily register SendEmailCommand to allow SmtpConfig to only be required if used
21+
services.AddTransient<SendEmailCommand>(c => new SendEmailCommand(
22+
c.GetRequiredService<ILogger<SendEmailCommand>>(),
23+
c.GetRequiredService<IBackgroundJobs>(),
24+
c.GetRequiredService<SmtpConfig>()));
25+
26+
services.AddPlugin(new CommandsFeature());
27+
services.AddPlugin(new BackgroundsJobFeature());
28+
services.AddHostedService<JobsHostedService>();
29+
}).ConfigureAppHost(afterAppHostInit: appHost => {
30+
var services = appHost.GetApplicationServices();
31+
32+
// Log if EmailSender is enabled and SmtpConfig missing
33+
var log = services.GetRequiredService<ILogger<ConfigureBackgroundJobs>>();
34+
var emailSender = services.GetRequiredService<IEmailSender<ApplicationUser>>();
35+
if (emailSender is EmailSender)
36+
{
37+
var smtpConfig = services.GetService<SmtpConfig>();
38+
if (smtpConfig is null)
39+
{
40+
log.LogWarning("SMTP is not configured, please configure SMTP to enable sending emails");
41+
}
42+
else
43+
{
44+
log.LogWarning("SMTP is configured with <{FromEmail}> {FromName}", smtpConfig.FromEmail, smtpConfig.FromName);
45+
}
46+
}
47+
48+
var jobs = services.GetRequiredService<IBackgroundJobs>();
49+
// Example of registering a Recurring Job to run Every Hour
50+
//jobs.RecurringCommand<MyCommand>(Schedule.Hourly);
51+
});
52+
}
53+
54+
public class JobsHostedService(ILogger<JobsHostedService> log, IBackgroundJobs jobs) : BackgroundService
55+
{
56+
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
57+
{
58+
await jobs.StartAsync(stoppingToken);
59+
60+
using var timer = new PeriodicTimer(TimeSpan.FromSeconds(3));
61+
while (!stoppingToken.IsCancellationRequested && await timer.WaitForNextTickAsync(stoppingToken))
62+
{
63+
await jobs.TickAsync();
64+
}
65+
}
66+
}
67+
68+
/// <summary>
69+
/// Sends emails by executing SendEmailCommand in a background job where it's serially processed by 'smtp' worker
70+
/// </summary>
71+
public class EmailSender(IBackgroundJobs jobs) : IEmailSender<ApplicationUser>
72+
{
73+
public Task SendEmailAsync(string email, string subject, string htmlMessage)
74+
{
75+
jobs.EnqueueCommand<SendEmailCommand>(new SendEmail {
76+
To = email,
77+
Subject = subject,
78+
BodyHtml = htmlMessage,
79+
});
80+
return Task.CompletedTask;
81+
}
82+
83+
public Task SendConfirmationLinkAsync(ApplicationUser user, string email, string confirmationLink) =>
84+
SendEmailAsync(email, "Confirm your email", $"Please confirm your account by <a href='{confirmationLink}'>clicking here</a>.");
85+
86+
public Task SendPasswordResetLinkAsync(ApplicationUser user, string email, string resetLink) =>
87+
SendEmailAsync(email, "Reset your password", $"Please reset your password by <a href='{resetLink}'>clicking here</a>.");
88+
89+
public Task SendPasswordResetCodeAsync(ApplicationUser user, string email, string resetCode) =>
90+
SendEmailAsync(email, "Reset your password", $"Please reset your password using the following code: {resetCode}");
91+
}

MyApp/Configure.Mq.cs

-79
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
using Microsoft.AspNetCore.Identity;
2+
using Microsoft.AspNetCore.Identity.UI.Services;
3+
using MyApp.Data;
4+
5+
namespace MyApp;
6+
7+
// Remove the "else if (EmailSender is IdentityNoOpEmailSender)" block from RegisterConfirmation.razor after updating with a real implementation.
8+
internal sealed class IdentityNoOpEmailSender : IEmailSender<ApplicationUser>
9+
{
10+
private readonly IEmailSender emailSender = new NoOpEmailSender();
11+
12+
public Task SendConfirmationLinkAsync(ApplicationUser user, string email, string confirmationLink) =>
13+
emailSender.SendEmailAsync(email, "Confirm your email", $"Please confirm your account by <a href='{confirmationLink}'>clicking here</a>.");
14+
15+
public Task SendPasswordResetLinkAsync(ApplicationUser user, string email, string resetLink) =>
16+
emailSender.SendEmailAsync(email, "Reset your password", $"Please reset your password by <a href='{resetLink}'>clicking here</a>.");
17+
18+
public Task SendPasswordResetCodeAsync(ApplicationUser user, string email, string resetCode) =>
19+
emailSender.SendEmailAsync(email, "Reset your password", $"Please reset your password using the following code: {resetCode}");
20+
}

MyApp/MyApp.csproj

+1
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
<PackageReference Include="ServiceStack.Mvc" Version="8.*" />
4040
<PackageReference Include="ServiceStack.Server" Version="8.*" />
4141
<PackageReference Include="ServiceStack.Extensions" Version="8.*" />
42+
<PackageReference Include="ServiceStack.Jobs" Version="8.*" />
4243
<PackageReference Include="ServiceStack.OrmLite.Sqlite.Data" Version="8.*" />
4344
</ItemGroup>
4445

MyApp/Program.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@
8585

8686
services.AddSingleton<IEmailSender<ApplicationUser>, IdentityNoOpEmailSender>();
8787
// Uncomment to send emails with SMTP, configure SMTP with "SmtpConfig" in appsettings.json
88-
//services.AddSingleton<IEmailSender<ApplicationUser>, EmailSender>();
88+
// services.AddSingleton<IEmailSender<ApplicationUser>, EmailSender>();
8989
services.AddScoped<IUserClaimsPrincipalFactory<ApplicationUser>, AdditionalUserClaimsPrincipalFactory>();
9090

9191
// Register all services

0 commit comments

Comments
 (0)