2023-08-06 18:55:24 +00:00
|
|
|
using System.Net.Http.Headers;
|
|
|
|
|
using Elsa.Extensions;
|
|
|
|
|
using Elsa.Http.ContentWriters;
|
Add a more generic UIHandler to customize how inputAttributes can be handle by UI (#4688)
* add a more generic UIHandler to customize how inputAttributes can be handle by the ui
* Add IPropertyUIHandlerResolver and update PropertyUIHandlerResolver
Introduced a new interface, IPropertyUIHandlerResolver, to resolve UI options for a property. Refactored PropertyUIHandlerResolver to implement this interface and removed the unnecessary partial class structure. Also, cleaned up some unnecessary usings in various files for better code organization.
* Refactor variable name and description in InputDescriptor
The 'uISpecifications' variable in the InputDescriptor model is renamed to 'uiSpecifications' for better readability. Additionally, the associated comment was revised to explain that the dictionary is used by the UI.
* "Refactor codebase for improved organization and cleaner architecture"
The codebase has been significantly refactored, moving several classes to more appropriate namespaces for improved organization and cleaner architecture. This includes shifting UI hint handlers, activities, and memory-related components, amongst others. The changes should improve code readability and maintainability, but as this is a broad refactoring effort, thorough regression testing is advised.
* Add CheckList UIHint with associated handler and provider
This update introduces a new UIHint called CheckList to the Elsa.Workflows.Core. This includes the necessary handler and provider classes. The handler is registered in the WorkflowsFeature.cs, and the CheckList UIHint key has been added to the InputUIHints.cs. Various associated files have been created in both the Elsa.Api.Client and Elsa.Workflows.Core project to support this new UIHint.
---------
Co-authored-by: Jérémie DEVILLARD <jdevillard@users.noreply.github.com>
Co-authored-by: Sipke Schoorstra <sipkeschoorstra@outlook.com>
2023-12-26 17:56:29 +00:00
|
|
|
using Elsa.Http.UIHints;
|
|
|
|
|
using Elsa.Workflows;
|
|
|
|
|
using Elsa.Workflows.Attributes;
|
|
|
|
|
using Elsa.Workflows.UIHints;
|
|
|
|
|
using Elsa.Workflows.Models;
|
2023-12-08 20:53:43 +00:00
|
|
|
using HttpHeaders = Elsa.Http.Models.HttpHeaders;
|
2023-08-06 18:55:24 +00:00
|
|
|
|
|
|
|
|
namespace Elsa.Http;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Base class for activities that send HTTP requests.
|
|
|
|
|
/// </summary>
|
2023-09-22 09:13:23 +00:00
|
|
|
[Output(IsSerializable = false)]
|
2023-08-06 18:55:24 +00:00
|
|
|
public abstract class SendHttpRequestBase : Activity<HttpResponseMessage>
|
|
|
|
|
{
|
|
|
|
|
/// <inheritdoc />
|
|
|
|
|
protected SendHttpRequestBase(string? source = default, int? line = default) : base(source, line)
|
|
|
|
|
{
|
|
|
|
|
}
|
2023-10-12 12:20:15 +00:00
|
|
|
|
2023-08-06 18:55:24 +00:00
|
|
|
/// <summary>
|
|
|
|
|
/// The URL to send the request to.
|
|
|
|
|
/// </summary>
|
|
|
|
|
[Input]
|
|
|
|
|
public Input<Uri?> Url { get; set; } = default!;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// The HTTP method to use when sending the request.
|
|
|
|
|
/// </summary>
|
|
|
|
|
[Input(
|
|
|
|
|
Description = "The HTTP method to use when sending the request.",
|
|
|
|
|
Options = new[] { "GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS", "HEAD" },
|
|
|
|
|
DefaultValue = "GET",
|
Add a more generic UIHandler to customize how inputAttributes can be handle by UI (#4688)
* add a more generic UIHandler to customize how inputAttributes can be handle by the ui
* Add IPropertyUIHandlerResolver and update PropertyUIHandlerResolver
Introduced a new interface, IPropertyUIHandlerResolver, to resolve UI options for a property. Refactored PropertyUIHandlerResolver to implement this interface and removed the unnecessary partial class structure. Also, cleaned up some unnecessary usings in various files for better code organization.
* Refactor variable name and description in InputDescriptor
The 'uISpecifications' variable in the InputDescriptor model is renamed to 'uiSpecifications' for better readability. Additionally, the associated comment was revised to explain that the dictionary is used by the UI.
* "Refactor codebase for improved organization and cleaner architecture"
The codebase has been significantly refactored, moving several classes to more appropriate namespaces for improved organization and cleaner architecture. This includes shifting UI hint handlers, activities, and memory-related components, amongst others. The changes should improve code readability and maintainability, but as this is a broad refactoring effort, thorough regression testing is advised.
* Add CheckList UIHint with associated handler and provider
This update introduces a new UIHint called CheckList to the Elsa.Workflows.Core. This includes the necessary handler and provider classes. The handler is registered in the WorkflowsFeature.cs, and the CheckList UIHint key has been added to the InputUIHints.cs. Various associated files have been created in both the Elsa.Api.Client and Elsa.Workflows.Core project to support this new UIHint.
---------
Co-authored-by: Jérémie DEVILLARD <jdevillard@users.noreply.github.com>
Co-authored-by: Sipke Schoorstra <sipkeschoorstra@outlook.com>
2023-12-26 17:56:29 +00:00
|
|
|
UIHint = InputUIHints.DropDown
|
2023-08-06 18:55:24 +00:00
|
|
|
)]
|
|
|
|
|
public Input<string> Method { get; set; } = new("GET");
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// The content to send with the request. Can be a string, an object, a byte array or a stream.
|
|
|
|
|
/// </summary>
|
|
|
|
|
[Input(Description = "The content to send with the request. Can be a string, an object, a byte array or a stream.")]
|
|
|
|
|
public Input<object?> Content { get; set; } = default!;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// The content type to use when sending the request.
|
|
|
|
|
/// </summary>
|
|
|
|
|
[Input(
|
|
|
|
|
Description = "The content type to use when sending the request.",
|
Add a more generic UIHandler to customize how inputAttributes can be handle by UI (#4688)
* add a more generic UIHandler to customize how inputAttributes can be handle by the ui
* Add IPropertyUIHandlerResolver and update PropertyUIHandlerResolver
Introduced a new interface, IPropertyUIHandlerResolver, to resolve UI options for a property. Refactored PropertyUIHandlerResolver to implement this interface and removed the unnecessary partial class structure. Also, cleaned up some unnecessary usings in various files for better code organization.
* Refactor variable name and description in InputDescriptor
The 'uISpecifications' variable in the InputDescriptor model is renamed to 'uiSpecifications' for better readability. Additionally, the associated comment was revised to explain that the dictionary is used by the UI.
* "Refactor codebase for improved organization and cleaner architecture"
The codebase has been significantly refactored, moving several classes to more appropriate namespaces for improved organization and cleaner architecture. This includes shifting UI hint handlers, activities, and memory-related components, amongst others. The changes should improve code readability and maintainability, but as this is a broad refactoring effort, thorough regression testing is advised.
* Add CheckList UIHint with associated handler and provider
This update introduces a new UIHint called CheckList to the Elsa.Workflows.Core. This includes the necessary handler and provider classes. The handler is registered in the WorkflowsFeature.cs, and the CheckList UIHint key has been added to the InputUIHints.cs. Various associated files have been created in both the Elsa.Api.Client and Elsa.Workflows.Core project to support this new UIHint.
---------
Co-authored-by: Jérémie DEVILLARD <jdevillard@users.noreply.github.com>
Co-authored-by: Sipke Schoorstra <sipkeschoorstra@outlook.com>
2023-12-26 17:56:29 +00:00
|
|
|
UIHandler = typeof(HttpContentTypeOptionsProvider),
|
|
|
|
|
UIHint = InputUIHints.DropDown
|
2023-08-06 18:55:24 +00:00
|
|
|
)]
|
|
|
|
|
public Input<string?> ContentType { get; set; } = default!;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// The Authorization header value to send with the request.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <example>Bearer {some-access-token}</example>
|
2023-12-26 10:13:01 +00:00
|
|
|
[Input(Description = "The Authorization header value to send with the request. For example: Bearer {some-access-token}", Category = "Security")]
|
2023-08-06 18:55:24 +00:00
|
|
|
public Input<string?> Authorization { get; set; } = default!;
|
|
|
|
|
|
2023-12-26 10:13:01 +00:00
|
|
|
/// <summary>
|
|
|
|
|
/// A value that allows to add the Authorization header without validation.
|
|
|
|
|
/// </summary>
|
|
|
|
|
[Input(Description = "A value that allows to add the Authorization header without validation.", Category = "Security")]
|
|
|
|
|
public Input<bool> DisableAuthorizationHeaderValidation { get; set; } = default!;
|
|
|
|
|
|
2023-08-06 18:55:24 +00:00
|
|
|
/// <summary>
|
|
|
|
|
/// The headers to send along with the request.
|
|
|
|
|
/// </summary>
|
|
|
|
|
[Input(Description = "The headers to send along with the request.", Category = "Advanced")]
|
2023-12-08 20:53:43 +00:00
|
|
|
public Input<HttpHeaders?> RequestHeaders { get; set; } = new(new HttpHeaders());
|
2023-08-06 18:55:24 +00:00
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// The parsed content, if any.
|
|
|
|
|
/// </summary>
|
|
|
|
|
[Output(Description = "The parsed content, if any.")]
|
|
|
|
|
public Output<object?> ParsedContent { get; set; } = default!;
|
|
|
|
|
|
|
|
|
|
/// <inheritdoc />
|
|
|
|
|
protected override async ValueTask ExecuteAsync(ActivityExecutionContext context)
|
|
|
|
|
{
|
|
|
|
|
await TrySendAsync(context);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Handles the response.
|
|
|
|
|
/// </summary>
|
|
|
|
|
protected abstract ValueTask HandleResponseAsync(ActivityExecutionContext context, HttpResponseMessage response);
|
|
|
|
|
|
2023-09-10 19:31:57 +00:00
|
|
|
/// <summary>
|
|
|
|
|
/// Handles an exception that occurred while sending the request.
|
|
|
|
|
/// </summary>
|
|
|
|
|
protected abstract ValueTask HandleRequestExceptionAsync(ActivityExecutionContext context, HttpRequestException exception);
|
2023-10-12 12:20:15 +00:00
|
|
|
|
2023-09-18 13:56:38 +00:00
|
|
|
/// <summary>
|
|
|
|
|
/// Handles <see cref="TaskCanceledException"/> that occurred while sending the request.
|
|
|
|
|
/// </summary>
|
|
|
|
|
protected abstract ValueTask HandleTaskCanceledExceptionAsync(ActivityExecutionContext context, TaskCanceledException exception);
|
2023-09-10 19:31:57 +00:00
|
|
|
|
2023-08-06 18:55:24 +00:00
|
|
|
private async Task TrySendAsync(ActivityExecutionContext context)
|
|
|
|
|
{
|
|
|
|
|
var request = PrepareRequest(context);
|
|
|
|
|
var httpClientFactory = context.GetRequiredService<IHttpClientFactory>();
|
|
|
|
|
var httpClient = httpClientFactory.CreateClient(nameof(SendHttpRequestBase));
|
|
|
|
|
var cancellationToken = context.CancellationToken;
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
var response = await httpClient.SendAsync(request, cancellationToken);
|
|
|
|
|
var parsedContent = await ParseContentAsync(context, response.Content);
|
|
|
|
|
context.Set(Result, response);
|
|
|
|
|
context.Set(ParsedContent, parsedContent);
|
|
|
|
|
|
|
|
|
|
await HandleResponseAsync(context, response);
|
|
|
|
|
}
|
2023-10-12 12:20:15 +00:00
|
|
|
catch (HttpRequestException e)
|
2023-09-10 19:31:57 +00:00
|
|
|
{
|
|
|
|
|
context.AddExecutionLogEntry("Error", e.Message, payload: new { StackTrace = e.StackTrace });
|
|
|
|
|
context.JournalData.Add("Error", e.Message);
|
|
|
|
|
await HandleRequestExceptionAsync(context, e);
|
|
|
|
|
}
|
2023-08-06 18:55:24 +00:00
|
|
|
catch (TaskCanceledException e)
|
|
|
|
|
{
|
2023-09-18 13:56:38 +00:00
|
|
|
context.AddExecutionLogEntry("Error", e.Message, payload: new { StackTrace = e.StackTrace });
|
2023-08-06 18:55:24 +00:00
|
|
|
context.JournalData.Add("Cancelled", true);
|
2023-09-18 13:56:38 +00:00
|
|
|
await HandleTaskCanceledExceptionAsync(context, e);
|
2023-08-06 18:55:24 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async Task<object?> ParseContentAsync(ActivityExecutionContext context, HttpContent httpContent)
|
|
|
|
|
{
|
|
|
|
|
if (!HasContent(httpContent))
|
|
|
|
|
return null;
|
|
|
|
|
|
|
|
|
|
var cancellationToken = context.CancellationToken;
|
|
|
|
|
var targetType = ParsedContent.GetTargetType(context);
|
|
|
|
|
var contentStream = await httpContent.ReadAsStreamAsync(cancellationToken);
|
|
|
|
|
var contentType = httpContent.Headers.ContentType?.MediaType!;
|
|
|
|
|
|
|
|
|
|
targetType ??= contentType switch
|
|
|
|
|
{
|
|
|
|
|
"application/json" => typeof(object),
|
|
|
|
|
_ => typeof(string)
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return await context.ParseContentAsync(contentStream, contentType, targetType, cancellationToken);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static bool HasContent(HttpContent httpContent) => httpContent.Headers.ContentLength > 0;
|
|
|
|
|
|
|
|
|
|
private HttpRequestMessage PrepareRequest(ActivityExecutionContext context)
|
|
|
|
|
{
|
|
|
|
|
var method = Method.GetOrDefault(context) ?? "GET";
|
|
|
|
|
var url = Url.Get(context);
|
|
|
|
|
var request = new HttpRequestMessage(new HttpMethod(method), url);
|
|
|
|
|
var headers = context.GetHeaders(RequestHeaders);
|
|
|
|
|
var authorization = Authorization.GetOrDefault(context);
|
2023-12-26 10:13:01 +00:00
|
|
|
var addAuthorizationWithoutValidation = DisableAuthorizationHeaderValidation.GetOrDefault(context);
|
2023-08-06 18:55:24 +00:00
|
|
|
|
|
|
|
|
if (!string.IsNullOrWhiteSpace(authorization))
|
2023-12-26 10:13:01 +00:00
|
|
|
if(addAuthorizationWithoutValidation)
|
|
|
|
|
request.Headers.TryAddWithoutValidation("Authorization", authorization);
|
|
|
|
|
else
|
|
|
|
|
request.Headers.Authorization = AuthenticationHeaderValue.Parse(authorization);
|
2023-08-06 18:55:24 +00:00
|
|
|
|
|
|
|
|
foreach (var header in headers)
|
|
|
|
|
request.Headers.Add(header.Key, header.Value.AsEnumerable());
|
|
|
|
|
|
|
|
|
|
var contentType = ContentType.GetOrDefault(context);
|
|
|
|
|
var content = Content.GetOrDefault(context);
|
|
|
|
|
|
|
|
|
|
if (contentType != null && content != null)
|
|
|
|
|
{
|
2023-10-12 12:20:15 +00:00
|
|
|
var factories = context.GetServices<IHttpContentFactory>();
|
|
|
|
|
var factory = SelectContentWriter(contentType, factories);
|
|
|
|
|
request.Content = factory.CreateHttpContent(content, contentType);
|
2023-08-06 18:55:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return request;
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-12 12:20:15 +00:00
|
|
|
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();
|
|
|
|
|
}
|
2023-08-06 18:55:24 +00:00
|
|
|
}
|