2022-01-04 08:42:12 +00:00
using System.Net ;
2023-01-18 12:00:27 +00:00
using System.Net.Http.Headers ;
using Elsa.Extensions ;
2023-01-24 20:48:05 +00:00
using Elsa.Http.ContentWriters ;
2022-05-26 10:47:31 +00:00
using Elsa.Workflows.Core.Attributes ;
using Elsa.Workflows.Core.Models ;
2023-01-18 12:00:27 +00:00
using Elsa.Workflows.Management.Models ;
2022-01-04 08:42:12 +00:00
using Microsoft.AspNetCore.Http ;
2023-01-18 12:00:27 +00:00
using HttpRequestHeaders = Elsa . Http . Models . HttpRequestHeaders ;
using HttpResponseHeaders = Elsa . Http . Models . HttpResponseHeaders ;
2022-01-04 08:42:12 +00:00
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." ,
Options = new [ ] { "" , "text/plain" , "text/html" , "application/json" , "application/xml" , "application/x-www-form-urlencoded" } ,
UIHint = InputUIHints . Dropdown
) ]
public Input < string? > ContentType { get ; set ; } = default ! ;
/// <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-01-23 22:30:00 +00:00
var headers = ResponseHeaders . TryGet ( context ) ? ? new HttpResponseHeaders ( ) ;
foreach ( var header in headers )
response . Headers . Add ( header . Key , header . Value ) ;
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 ;
var contentType = ContentType . Get ( context ) ? ? DetermineContentType ( content ) ;
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 ;
// Write content.
await httpContent . CopyToAsync ( response . Body ) ;
2022-01-04 08:42:12 +00:00
}
2023-01-24 20:48:05 +00:00
private string DetermineContentType ( object? content ) = > content is byte [ ] or Stream ? "application/octet-stream" : "text/plain" ;
2022-01-04 08:42:12 +00:00
}