Merge branch 'v3' into v3-activity-state-tracking-issue

This commit is contained in:
Sipke Schoorstra 2023-10-13 09:28:01 +02:00
commit c042429b60
12 changed files with 103 additions and 58 deletions

View file

@ -4,6 +4,8 @@ on:
push:
branches:
- v3
tags:
- preview-*hotfix-* # e.g. preview-1.0.0-hotfix-1
release:
types: [ prereleased ]
env:
@ -23,7 +25,14 @@ jobs:
git fetch --no-tags --prune --depth=1 origin +refs/heads/*:refs/remotes/origin/*
git branch --remote --contains | grep origin/v3
- name: Set VERSION variable
run: echo "VERSION=3.0.0-preview.${{github.run_number}}" >> $GITHUB_ENV
run: |
if [[ "${{ github.ref }}" == refs/tags/* ]]; then
TAG_NAME=${{ github.ref }} # e.g., refs/tags/preview-740-hotfix-1
TAG_NAME=${TAG_NAME#refs/tags/} # remove the refs/tags/ prefix
echo "VERSION=3.0.0-preview-${TAG_NAME}.${{github.run_number}}" >> $GITHUB_ENV
else
echo "VERSION=3.0.0-preview.${{github.run_number}}" >> $GITHUB_ENV
fi
- name: Build designer package
working-directory: ./src/modules/Elsa.Workflows.Designer
run: |

View file

@ -1,5 +1,6 @@
using System.Net.Http.Headers;
using Elsa.Extensions;
using Elsa.Http.ActivityOptionProviders;
using Elsa.Http.ContentWriters;
using Elsa.Workflows.Core;
using Elsa.Workflows.Core.Attributes;
@ -18,7 +19,7 @@ public abstract class SendHttpRequestBase : Activity<HttpResponseMessage>
protected SendHttpRequestBase(string? source = default, int? line = default) : base(source, line)
{
}
/// <summary>
/// The URL to send the request to.
/// </summary>
@ -47,7 +48,7 @@ public abstract class SendHttpRequestBase : Activity<HttpResponseMessage>
/// </summary>
[Input(
Description = "The content type to use when sending the request.",
Options = new[] { "", "text/plain", "text/html", "application/json", "application/xml", "application/x-www-form-urlencoded" },
OptionsProvider = typeof(HttpContentTypeOptionsProvider),
UIHint = InputUIHints.Dropdown
)]
public Input<string?> ContentType { get; set; } = default!;
@ -89,7 +90,7 @@ public abstract class SendHttpRequestBase : Activity<HttpResponseMessage>
/// Handles an exception that occurred while sending the request.
/// </summary>
protected abstract ValueTask HandleRequestExceptionAsync(ActivityExecutionContext context, HttpRequestException exception);
/// <summary>
/// Handles <see cref="TaskCanceledException"/> that occurred while sending the request.
/// </summary>
@ -111,7 +112,7 @@ public abstract class SendHttpRequestBase : Activity<HttpResponseMessage>
await HandleResponseAsync(context, response);
}
catch(HttpRequestException e)
catch (HttpRequestException e)
{
context.AddExecutionLogEntry("Error", e.Message, payload: new { StackTrace = e.StackTrace });
context.JournalData.Add("Error", e.Message);
@ -165,14 +166,20 @@ public abstract class SendHttpRequestBase : Activity<HttpResponseMessage>
if (contentType != null && content != null)
{
var contentWriters = context.GetServices<IHttpContentFactory>();
var contentWriter = SelectContentWriter(contentType, contentWriters);
request.Content = contentWriter.CreateHttpContent(content, contentType);
var factories = context.GetServices<IHttpContentFactory>();
var factory = SelectContentWriter(contentType, factories);
request.Content = factory.CreateHttpContent(content, contentType);
}
return request;
}
private IHttpContentFactory SelectContentWriter(string? contentType, IEnumerable<IHttpContentFactory> requestContentWriters) =>
string.IsNullOrWhiteSpace(contentType) ? new JsonContentFactory() : requestContentWriters.First(w => w.SupportsContentType(contentType));
private IHttpContentFactory SelectContentWriter(string? contentType, IEnumerable<IHttpContentFactory> factories)
{
if (string.IsNullOrWhiteSpace(contentType))
return new JsonContentFactory();
var parsedContentType = new System.Net.Mime.ContentType(contentType);
return factories.FirstOrDefault(httpContentFactory => httpContentFactory.SupportedContentTypes.Any(c => c == parsedContentType.MediaType)) ?? new JsonContentFactory();
}
}

View file

@ -1,9 +1,9 @@
using System.Net;
using System.Runtime.CompilerServices;
using Elsa.Extensions;
using Elsa.Http.ActivityOptionProviders;
using Elsa.Http.ContentWriters;
using Elsa.Http.Models;
using Elsa.Http.Providers;
using Elsa.Workflows.Core;
using Elsa.Workflows.Core.Attributes;
using Elsa.Workflows.Core.Exceptions;
@ -40,7 +40,7 @@ public class WriteHttpResponse : Activity
/// </summary>
[Input(
Description = "The content type to write when sending the response.",
OptionsProvider = typeof(WriteHttpResponseContentTypeOptionsProvider),
OptionsProvider = typeof(HttpContentTypeOptionsProvider),
UIHint = InputUIHints.Dropdown
)]
public Input<string?> ContentType { get; set; } = default!;
@ -105,8 +105,9 @@ public class WriteHttpResponse : Activity
if (string.IsNullOrWhiteSpace(contentType))
contentType = DetermineContentType(content);
var contentWriter = context.GetServices<IHttpContentFactory>().FirstOrDefault(x => x.SupportsContentType(contentType)) ?? new TextContentFactory();
var httpContent = contentWriter.CreateHttpContent(content, contentType);
var factories = context.GetServices<IHttpContentFactory>();
var factory = factories.FirstOrDefault(httpContentFactory => httpContentFactory.SupportedContentTypes.Any(c => c == contentType)) ?? new TextContentFactory();
var httpContent = factory.CreateHttpContent(content, contentType);
// Set content type.
response.ContentType = httpContent.Headers.ContentType?.ToString() ?? contentType;

View file

@ -0,0 +1,34 @@
using System.Reflection;
using Elsa.Http.ContentWriters;
using Elsa.Workflows.Core.Contracts;
namespace Elsa.Http.ActivityOptionProviders;
/// <summary>
/// Provides options for the <see cref="SendHttpRequest"/> activity's <see cref="SendHttpRequest.ContentType"/> property.
/// </summary>
public class HttpContentTypeOptionsProvider : IActivityPropertyOptionsProvider
{
private readonly IEnumerable<IHttpContentFactory> _httpContentFactories;
/// <summary>
/// Creates a new instance of the <see cref="HttpContentTypeOptionsProvider"/> class.
/// </summary>
public HttpContentTypeOptionsProvider(IEnumerable<IHttpContentFactory> httpContentFactories)
{
_httpContentFactories = httpContentFactories;
}
/// <inheritdoc />
public ValueTask<IDictionary<string, object>> GetOptionsAsync(PropertyInfo property, CancellationToken cancellationToken = default)
{
var contentTypes = _httpContentFactories.SelectMany(x => x.SupportedContentTypes).Distinct().OrderBy(x => x).ToArray();
var options = new Dictionary<string, object>
{
["items"] = new[] { "" }.Concat(contentTypes)
};
return new(options);
}
}

View file

@ -0,0 +1,23 @@
using System.Net.Mime;
namespace Elsa.Http.ContentWriters;
/// <summary>
/// Creates a <see cref="HttpContent"/> object for application/octet-stream.
/// </summary>
public class BinaryContentFactory : IHttpContentFactory
{
/// <inheritdoc />
public IEnumerable<string> SupportedContentTypes => new[] { MediaTypeNames.Application.Octet };
/// <inheritdoc />
public HttpContent CreateHttpContent(object content, string contentType)
{
return content switch
{
byte[] bytes => new ByteArrayContent(bytes),
Stream stream => new StreamContent(stream),
_ => throw new NotSupportedException($"Content of type {content.GetType()} is not supported.")
};
}
}

View file

@ -8,10 +8,8 @@ namespace Elsa.Http.ContentWriters;
/// </summary>
public class FormUrlEncodedHttpContentFactory : IHttpContentFactory
{
private readonly List<string> _supportedContentTypes = new() { "application/x-www-form-urlencoded" };
/// <inheritdoc />
public bool SupportsContentType(string contentType) => _supportedContentTypes.Contains(contentType);
public IEnumerable<string> SupportedContentTypes => new[] { "application/x-www-form-urlencoded" };
/// <inheritdoc />
public HttpContent CreateHttpContent(object content, string? contentType = null) => new FormUrlEncodedContent(GetContentAsDictionary(content));

View file

@ -8,7 +8,7 @@ public interface IHttpContentFactory
/// <summary>
/// Returns a value indicating whether this factory supports the specified content type.
/// </summary>
bool SupportsContentType(string contentType);
IEnumerable<string> SupportedContentTypes { get; }
/// <summary>
/// Creates a concrete <see cref="HttpContent"/> derivative based on the specified content type.

View file

@ -10,10 +10,8 @@ namespace Elsa.Http.ContentWriters;
/// </summary>
public class JsonContentFactory : IHttpContentFactory
{
private readonly List<string> _supportedContentTypes = new() { MediaTypeNames.Application.Json, "text/json" };
/// <inheritdoc />
public bool SupportsContentType(string contentType) => _supportedContentTypes.Contains(contentType);
public IEnumerable<string> SupportedContentTypes => new[] { MediaTypeNames.Application.Json, "text/json" };
/// <inheritdoc />
public HttpContent CreateHttpContent(object content, string contentType)

View file

@ -8,16 +8,14 @@ namespace Elsa.Http.ContentWriters;
/// </summary>
public class TextContentFactory : IHttpContentFactory
{
private readonly List<string> _supportedContentTypes = new()
/// <inheritdoc />
public IEnumerable<string> SupportedContentTypes => new[]
{
MediaTypeNames.Text.Plain,
MediaTypeNames.Text.RichText,
MediaTypeNames.Text.Html,
};
/// <inheritdoc />
public bool SupportsContentType(string contentType) => _supportedContentTypes.Contains(contentType);
/// <inheritdoc />
public HttpContent CreateHttpContent(object content, string contentType)
{

View file

@ -5,14 +5,17 @@ using System.Xml.Serialization;
namespace Elsa.Http.ContentWriters;
/// <summary>
/// Creates a <see cref="HttpContent"/> object for application/json.
/// Creates a <see cref="HttpContent"/> object for XML types.
/// </summary>
public class XmlContentFactory : IHttpContentFactory
{
private readonly List<string> _supportedContentTypes = new() { MediaTypeNames.Application.Xml, MediaTypeNames.Text.Xml };
/// <inheritdoc />
public bool SupportsContentType(string contentType) => _supportedContentTypes.Contains(contentType);
public IEnumerable<string> SupportedContentTypes => new[]
{
MediaTypeNames.Application.Xml,
MediaTypeNames.Text.Xml,
MediaTypeNames.Application.Soap,
};
/// <inheritdoc />
public HttpContent CreateHttpContent(object content, string contentType)
@ -20,7 +23,7 @@ public class XmlContentFactory : IHttpContentFactory
var text = content as string ?? Serialize(content);
return new StringContent(text, Encoding.UTF8, contentType);
}
private string Serialize(object value)
{
using var writer = new StringWriter();

View file

@ -4,6 +4,7 @@ using Elsa.Extensions;
using Elsa.Features.Abstractions;
using Elsa.Features.Attributes;
using Elsa.Features.Services;
using Elsa.Http.ActivityOptionProviders;
using Elsa.Http.ContentWriters;
using Elsa.Http.Contracts;
using Elsa.Http.DownloadableContentHandlers;
@ -14,7 +15,6 @@ using Elsa.Http.Models;
using Elsa.Http.Options;
using Elsa.Http.Parsers;
using Elsa.Http.PortResolvers;
using Elsa.Http.Providers;
using Elsa.Http.Selectors;
using Elsa.Http.Services;
using Elsa.JavaScript.Features;
@ -173,7 +173,7 @@ public class HttpFeature : FeatureBase
.AddSingleton<IHttpContentFactory, FormUrlEncodedHttpContentFactory>()
// Activity property options providers.
.AddSingleton<IActivityPropertyOptionsProvider, WriteHttpResponseContentTypeOptionsProvider>()
.AddSingleton<IActivityPropertyOptionsProvider, HttpContentTypeOptionsProvider>()
// Port resolvers.
.AddSingleton<IActivityPortResolver, SendHttpRequestActivityPortResolver>()

View file

@ -1,26 +0,0 @@
using System.Reflection;
using Elsa.Http.Options;
using Elsa.Workflows.Core.Contracts;
using Microsoft.Extensions.Options;
namespace Elsa.Http.Providers;
internal class WriteHttpResponseContentTypeOptionsProvider : IActivityPropertyOptionsProvider
{
private readonly HttpActivityOptions _options;
public WriteHttpResponseContentTypeOptionsProvider(IOptions<HttpActivityOptions> options)
{
_options = options.Value;
}
public ValueTask<IDictionary<string, object>> GetOptionsAsync(PropertyInfo property, CancellationToken cancellationToken = default)
{
var options = new Dictionary<string, object>
{
["items"] = new[] { "" }.Concat(_options.AvailableContentTypes)
};
return new(options);
}
}