using System; using System.Collections.Generic; using System.Linq; using System.Net.Http; using System.Net.Http.Headers; using System.Text; using System.Threading; using System.Threading.Tasks; using Elsa.Activities.Http.Models; using Elsa.Activities.Http.Services; using Elsa.ActivityResults; using Elsa.Attributes; using Elsa.Design; using Elsa.Services; using Elsa.Services.Models; using Microsoft.AspNetCore.Http; using HttpRequestHeaders = Elsa.Activities.Http.Models.HttpRequestHeaders; // ReSharper disable once CheckNamespace namespace Elsa.Activities.Http { [ActivityDefinition( Category = "HTTP", DisplayName = "Send HTTP Request", Description = "Send an HTTP request.", Outcomes = new[] { OutcomeNames.Done, "x => !!x.state.supportedStatusCodes ? ['UnSupportedStatusCode', ...x.state.supportedStatusCodes] : ['UnSupportedStatusCode']" } )] public class SendHttpRequest : Activity { private readonly HttpClient _httpClient; private readonly IEnumerable _parsers; public SendHttpRequest( IHttpClientFactory httpClientFactory, IEnumerable parsers) { _httpClient = httpClientFactory.CreateClient(nameof(SendHttpRequest)); this._parsers = parsers; } /// /// The URL to invoke. /// [ActivityProperty(Hint = "The URL to send the HTTP request to.")] public PathString Url { get; set; } /// /// The HTTP method to use. /// [ActivityProperty( Type = ActivityPropertyTypes.Select, Hint = "The HTTP method to use when making the request." )] [SelectOptions("GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS", "HEAD")] public string Method { get; set; } /// /// The body to send along with the request. /// [ActivityProperty(Hint = "The HTTP content to send along with the request.")] [WorkflowExpressionOptions(Multiline = true)] public string? Content { get; set; } /// /// The Content Type header to send along with the request body. /// [ActivityProperty( Type = ActivityPropertyTypes.Select, Hint = "The content type to send with the request (if applicable)." )] [SelectOptions("text/plain", "text/html", "application/json", "application/xml")] public string ContentType { get; set; } [ActivityProperty(Hint = "The Authorization header value to send.")] public string Authorization { get; set; } /// /// The headers to send along with the request. /// [ActivityProperty(Hint = "The headers to send along with the request.")] [WorkflowExpressionOptions(Multiline = true)] public HttpRequestHeaders RequestHeaders { get; set; } = new HttpRequestHeaders(); [ActivityProperty(Hint = "Check to read the content of the response.")] public bool ReadContent { get; set; } /// /// A list of HTTP status codes this activity can handle. /// [ActivityProperty( Type = ActivityPropertyTypes.List, Hint = "A list of possible HTTP status codes to handle, comma-separated. Example: 200, 400, 404" )] public ICollection SupportedStatusCodes { get; set; } = new HashSet(new[] { 200 }); protected override async Task OnExecuteAsync( ActivityExecutionContext context, CancellationToken cancellationToken) { var request = await CreateRequestAsync(context, cancellationToken); var response = await _httpClient.SendAsync(request, cancellationToken); var hasContent = response.Content != null; var contentType = response.Content?.Headers.ContentType.MediaType; var responseModel = new HttpResponseModel { StatusCode = response.StatusCode, Headers = new Dictionary( response.Headers.ToDictionary(x => x.Key, x => x.Value.ToArray()) ) }; if (hasContent && ReadContent) { var formatter = SelectContentParser(contentType); responseModel.Content = await formatter.ParseAsync(response, cancellationToken); } var statusCode = (int)response.StatusCode; var statusOutcome = statusCode.ToString(); var isSupportedStatusCode = SupportedStatusCodes.Contains(statusCode); var outcomes = new List { OutcomeNames.Done, statusOutcome }; if (!isSupportedStatusCode) outcomes.Add("UnSupportedStatusCode"); return Done(outcomes, responseModel); } private IHttpResponseBodyParser SelectContentParser(string contentType) { var formatters = _parsers.OrderByDescending(x => x.Priority).ToList(); return formatters.FirstOrDefault( x => x.SupportedContentTypes.Contains(contentType, StringComparer.OrdinalIgnoreCase) ) ?? formatters.Last(); } private async Task CreateRequestAsync( ActivityExecutionContext context, CancellationToken cancellationToken) { var methodSupportsBody = GetMethodSupportsBody(Method); var url = Url; var request = new HttpRequestMessage(new HttpMethod(Method), url); var authorizationHeaderValue = Authorization; var requestHeaders = new HeaderDictionary(RequestHeaders); if (methodSupportsBody) { var body = Content; var contentType = ContentType; if (!string.IsNullOrWhiteSpace(body)) request.Content = new StringContent(body, Encoding.UTF8, contentType); } if (!string.IsNullOrWhiteSpace(authorizationHeaderValue)) request.Headers.Authorization = AuthenticationHeaderValue.Parse(authorizationHeaderValue); foreach (var header in requestHeaders) request.Headers.Add(header.Key, header.Value.AsEnumerable()); return request; } private static bool GetMethodSupportsBody(string method) { var methods = new[] { "POST", "PUT", "PATCH" }; return methods.Contains(method, StringComparer.InvariantCultureIgnoreCase); } } }