Skip to content

Commit 8c5d595

Browse files
committed
Replace Background MQ with Background Jobs
1 parent 1973e8f commit 8c5d595

File tree

7 files changed

+131
-105
lines changed

7 files changed

+131
-105
lines changed

MyApp.ServiceInterface/EmailServices.cs

Lines changed: 19 additions & 11 deletions
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

Lines changed: 0 additions & 15 deletions
This file was deleted.
Lines changed: 20 additions & 0 deletions
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.Areas.Identity.Pages.Account;
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/Configure.BackgroundJobs.cs

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

MyApp/Configure.Mq.cs

Lines changed: 0 additions & 79 deletions
This file was deleted.

MyApp/MyApp.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
<PackageReference Include="ServiceStack.Mvc" Version="8.*" />
3737
<PackageReference Include="ServiceStack.Server" Version="8.*" />
3838
<PackageReference Include="ServiceStack.Extensions" Version="8.*" />
39+
<PackageReference Include="ServiceStack.Jobs" Version="8.*" />
3940
<PackageReference Include="ServiceStack.OrmLite.Sqlite.Data" Version="8.*" />
4041
</ItemGroup>
4142

MyApp/Program.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using Microsoft.AspNetCore.HttpOverrides;
22
using Microsoft.AspNetCore.Identity;
33
using Microsoft.EntityFrameworkCore;
4+
using MyApp.Areas.Identity.Pages.Account;
45
using MyApp.ServiceInterface;
56

67
var builder = WebApplication.CreateBuilder(args);

0 commit comments

Comments
 (0)