Add support for email attachments
This commit is contained in:
parent
618f4e837c
commit
97a9619fad
|
|
@ -0,0 +1,5 @@
|
|||
// ReSharper disable once CheckNamespace
|
||||
namespace Elsa.Activities.Email
|
||||
{
|
||||
public record EmailAttachment(byte[] Content, string? FileName, string? ContentType);
|
||||
}
|
||||
|
|
@ -1,5 +1,11 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Elsa.Activities.Email.Options;
|
||||
using Elsa.Activities.Email.Services;
|
||||
|
|
@ -7,11 +13,11 @@ using Elsa.ActivityResults;
|
|||
using Elsa.Attributes;
|
||||
using Elsa.Design;
|
||||
using Elsa.Expressions;
|
||||
using Elsa.Serialization;
|
||||
using Elsa.Services;
|
||||
using Elsa.Services.Models;
|
||||
using Microsoft.Extensions.Options;
|
||||
using MimeKit;
|
||||
using MimeKit.Text;
|
||||
|
||||
// ReSharper disable once CheckNamespace
|
||||
namespace Elsa.Activities.Email
|
||||
|
|
@ -20,11 +26,15 @@ namespace Elsa.Activities.Email
|
|||
public class SendEmail : Activity
|
||||
{
|
||||
private readonly ISmtpService _smtpService;
|
||||
private readonly IHttpClientFactory _httpClientFactory;
|
||||
private readonly IContentSerializer _contentSerializer;
|
||||
private readonly SmtpOptions _options;
|
||||
|
||||
public SendEmail(ISmtpService smtpService, IOptions<SmtpOptions> options)
|
||||
public SendEmail(ISmtpService smtpService, IOptions<SmtpOptions> options, IHttpClientFactory httpClientFactory, IContentSerializer contentSerializer)
|
||||
{
|
||||
_smtpService = smtpService;
|
||||
_httpClientFactory = httpClientFactory;
|
||||
_contentSerializer = contentSerializer;
|
||||
_options = options.Value;
|
||||
}
|
||||
|
||||
|
|
@ -35,17 +45,17 @@ namespace Elsa.Activities.Email
|
|||
public ICollection<string> To { get; set; } = new List<string>();
|
||||
|
||||
[ActivityInput(
|
||||
Hint = "The cc recipients email addresses.",
|
||||
UIHint = ActivityInputUIHints.MultiText,
|
||||
DefaultSyntax = SyntaxNames.Json,
|
||||
Hint = "The cc recipients email addresses.",
|
||||
UIHint = ActivityInputUIHints.MultiText,
|
||||
DefaultSyntax = SyntaxNames.Json,
|
||||
SupportedSyntaxes = new[] { SyntaxNames.Json, SyntaxNames.JavaScript },
|
||||
Category = "More")]
|
||||
public ICollection<string> Cc { get; set; } = new List<string>();
|
||||
|
||||
[ActivityInput(
|
||||
Hint = "The Bcc recipients email addresses.",
|
||||
UIHint = ActivityInputUIHints.MultiText,
|
||||
DefaultSyntax = SyntaxNames.Json,
|
||||
Hint = "The Bcc recipients email addresses.",
|
||||
UIHint = ActivityInputUIHints.MultiText,
|
||||
DefaultSyntax = SyntaxNames.Json,
|
||||
SupportedSyntaxes = new[] { SyntaxNames.Json, SyntaxNames.JavaScript },
|
||||
Category = "More")]
|
||||
public ICollection<string> Bcc { get; set; } = new List<string>();
|
||||
|
|
@ -53,21 +63,29 @@ namespace Elsa.Activities.Email
|
|||
[ActivityInput(Hint = "The subject of the email message.", SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
|
||||
public string? Subject { get; set; }
|
||||
|
||||
[ActivityInput(
|
||||
Hint = "The attachments to send with the email message. Can be (an array of) a fully-qualified file path, URL, stream, byte array or instances of EmailAttachment.",
|
||||
UIHint = ActivityInputUIHints.MultiLine,
|
||||
SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid }
|
||||
)]
|
||||
public object? Attachments { get; set; }
|
||||
|
||||
[ActivityInput(Hint = "The body of the email message.", UIHint = ActivityInputUIHints.MultiLine, SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
|
||||
public string? Body { get; set; }
|
||||
|
||||
protected override async ValueTask<IActivityExecutionResult> OnExecuteAsync(ActivityExecutionContext context)
|
||||
{
|
||||
var cancellationToken = context.CancellationToken;
|
||||
var message = new MimeMessage();
|
||||
var from = From is null or "" ? _options.DefaultSender : From;
|
||||
var from = string.IsNullOrWhiteSpace(From) ? _options.DefaultSender : From;
|
||||
|
||||
message.From.Add(MailboxAddress.Parse(from));
|
||||
message.Subject = Subject;
|
||||
|
||||
message.Body = new TextPart(TextFormat.Html)
|
||||
{
|
||||
Text = Body
|
||||
};
|
||||
var bodyBuilder = new BodyBuilder { HtmlBody = Body };
|
||||
await AddAttachmentsAsync(bodyBuilder, cancellationToken);
|
||||
|
||||
message.Body = bodyBuilder.ToMessageBody();
|
||||
|
||||
SetRecipientsEmailAddresses(message.To, To);
|
||||
SetRecipientsEmailAddresses(message.Cc, Cc);
|
||||
|
|
@ -78,12 +96,74 @@ namespace Elsa.Activities.Email
|
|||
return Done();
|
||||
}
|
||||
|
||||
private async Task AddAttachmentsAsync(BodyBuilder bodyBuilder, CancellationToken cancellationToken)
|
||||
{
|
||||
var attachments = Attachments;
|
||||
|
||||
if (attachments != null)
|
||||
{
|
||||
var index = 0;
|
||||
var attachmentObjects = InterpretAttachmentsModel(attachments);
|
||||
|
||||
foreach (var attachmentObject in attachmentObjects)
|
||||
{
|
||||
switch (attachmentObject)
|
||||
{
|
||||
case Uri url:
|
||||
await AttachOnlineFileAsync(bodyBuilder, url, cancellationToken);
|
||||
break;
|
||||
case string path when path.Contains("://"):
|
||||
await AttachOnlineFileAsync(bodyBuilder, new Uri(path), cancellationToken);
|
||||
break;
|
||||
case string path:
|
||||
await AttachLocalFileAsync(bodyBuilder, path, cancellationToken);
|
||||
break;
|
||||
case EmailAttachment emailAttachment:
|
||||
{
|
||||
var fileName = emailAttachment.FileName ?? $"Attachment-{++index}";
|
||||
var contentType = emailAttachment.ContentType ?? "application/binary";
|
||||
bodyBuilder.Attachments.Add(fileName, emailAttachment.Content, ContentType.Parse(contentType));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
var json = _contentSerializer.Serialize(attachmentObject);
|
||||
var fileName = $"Attachment-{++index}";
|
||||
var contentType = "application/json";
|
||||
bodyBuilder.Attachments.Add(fileName, Encoding.UTF8.GetBytes(json), ContentType.Parse(contentType));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task AttachLocalFileAsync(BodyBuilder bodyBuilder, string path, CancellationToken cancellationToken) => await bodyBuilder.Attachments.AddAsync(path, cancellationToken);
|
||||
|
||||
private async Task AttachOnlineFileAsync(BodyBuilder bodyBuilder, Uri url, CancellationToken cancellationToken)
|
||||
{
|
||||
var fileName = Path.GetFileName(url.LocalPath);
|
||||
var response = await DownloadUrlAsync(url);
|
||||
var contentStream = await response.Content.ReadAsStreamAsync();
|
||||
var contentType = response.Content.Headers.ContentType.MediaType;
|
||||
await bodyBuilder.Attachments.AddAsync(fileName, contentStream, ContentType.Parse(contentType), cancellationToken);
|
||||
}
|
||||
|
||||
private IEnumerable InterpretAttachmentsModel(object attachments) => attachments is string text ? new[] { text } : attachments is IEnumerable enumerable ? enumerable : new[] { attachments };
|
||||
|
||||
private void SetRecipientsEmailAddresses(InternetAddressList list, IEnumerable<string>? addresses)
|
||||
{
|
||||
if(addresses == null)
|
||||
if (addresses == null)
|
||||
return;
|
||||
|
||||
|
||||
list.AddRange(addresses.Select(MailboxAddress.Parse));
|
||||
}
|
||||
|
||||
private async Task<HttpResponseMessage> DownloadUrlAsync(Uri url)
|
||||
{
|
||||
using var httpClient = _httpClientFactory.CreateClient();
|
||||
var response = await httpClient.GetAsync(url);
|
||||
return response;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -16,10 +16,12 @@
|
|||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\core\Elsa.Core\Elsa.Core.csproj" />
|
||||
<ProjectReference Include="..\..\scripting\Elsa.Scripting.JavaScript\Elsa.Scripting.JavaScript.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="MailKit" Version="2.12.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="5.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
using System;
|
||||
using Elsa;
|
||||
using Elsa.Activities.Email;
|
||||
using Elsa.Activities.Email.Handlers;
|
||||
using Elsa.Activities.Email.Options;
|
||||
using Elsa.Activities.Email.Services;
|
||||
|
||||
|
|
@ -12,6 +13,9 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||
public static ElsaOptionsBuilder AddEmailActivities(this ElsaOptionsBuilder options, Action<SmtpOptions>? configureOptions = null)
|
||||
{
|
||||
options.Services.AddEmailServices(configureOptions);
|
||||
options.Services.AddNotificationHandlersFrom<ConfigureJavaScriptEngine>();
|
||||
options.Services.AddJavaScriptTypeDefinitionProvider<EmailTypeDefinitionProvider>();
|
||||
options.Services.AddHttpClient();
|
||||
options.AddEmailActivitiesInternal();
|
||||
return options;
|
||||
}
|
||||
|
|
@ -21,7 +25,7 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||
if (configureOptions != null)
|
||||
services.Configure(configureOptions);
|
||||
|
||||
return services.AddSingleton<ISmtpService, SmtpService>();
|
||||
return services.AddSingleton<ISmtpService, MailKitSmtpService>();
|
||||
}
|
||||
|
||||
private static ElsaOptionsBuilder AddEmailActivitiesInternal(this ElsaOptionsBuilder services) => services.AddActivity<SendEmail>();
|
||||
|
|
|
|||
|
|
@ -0,0 +1,34 @@
|
|||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Elsa.Scripting.JavaScript.Extensions;
|
||||
using Elsa.Scripting.JavaScript.Messages;
|
||||
using Elsa.Services;
|
||||
using Elsa.Services.WorkflowStorage;
|
||||
using MediatR;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using NodaTime;
|
||||
|
||||
namespace Elsa.Activities.Email.Handlers
|
||||
{
|
||||
public class ConfigureJavaScriptEngine : INotificationHandler<EvaluatingJavaScriptExpression>
|
||||
{
|
||||
private readonly IConfiguration _configuration;
|
||||
private readonly IActivityTypeService _activityTypeService;
|
||||
private readonly IWorkflowStorageService _workflowStorageService;
|
||||
|
||||
public ConfigureJavaScriptEngine(IConfiguration configuration, IActivityTypeService activityTypeService, IWorkflowStorageService workflowStorageService)
|
||||
{
|
||||
_configuration = configuration;
|
||||
_activityTypeService = activityTypeService;
|
||||
_workflowStorageService = workflowStorageService;
|
||||
}
|
||||
|
||||
public Task Handle(EvaluatingJavaScriptExpression notification, CancellationToken cancellationToken)
|
||||
{
|
||||
var engine = notification.Engine;
|
||||
|
||||
engine.RegisterType<EmailAttachment>();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Elsa.Scripting.JavaScript.Services;
|
||||
|
||||
namespace Elsa.Activities.Email.Handlers
|
||||
{
|
||||
public class EmailTypeDefinitionProvider : TypeDefinitionProvider
|
||||
{
|
||||
public override IEnumerable<Type> CollectTypes(TypeDefinitionContext context)
|
||||
{
|
||||
return new[] { typeof(EmailAttachment) };
|
||||
}
|
||||
}
|
||||
}
|
||||
17
src/activities/Elsa.Activities.Email/IsExternalInit.cs
Normal file
17
src/activities/Elsa.Activities.Email/IsExternalInit.cs
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
using System.ComponentModel;
|
||||
|
||||
// ReSharper disable once CheckNamespace
|
||||
namespace System.Runtime.CompilerServices
|
||||
{
|
||||
/// <summary>
|
||||
/// Reserved to be used by the compiler for tracking metadata.
|
||||
/// This class should not be used by developers in source code.
|
||||
/// </summary>
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
internal static class IsExternalInit
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -12,15 +12,15 @@ using MimeKit;
|
|||
|
||||
namespace Elsa.Activities.Email.Services
|
||||
{
|
||||
public class SmtpService : ISmtpService
|
||||
public class MailKitSmtpService : ISmtpService
|
||||
{
|
||||
private readonly SmtpOptions _options;
|
||||
private readonly ILogger<SmtpService> _logger;
|
||||
private readonly ILogger<MailKitSmtpService> _logger;
|
||||
private const string EmailExtension = ".eml";
|
||||
|
||||
public SmtpService(
|
||||
public MailKitSmtpService(
|
||||
IOptions<SmtpOptions> options,
|
||||
ILogger<SmtpService> logger
|
||||
ILogger<MailKitSmtpService> logger
|
||||
)
|
||||
{
|
||||
_options = options.Value;
|
||||
|
|
@ -15,6 +15,8 @@ namespace Elsa
|
|||
{
|
||||
return services.AddTransient(typeof(INotificationHandler<T>), typeof(THandler));
|
||||
}
|
||||
|
||||
public static IServiceCollection AddNotificationHandlersFrom<TMarker>(this IServiceCollection services) => services.AddNotificationHandlers(typeof(TMarker));
|
||||
|
||||
public static IServiceCollection AddNotificationHandlers(this IServiceCollection services, params Type[] markerTypes)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ namespace Elsa.Samples.SendHttp
|
|||
.WithReadContent(true))
|
||||
.WithName("TestHttpRequest")
|
||||
.WriteHttpResponse(setup => setup.WithStatusCode(System.Net.HttpStatusCode.OK)
|
||||
.WithContent(async context => JsonSerializer.Serialize((await context.GetNamedActivityPropertyAsync<SendHttpRequest, HttpResponseModel>("TestHttpRequest", x => x.Output))!.Content)));
|
||||
.WithContent(async context => JsonSerializer.Serialize(await context.GetNamedActivityPropertyAsync<SendHttpRequest, object>("TestHttpRequest", x => x.ResponseContent))));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,10 @@
|
|||
using Jint;
|
||||
using Jint.Runtime.Interop;
|
||||
|
||||
namespace Elsa.Scripting.JavaScript.Extensions
|
||||
{
|
||||
public static class EngineExtensions
|
||||
{
|
||||
public static void RegisterType<T>(this Engine engine) => engine.SetValue(typeof(T).Name, TypeReference.CreateTypeReference(engine, typeof(T)));
|
||||
}
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@ using System.Linq;
|
|||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Elsa.Providers.WorkflowStorage;
|
||||
using Elsa.Scripting.JavaScript.Extensions;
|
||||
using Elsa.Scripting.JavaScript.Messages;
|
||||
using Elsa.Services;
|
||||
using Elsa.Services.Models;
|
||||
|
|
@ -65,15 +66,15 @@ namespace Elsa.Scripting.JavaScript.Handlers
|
|||
engine.SetValue("workflowContext", activityExecutionContext.GetWorkflowContext());
|
||||
|
||||
// Types.
|
||||
RegisterType<Instant>(engine);
|
||||
RegisterType<Duration>(engine);
|
||||
RegisterType<Period>(engine);
|
||||
RegisterType<LocalDate>(engine);
|
||||
RegisterType<LocalTime>(engine);
|
||||
RegisterType<LocalDateTime>(engine);
|
||||
RegisterType<Guid>(engine);
|
||||
RegisterType<WorkflowExecutionContext>(engine);
|
||||
RegisterType<ActivityExecutionContext>(engine);
|
||||
engine.RegisterType<Instant>();
|
||||
engine.RegisterType<Duration>();
|
||||
engine.RegisterType<Period>();
|
||||
engine.RegisterType<LocalDate>();
|
||||
engine.RegisterType<LocalTime>();
|
||||
engine.RegisterType<LocalDateTime>();
|
||||
engine.RegisterType<Guid>();
|
||||
engine.RegisterType<WorkflowExecutionContext>();
|
||||
engine.RegisterType<ActivityExecutionContext>();
|
||||
|
||||
// Workflow variables.
|
||||
var variables = workflowExecutionContext.GetMergedVariables();
|
||||
|
|
@ -157,7 +158,5 @@ namespace Elsa.Scripting.JavaScript.Handlers
|
|||
var storageContext = new WorkflowStorageContext(context.WorkflowInstance, activityBlueprint.Id);
|
||||
return await storageService.LoadAsync(providerName, storageContext, propertyName, context.CancellationToken);
|
||||
}
|
||||
|
||||
private void RegisterType<T>(Engine engine) => engine.SetValue(typeof(T).Name, TypeReference.CreateTypeReference(engine, typeof(T)));
|
||||
}
|
||||
}
|
||||
|
|
@ -9,6 +9,9 @@ namespace Elsa.Scripting.JavaScript.Services
|
|||
|
||||
public object? ConvertToDesiredType(object? evaluationResult, Type desiredType)
|
||||
{
|
||||
if (desiredType == typeof(object))
|
||||
return evaluationResult;
|
||||
|
||||
var converter = TypeDescriptor.GetConverter(evaluationResult!);
|
||||
|
||||
if (converter.CanConvertTo(desiredType))
|
||||
|
|
|
|||
Loading…
Reference in a new issue