2022-01-04 08:42:12 +00:00
using System.Net ;
2023-01-18 12:00:27 +00:00
using Elsa.Extensions ;
2023-01-24 20:48:05 +00:00
using Elsa.Http.ContentWriters ;
2023-01-24 21:39:31 +00:00
using Elsa.Http.Models ;
using Elsa.Http.Providers ;
2023-03-03 13:48:05 +00:00
using Elsa.Workflows.Core ;
2022-05-26 10:47:31 +00:00
using Elsa.Workflows.Core.Attributes ;
using Elsa.Workflows.Core.Models ;
2022-01-04 08:42:12 +00:00
using Microsoft.AspNetCore.Http ;
2022-05-26 10:47:31 +00:00
namespace Elsa.Http ;
2022-01-04 08:42:12 +00:00
2022-12-21 21:22:21 +00:00
/// <summary>
/// Write a response to the current HTTP response object.
/// </summary>
[Activity("Elsa", "HTTP", "Write a response to the current HTTP response object.", DisplayName = "HTTP Response")]
2023-01-19 20:49:24 +00:00
public class WriteHttpResponse : CodeActivity
2022-01-04 08:42:12 +00:00
{
2022-12-21 21:22:21 +00:00
/// <summary>
/// The status code to return.
/// </summary>
[Input(DefaultValue = HttpStatusCode.OK, Description = "The status code to return.")]
2022-01-04 08:42:12 +00:00
public Input < HttpStatusCode > StatusCode { get ; set ; } = new ( HttpStatusCode . OK ) ;
2022-12-21 21:22:21 +00:00
/// <summary>
/// The content to write back.
/// </summary>
2023-01-24 20:48:05 +00:00
[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 < object? > Content { get ; set ; } = default ! ;
2022-01-04 08:42:12 +00:00
2023-01-18 12:00:27 +00:00
/// <summary>
/// The content type to use when returning the response.
/// </summary>
[ Input (
Description = "The content type to use when returning the response." ,
2023-01-24 21:39:31 +00:00
OptionsProvider = typeof ( WriteHttpResponseContentTypeOptionsProvider ) ,
2023-01-18 12:00:27 +00:00
UIHint = InputUIHints . Dropdown
) ]
public Input < string? > ContentType { get ; set ; } = default ! ;
2023-02-03 13:29:44 +00:00
2023-01-18 12:00:27 +00:00
/// <summary>
/// The headers to return along with the response.
/// </summary>
[Input(Description = "The headers to return along with the response.", Category = "Advanced")]
public Input < HttpResponseHeaders ? > ResponseHeaders { get ; set ; } = new ( new HttpResponseHeaders ( ) ) ;
2022-12-21 21:22:21 +00:00
/// <inheritdoc />
2022-01-26 14:48:59 +00:00
protected override async ValueTask ExecuteAsync ( ActivityExecutionContext context )
2022-01-04 08:42:12 +00:00
{
var httpContextAccessor = context . GetRequiredService < IHttpContextAccessor > ( ) ;
var httpContext = httpContextAccessor . HttpContext ;
2022-10-04 09:57:01 +00:00
if ( httpContext = = null )
{
2022-12-21 21:22:21 +00:00
// 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.
2022-10-04 09:57:01 +00:00
context . CreateBookmark ( OnResumeAsync ) ;
return ;
}
2022-12-21 21:22:21 +00:00
2023-01-23 22:30:00 +00:00
await WriteResponseAsync ( context , httpContext . Response ) ;
2022-10-04 09:57:01 +00:00
}
private async ValueTask OnResumeAsync ( ActivityExecutionContext context )
{
var httpContextAccessor = context . GetRequiredService < IHttpContextAccessor > ( ) ;
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" ) ;
}
2022-12-21 21:22:21 +00:00
2023-01-23 22:30:00 +00:00
await WriteResponseAsync ( context , httpContext . Response ) ;
}
2022-01-04 08:42:12 +00:00
2023-01-23 22:30:00 +00:00
private async Task WriteResponseAsync ( ActivityExecutionContext context , HttpResponse response )
{
2023-01-24 20:48:05 +00:00
// Set status code.
2022-01-04 08:42:12 +00:00
response . StatusCode = ( int ) context . Get ( StatusCode ) ;
2023-01-24 20:48:05 +00:00
// Add headers.
2023-03-09 09:24:38 +00:00
var headers = context . GetHeaders ( ResponseHeaders ) ;
2023-01-23 22:30:00 +00:00
foreach ( var header in headers )
response . Headers . Add ( header . Key , header . Value ) ;
2023-02-03 13:29:44 +00:00
2023-01-24 20:48:05 +00:00
// Get content and content type.
2022-01-04 08:42:12 +00:00
var content = context . Get ( Content ) ;
2023-01-24 20:48:05 +00:00
if ( content = = null )
return ;
2023-02-03 13:29:44 +00:00
2023-03-24 19:57:27 +00:00
var contentType = ContentType . GetOrDefault ( context ) ;
2023-02-03 13:29:44 +00:00
if ( string . IsNullOrWhiteSpace ( contentType ) )
contentType = DetermineContentType ( content ) ;
2023-01-24 20:48:05 +00:00
var contentWriter = context . GetServices < IHttpContentFactory > ( ) . FirstOrDefault ( x = > x . SupportsContentType ( contentType ) ) ? ? new TextContentFactory ( ) ;
var httpContent = contentWriter . CreateHttpContent ( content , contentType ) ;
// Set content type.
response . ContentType = httpContent . Headers . ContentType ? . ToString ( ) ? ? contentType ;
2023-02-03 13:29:44 +00:00
2023-01-24 20:48:05 +00:00
// Write content.
await httpContent . CopyToAsync ( response . Body ) ;
2022-01-04 08:42:12 +00:00
}
2023-01-24 20:48:05 +00:00
2023-03-09 10:24:00 +00:00
private string DetermineContentType ( object? content ) = > content is byte [ ] or Stream
? "application/octet-stream"
: content is string
? "text/plain"
: "application/json" ;
2022-01-04 08:42:12 +00:00
}