2022-01-04 08:42:12 +00:00
using System.Net ;
2023-08-06 18:55:24 +00:00
using System.Runtime.CompilerServices ;
2023-01-18 12:00:27 +00:00
using Elsa.Extensions ;
2023-10-12 12:20:15 +00:00
using Elsa.Http.ActivityOptionProviders ;
2023-01-24 20:48:05 +00:00
using Elsa.Http.ContentWriters ;
2023-01-24 21:39:31 +00:00
using Elsa.Http.Models ;
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 ;
2023-09-16 09:06:56 +00:00
using Elsa.Workflows.Core.Exceptions ;
2022-05-26 10:47:31 +00:00
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-07-21 08:57:19 +00:00
public class WriteHttpResponse : Activity
2022-01-04 08:42:12 +00:00
{
2023-08-06 18:55:24 +00:00
/// <inheritdoc />
public WriteHttpResponse ( [ CallerFilePath ] string? source = default , [ CallerLineNumber ] int? line = default ) : base ( source , line )
{
}
2023-11-22 19:46:40 +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 (
2023-05-08 20:38:59 +00:00
Description = "The content type to write when sending the response." ,
2023-10-12 12:20:15 +00:00
OptionsProvider = typeof ( HttpContentTypeOptionsProvider ) ,
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>
2023-05-08 20:38:59 +00:00
[Input(Description = "The headers to send along with the response.", Category = "Advanced")]
2023-12-08 20:53:43 +00:00
public Input < HttpHeaders ? > ResponseHeaders { get ; set ; } = new ( new HttpHeaders ( ) ) ;
2023-01-18 12:00:27 +00:00
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
2023-09-16 09:06:56 +00:00
context . CreateBookmark ( OnResumeAsync , BookmarkMetadata . HttpCrossBoundary ) ;
2022-10-04 09:57:01 +00:00
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.
2023-09-16 09:06:56 +00:00
throw new FaultException ( "Cannot execute in a non-HTTP context" ) ;
2022-10-04 09:57:01 +00:00
}
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.
2023-08-12 09:47:07 +00:00
var statusCode = StatusCode . GetOrDefault ( context , ( ) = > HttpStatusCode . OK ) ;
response . StatusCode = ( int ) statusCode ;
2022-01-04 08:42:12 +00:00
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-10-23 12:23:55 +00:00
if ( content ! = null )
{
var contentType = ContentType . GetOrDefault ( context ) ;
2023-02-03 13:29:44 +00:00
2023-10-23 12:23:55 +00:00
if ( string . IsNullOrWhiteSpace ( contentType ) )
contentType = DetermineContentType ( content ) ;
2023-02-03 13:29:44 +00:00
2023-10-23 12:23:55 +00:00
var factories = context . GetServices < IHttpContentFactory > ( ) ;
var factory = factories . FirstOrDefault ( httpContentFactory = > httpContentFactory . SupportedContentTypes . Any ( c = > c = = contentType ) ) ? ? new TextContentFactory ( ) ;
var httpContent = factory . CreateHttpContent ( content , contentType ) ;
2023-01-24 20:48:05 +00:00
2023-10-23 12:23:55 +00:00
// Set content type.
response . ContentType = httpContent . Headers . ContentType ? . ToString ( ) ? ? contentType ;
2023-02-03 13:29:44 +00:00
2023-10-23 12:23:55 +00:00
// Write content.
if ( statusCode ! = HttpStatusCode . NoContent )
2023-11-22 19:46:40 +00:00
{
try
{
await httpContent . CopyToAsync ( response . Body ) ;
}
catch ( NotSupportedException )
{
// This can happen the Content property is a type that cannot be serialized or contains a type that cannot be serialized.
await response . WriteAsync ( "The response includes a type that cannot be serialized." ) ;
}
}
2023-10-23 12:23:55 +00:00
}
2023-11-22 19:46:40 +00:00
2023-07-21 08:57:19 +00:00
// Complete activity.
await context . CompleteActivityAsync ( ) ;
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
}