using System.Net; using System.Net.Mime; using Elsa.Extensions; 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.Models; using Elsa.Workflows.Management.Models; using Microsoft.AspNetCore.Http; namespace Elsa.Http; /// /// Write a response to the current HTTP response object. /// [Activity("Elsa", "HTTP", "Write a response to the current HTTP response object.", DisplayName = "HTTP Response")] public class WriteHttpResponse : CodeActivity { /// /// The status code to return. /// [Input(DefaultValue = HttpStatusCode.OK, Description = "The status code to return.")] public Input StatusCode { get; set; } = new(HttpStatusCode.OK); /// /// The content to write back. /// [Input(Description = "The content to write back. String values will be sent as-is, while objects will be serialized to a JSON string. Byte arrays and streams will be sent as files.")] public Input Content { get; set; } = default!; /// /// The content type to use when returning the response. /// [Input( Description = "The content type to use when returning the response.", OptionsProvider = typeof(WriteHttpResponseContentTypeOptionsProvider), UIHint = InputUIHints.Dropdown )] public Input ContentType { get; set; } = default!; /// /// The headers to return along with the response. /// [Input(Description = "The headers to return along with the response.", Category = "Advanced")] public Input ResponseHeaders { get; set; } = new(new HttpResponseHeaders()); /// protected override async ValueTask ExecuteAsync(ActivityExecutionContext context) { var httpContextAccessor = context.GetRequiredService(); var httpContext = httpContextAccessor.HttpContext; if (httpContext == null) { // We're executing in a non-HTTP context (e.g. in a virtual actor). // Create a bookmark to allow the invoker to export the state and resume execution from there. context.CreateBookmark(OnResumeAsync); return; } await WriteResponseAsync(context, httpContext.Response); } private async ValueTask OnResumeAsync(ActivityExecutionContext context) { var httpContextAccessor = context.GetRequiredService(); var httpContext = httpContextAccessor.HttpContext; if (httpContext == null) { // We're not in an HTTP context, so let's fail. throw new Exception("Cannot execute in a non-HTTP context"); } await WriteResponseAsync(context, httpContext.Response); } private async Task WriteResponseAsync(ActivityExecutionContext context, HttpResponse response) { // Set status code. response.StatusCode = (int)context.Get(StatusCode); // Add headers. var headers = ResponseHeaders.TryGet(context) ?? new HttpResponseHeaders(); foreach (var header in headers) response.Headers.Add(header.Key, header.Value); // Get content and content type. var content = context.Get(Content); if (content == null) return; var contentType = ContentType.TryGet(context) ?? MediaTypeNames.Text.Plain; if (string.IsNullOrWhiteSpace(contentType)) contentType = DetermineContentType(content); var contentWriter = context.GetServices().FirstOrDefault(x => x.SupportsContentType(contentType)) ?? new TextContentFactory(); var httpContent = contentWriter.CreateHttpContent(content, contentType); // Set content type. response.ContentType = httpContent.Headers.ContentType?.ToString() ?? contentType; // Write content. await httpContent.CopyToAsync(response.Body); } private string DetermineContentType(object? content) => content is byte[] or Stream ? "application/octet-stream" : "text/plain"; }