mail module
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
using Raven.Client.Documents.Linq;
|
||||
using System.Linq.Expressions;
|
||||
|
||||
namespace zero.Raven;
|
||||
|
||||
@@ -15,6 +16,10 @@ public static class RavenOperationsExtensions
|
||||
/// </summary>
|
||||
public static async Task<Result<T>> Delete<T>(this IRavenOperations ops, string id) where T : ZeroIdEntity, new() => await ops.Delete(await ops.Load<T>(id));
|
||||
|
||||
/// <summary>
|
||||
/// Deletes entities by selector
|
||||
/// </summary>
|
||||
public static async Task<int> Delete<T>(this IRavenOperations ops, Expression<Func<T, bool>> predicate) where T : ZeroIdEntity, new() => await ops.Delete(await ops.Load<T>(predicate));
|
||||
|
||||
/// <summary>
|
||||
/// Deletes entities by Id
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
//using System.IO;
|
||||
//using System.Text;
|
||||
|
||||
//namespace zero.Mails;
|
||||
|
||||
///// <summary>
|
||||
///// Default implementation of an IMailSender which sends the mail a flat file
|
||||
///// and therefore not using the SMTP channel.
|
||||
///// Implementing real mail sending is up to the consuming application.
|
||||
///// </summary>
|
||||
//public class FileMailDispatcher : IMailDispatcher
|
||||
//{
|
||||
// protected Queue<Mail> Queue { get; private set; } = new Queue<Mail>();
|
||||
|
||||
// protected IPaths PathResolver { get; private set; }
|
||||
|
||||
// string MailDirectory;
|
||||
|
||||
|
||||
// public FileMailDispatcher(IPaths pathResolver)
|
||||
// {
|
||||
// PathResolver = pathResolver;
|
||||
// MailDirectory = "mails";
|
||||
// }
|
||||
|
||||
|
||||
// /// <inheritdoc />
|
||||
// public void Enqueue(Mail message)
|
||||
// {
|
||||
// Queue.Enqueue(message);
|
||||
// }
|
||||
|
||||
|
||||
// /// <inheritdoc />
|
||||
// public async Task Send(CancellationToken token = default)
|
||||
// {
|
||||
// if (Queue.Count < 1)
|
||||
// {
|
||||
// return;
|
||||
// }
|
||||
|
||||
// string folder = PathResolver.Map(MailDirectory);
|
||||
// PathResolver.Create(folder);
|
||||
|
||||
// while (Queue.Count > 0)
|
||||
// {
|
||||
// Mail message = Queue.Dequeue();
|
||||
// string content = JsonConvert.SerializeObject(message, new JsonSerializerSettings()
|
||||
// {
|
||||
// Formatting = Formatting.Indented,
|
||||
// TypeNameHandling = TypeNameHandling.None,
|
||||
// ReferenceLoopHandling = ReferenceLoopHandling.Ignore
|
||||
// });
|
||||
|
||||
// string filename = Safenames.File(DateTimeOffset.Now.ToString("yyyy-MM-dd-HH-mm-ss") + "_" + message.To[0].Address + ".txt");
|
||||
// string path = PathResolver.Map(folder, filename);
|
||||
|
||||
// await File.WriteAllTextAsync(path, content, token);
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
// /// <inheritdoc />
|
||||
// public void Dispose() { }
|
||||
|
||||
|
||||
// /// <summary>
|
||||
// /// Creats the file content from a mail message
|
||||
// /// </summary>
|
||||
// string CreateContent(Mail message)
|
||||
// {
|
||||
// StringBuilder text = new();
|
||||
// text.AppendLine("To: " + message.To);
|
||||
// text.AppendLine("CC: " + message.CC);
|
||||
// text.AppendLine("Bcc: " + message.Bcc);
|
||||
// text.AppendLine("From: " + message.From);
|
||||
// text.AppendLine("Subject: " + message.Subject);
|
||||
// text.Append("Date: " + DateTimeOffset.UtcNow.ToString());
|
||||
|
||||
// text.AppendLine();
|
||||
// text.AppendLine("=============== BODY ===============");
|
||||
// text.AppendLine();
|
||||
|
||||
// text.AppendLine(message.Body);
|
||||
|
||||
// return text.ToString();
|
||||
// }
|
||||
//}
|
||||
@@ -0,0 +1,19 @@
|
||||
namespace zero.Mails;
|
||||
|
||||
public interface IMailDispatcher : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds a new mail message to the outgoing queue
|
||||
/// </summary>
|
||||
void Enqueue(Mail message);
|
||||
|
||||
/// <summary>
|
||||
/// Sends all mails which have been added to the queue previously
|
||||
/// </summary>
|
||||
Task Send(CancellationToken token = default);
|
||||
|
||||
/// <summary>
|
||||
/// Whether a certain sender signature is supported by this dispatcher
|
||||
/// </summary>
|
||||
Task<bool> IsSenderSupported(string email) => Task.FromResult(true);
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace zero.Mails;
|
||||
|
||||
/// <summary>
|
||||
/// Default implementation of an IMailSender which sends the mail to the attached logger
|
||||
/// and therefore not using the SMTP channel.
|
||||
/// Implementing real mail sending is up to the consuming application.
|
||||
/// </summary>
|
||||
public class LoggerMailDispatcher : IMailDispatcher
|
||||
{
|
||||
protected Queue<Mail> Queue { get; private set; } = new Queue<Mail>();
|
||||
|
||||
protected ILogger<LoggerMailDispatcher> Logger { get; set; }
|
||||
|
||||
|
||||
public LoggerMailDispatcher(ILogger<LoggerMailDispatcher> logger)
|
||||
{
|
||||
Logger = logger;
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Enqueue(Mail message)
|
||||
{
|
||||
Queue.Enqueue(message);
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task Send(CancellationToken token = default)
|
||||
{
|
||||
while (Queue.Count > 0)
|
||||
{
|
||||
Mail message = Queue.Dequeue();
|
||||
Logger.LogInformation("Mail to {to}. Subject: {subject}", message.To[0].Address, message.Subject);
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose() { }
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
using System.Net.Mail;
|
||||
|
||||
namespace zero.Mails;
|
||||
|
||||
public class Mail<T> : Mail where T : class
|
||||
{
|
||||
public T Model { get; set; }
|
||||
}
|
||||
|
||||
public class Mail : MailMessage
|
||||
{
|
||||
public bool IsDeactivated { get; set; }
|
||||
|
||||
public string ViewKey { get; set; }
|
||||
|
||||
public string ViewPath { get; set; }
|
||||
|
||||
public string Tag { get; set; }
|
||||
|
||||
public bool HasView { get; set; } = true;
|
||||
|
||||
public bool IsRendered { get; set; }
|
||||
|
||||
public string Preheader { get; set; }
|
||||
|
||||
public MailPlaceholders Placeholders { get; set; } = new();
|
||||
|
||||
public MailMetadata Metadata { get; set; } = new();
|
||||
}
|
||||
|
||||
public class MailMetadata : Dictionary<string, string>
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public class MailPlaceholders : Dictionary<string, string>
|
||||
{
|
||||
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace zero.Mails;
|
||||
|
||||
public class MailOptions
|
||||
{
|
||||
public string SenderEmail { get; set; }
|
||||
|
||||
public string SenderName { get; set; }
|
||||
|
||||
public Func<Mail, string> BuildViewPath { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System.Net.Mail;
|
||||
using System.Text;
|
||||
|
||||
namespace zero.Mails;
|
||||
|
||||
public class MailProvider : IMailProvider
|
||||
{
|
||||
protected ILogger<IMailProvider> Logger { get; set; }
|
||||
|
||||
protected IZeroContext Zero { get; set; }
|
||||
|
||||
protected IMailDispatcher MailSender { get; set; }
|
||||
|
||||
protected IRazorRenderer Renderer { get; set; }
|
||||
|
||||
protected MailOptions Options { get; set; }
|
||||
|
||||
private Encoding encoding = Encoding.UTF8;
|
||||
|
||||
|
||||
public MailProvider(IZeroContext zero, ILogger<IMailProvider> logger, IMailDispatcher mailSender, IRazorRenderer renderer)
|
||||
{
|
||||
Zero = zero;
|
||||
Logger = logger;
|
||||
MailSender = mailSender;
|
||||
Renderer = renderer;
|
||||
Options = zero.Options.For<MailOptions>();
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual async Task Send(Mail message, CancellationToken token = default)
|
||||
{
|
||||
await Send(message, MailSender, token);
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual async Task Send(Mail message, IMailDispatcher dispatcher, CancellationToken token = default)
|
||||
{
|
||||
if (message.IsDeactivated)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await Prepare(message);
|
||||
dispatcher.Enqueue(message);
|
||||
await dispatcher.Send(token);
|
||||
|
||||
Logger.LogInformation("Dispatched email to {recipient} (cc: {cc}, bcc: {bcc})", message.To, message.CC, message.Bcc);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, "Failed to send mail message");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual async Task<string> Prepare(Mail message)
|
||||
{
|
||||
message.From ??= new MailAddress(Options.SenderEmail, Options.SenderName, encoding);
|
||||
message.Sender ??= message.From;
|
||||
|
||||
if (message.ReplyToList.Count < 1)
|
||||
{
|
||||
message.ReplyToList.Add(message.From);
|
||||
}
|
||||
|
||||
message.Subject = TokenReplacement.Apply(message.Subject, message.Placeholders);
|
||||
message.SubjectEncoding = encoding;
|
||||
message.Body = TokenReplacement.Apply(message.Body, message.Placeholders);
|
||||
message.BodyEncoding = encoding;
|
||||
message.Preheader = TokenReplacement.Apply(message.Preheader, message.Placeholders);
|
||||
|
||||
if (!message.HasView || message.Body.HasValue())
|
||||
{
|
||||
message.IsRendered = true;
|
||||
return message.Body;
|
||||
}
|
||||
|
||||
string viewPath = message.ViewPath;
|
||||
|
||||
if (viewPath.IsNullOrEmpty())
|
||||
{
|
||||
viewPath = Options.BuildViewPath(message);
|
||||
}
|
||||
|
||||
message.Body = await Renderer.ViewAsync(viewPath, message);
|
||||
message.IsBodyHtml = true;
|
||||
message.IsRendered = true;
|
||||
return message.Body;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public interface IMailProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// Renders the message body.
|
||||
/// This is automatically called when sending messages.
|
||||
/// </summary>
|
||||
Task<string> Prepare(Mail message);
|
||||
|
||||
/// <summary>
|
||||
/// Sends a message with the default dispatcher
|
||||
/// </summary>
|
||||
Task Send(Mail message, CancellationToken token = default);
|
||||
|
||||
/// <summary>
|
||||
/// Sends a message with the specified dispatcher
|
||||
/// </summary>
|
||||
Task Send(Mail message, IMailDispatcher dispatcher, CancellationToken token = default);
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace zero.Mails;
|
||||
|
||||
internal class ZeroMailModule : ZeroModule
|
||||
{
|
||||
public override void ConfigureServices(IServiceCollection services, IConfiguration configuration)
|
||||
{
|
||||
services.AddScoped<IMailProvider, MailProvider>();
|
||||
services.AddScoped<IMailDispatcher, LoggerMailDispatcher>();
|
||||
|
||||
services.AddOptions<MailOptions>().Bind(configuration.GetSection("Zero:Mails")).Configure(opts =>
|
||||
{
|
||||
opts.BuildViewPath = mail => $"~/Mails/{mail.ViewKey.Replace('.', '/')}.cshtml";
|
||||
});
|
||||
}
|
||||
}
|
||||
+2
-1
@@ -3,6 +3,7 @@ using Microsoft.AspNetCore.Antiforgery;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using zero.Mails;
|
||||
using zero.Mvc;
|
||||
using zero.Numbers;
|
||||
using zero.Routing;
|
||||
@@ -52,7 +53,7 @@ public class ZeroBuilder
|
||||
Modules.Add<ZeroFileStorageModule>();
|
||||
//Modules.Add<ZeroIdentityModule>();
|
||||
Modules.Add<ZeroLocalizationModule>();
|
||||
//Modules.Add<ZeroMailModule>();
|
||||
Modules.Add<ZeroMailModule>();
|
||||
//Modules.Add<ZeroMapperModule>();
|
||||
Modules.Add<ZeroMediaModule>();
|
||||
//Modules.Add<ZeroPageModule>();
|
||||
|
||||
Reference in New Issue
Block a user