🧱 Render email based on razor components
This commit is contained in:
		
							
								
								
									
										80
									
								
								DysonNetwork.Sphere/Account/Email/EmailService.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										80
									
								
								DysonNetwork.Sphere/Account/Email/EmailService.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,80 @@ | ||||
| using MailKit.Net.Smtp; | ||||
| using Microsoft.AspNetCore.Components; | ||||
| using MimeKit; | ||||
|  | ||||
| namespace DysonNetwork.Sphere.Account.Email; | ||||
|  | ||||
| public class EmailServiceConfiguration | ||||
| { | ||||
|     public string Server { get; set; } = null!; | ||||
|     public int Port { get; set; } | ||||
|     public bool UseSsl { get; set; } | ||||
|     public string Username { get; set; } = null!; | ||||
|     public string Password { get; set; } = null!; | ||||
|     public string FromAddress { get; set; } = null!; | ||||
|     public string FromName { get; set; } = null!; | ||||
|     public string SubjectPrefix { get; set; } = null!; | ||||
| } | ||||
|  | ||||
| public class EmailService | ||||
| { | ||||
|     private readonly EmailServiceConfiguration _configuration; | ||||
|     private readonly RazorViewRenderer _viewRenderer; | ||||
|     private readonly ILogger<EmailService> _logger; | ||||
|  | ||||
|     public EmailService(IConfiguration configuration, RazorViewRenderer viewRenderer, ILogger<EmailService> logger) | ||||
|     { | ||||
|         var cfg = configuration.GetSection("Email").Get<EmailServiceConfiguration>(); | ||||
|         _configuration = cfg ?? throw new ArgumentException("Email service was not configured."); | ||||
|         _viewRenderer = viewRenderer; | ||||
|         _logger = logger; | ||||
|     } | ||||
|  | ||||
|     public async Task SendEmailAsync(string? recipientName, string recipientEmail, string subject, string textBody) | ||||
|     { | ||||
|         await SendEmailAsync(recipientName, recipientEmail, subject, textBody, null); | ||||
|     } | ||||
|  | ||||
|     public async Task SendEmailAsync(string? recipientName, string recipientEmail, string subject, string textBody, | ||||
|         string? htmlBody) | ||||
|     { | ||||
|         subject = $"[{_configuration.SubjectPrefix}] {subject}"; | ||||
|  | ||||
|         var emailMessage = new MimeMessage(); | ||||
|         emailMessage.From.Add(new MailboxAddress(_configuration.FromName, _configuration.FromAddress)); | ||||
|         emailMessage.To.Add(new MailboxAddress(recipientName, recipientEmail)); | ||||
|         emailMessage.Subject = subject; | ||||
|  | ||||
|         var bodyBuilder = new BodyBuilder | ||||
|         { | ||||
|             TextBody = textBody | ||||
|         }; | ||||
|  | ||||
|         if (!string.IsNullOrEmpty(htmlBody)) | ||||
|             bodyBuilder.HtmlBody = htmlBody; | ||||
|  | ||||
|         emailMessage.Body = bodyBuilder.ToMessageBody(); | ||||
|  | ||||
|         using var client = new SmtpClient(); | ||||
|         await client.ConnectAsync(_configuration.Server, _configuration.Port, _configuration.UseSsl); | ||||
|         await client.AuthenticateAsync(_configuration.Username, _configuration.Password); | ||||
|         await client.SendAsync(emailMessage); | ||||
|         await client.DisconnectAsync(true); | ||||
|     } | ||||
|  | ||||
|     public async Task SendTemplatedEmailAsync<TComponent, TModel>(string? recipientName, string recipientEmail, | ||||
|         string subject, TModel model, string fallbackTextBody) | ||||
|         where TComponent : IComponent | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             var htmlBody = await _viewRenderer.RenderComponentToStringAsync<TComponent, TModel>(model); | ||||
|             await SendEmailAsync(recipientName, recipientEmail, subject, fallbackTextBody, htmlBody); | ||||
|         } | ||||
|         catch (Exception err) | ||||
|         { | ||||
|             _logger.LogError(err, "Failed to render email template..."); | ||||
|             await SendEmailAsync(recipientName, recipientEmail, subject, fallbackTextBody); | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										7
									
								
								DysonNetwork.Sphere/Account/Email/LandingEmail.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								DysonNetwork.Sphere/Account/Email/LandingEmail.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | ||||
| namespace DysonNetwork.Sphere.Account.Email; | ||||
|  | ||||
| public class LandingEmailModel | ||||
| { | ||||
|     public required string Name { get; set; } | ||||
|     public required string VerificationLink { get; set; }   | ||||
| } | ||||
							
								
								
									
										47
									
								
								DysonNetwork.Sphere/Account/Email/RazorViewRenderer.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								DysonNetwork.Sphere/Account/Email/RazorViewRenderer.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,47 @@ | ||||
| using Microsoft.AspNetCore.Components; | ||||
| using Microsoft.AspNetCore.Components.Web; | ||||
| using Microsoft.AspNetCore.Mvc; | ||||
| using Microsoft.AspNetCore.Mvc.Abstractions; | ||||
| using Microsoft.AspNetCore.Mvc.ModelBinding; | ||||
| using Microsoft.AspNetCore.Mvc.Razor; | ||||
| using Microsoft.AspNetCore.Mvc.Rendering; | ||||
| using Microsoft.AspNetCore.Mvc.ViewEngines; | ||||
| using Microsoft.AspNetCore.Mvc.ViewFeatures; | ||||
| using RouteData = Microsoft.AspNetCore.Routing.RouteData; | ||||
|  | ||||
| namespace DysonNetwork.Sphere.Account.Email; | ||||
|  | ||||
| public class RazorViewRenderer( | ||||
|     IServiceProvider serviceProvider, | ||||
|     ILoggerFactory loggerFactory, | ||||
|     ILogger<RazorViewRenderer> logger | ||||
| ) | ||||
| { | ||||
|     public async Task<string> RenderComponentToStringAsync<TComponent, TModel>(TModel model)  | ||||
|         where TComponent : IComponent | ||||
|     { | ||||
|         await using var htmlRenderer = new HtmlRenderer(serviceProvider, loggerFactory); | ||||
|          | ||||
|         var viewDictionary = new ViewDataDictionary<TModel>( | ||||
|             new EmptyModelMetadataProvider(), | ||||
|             new ModelStateDictionary()) | ||||
|         { | ||||
|             Model = model | ||||
|         }; | ||||
|          | ||||
|         return await htmlRenderer.Dispatcher.InvokeAsync(async () => | ||||
|         { | ||||
|             try  | ||||
|             { | ||||
|                 var parameterView = ParameterView.FromDictionary(viewDictionary); | ||||
|                 var output = await htmlRenderer.RenderComponentAsync<TComponent>(parameterView); | ||||
|                 return output.ToHtmlString(); | ||||
|             } | ||||
|             catch (Exception ex) | ||||
|             { | ||||
|                 logger.LogError(ex, "Error rendering component {ComponentName}", typeof(TComponent).Name); | ||||
|                 throw; | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user