< Summary

Information
Class: Elsa.Http.WriteHttpResponse
Assembly: Elsa.Http
File(s): /home/runner/work/elsa-core/elsa-core/src/modules/Elsa.Http/Activities/WriteHttpResponse.cs
Line coverage
74%
Covered lines: 40
Uncovered lines: 14
Coverable lines: 54
Total lines: 165
Line coverage: 74%
Branch coverage
75%
Covered branches: 21
Total branches: 28
Branch coverage: 75%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
.ctor(...)100%210%
.ctor(...)100%210%
get_StatusCode()100%11100%
get_Content()100%11100%
get_ContentType()100%11100%
get_ResponseHeaders()100%11100%
ExecuteAsync()100%22100%
OnResumeAsync()0%620%
WriteResponseAsync()81.25%161695.23%
DetermineContentType(...)62.5%88100%

File(s)

/home/runner/work/elsa-core/elsa-core/src/modules/Elsa.Http/Activities/WriteHttpResponse.cs

#LineLine coverage
 1using System.Net;
 2using System.Runtime.CompilerServices;
 3using Elsa.Extensions;
 4using Elsa.Http.ContentWriters;
 5using Elsa.Http.UIHints;
 6using Elsa.Workflows;
 7using Elsa.Workflows.Attributes;
 8using Elsa.Workflows.UIHints;
 9using Elsa.Workflows.Exceptions;
 10using Elsa.Workflows.Models;
 11using Microsoft.AspNetCore.Http;
 12using Microsoft.Extensions.Options;
 13using Elsa.Http.Options;
 14
 15namespace Elsa.Http;
 16
 17/// <summary>
 18/// Write a response to the current HTTP response object.
 19/// </summary>
 20[Activity("Elsa", "HTTP", "Write a response to the current HTTP response object.", DisplayName = "HTTP Response")]
 21public class WriteHttpResponse : Activity
 22{
 23    /// <inheritdoc />
 5224    public WriteHttpResponse([CallerFilePath] string? source = null, [CallerLineNumber] int? line = null) : base(source,
 25    {
 5226    }
 27
 28    /// <inheritdoc />
 029    public WriteHttpResponse(Input<object?> content, [CallerFilePath] string? source = null, [CallerLineNumber] int? lin
 30    {
 031        Content = content;
 032    }
 33
 34    /// <inheritdoc />
 035    public WriteHttpResponse(Input<object?> content, Input<string?> contentType, [CallerFilePath] string? source = null,
 36    {
 037        Content = content;
 038        ContentType = contentType;
 039    }
 40
 41    /// <summary>
 42    /// The status code to return.
 43    /// </summary>
 44    [Input(
 45        DefaultValue = HttpStatusCode.OK,
 46        Description = "The status code to return.",
 47        UIHint = InputUIHints.DropDown
 48    )]
 19849    public Input<HttpStatusCode> StatusCode { get; set; } = new(HttpStatusCode.OK);
 50
 51    /// <summary>
 52    /// The content to write back.
 53    /// </summary>
 54    [Input(Description = "The content to write back. String values will be sent as-is, while objects will be serialized 
 16455    public Input<object?> Content { get; set; } = null!;
 56
 57    /// <summary>
 58    /// The content type to use when returning the response.
 59    /// </summary>
 60    [Input(
 61        Description = "The content type to write when sending the response.",
 62        UIHandler = typeof(HttpContentTypeOptionsProvider),
 63        UIHint = InputUIHints.DropDown
 64    )]
 14965    public Input<string?> ContentType { get; set; } = null!;
 66
 67    /// <summary>
 68    /// The headers to return along with the response.
 69    /// </summary>
 70    [Input(
 71        Description = "The headers to send along with the response.",
 72        UIHint = InputUIHints.JsonEditor,
 73        Category = "Advanced"
 74    )]
 19775    public Input<HttpHeaders?> ResponseHeaders { get; set; } = new(new HttpHeaders());
 76
 77    /// <inheritdoc />
 78    protected override async ValueTask ExecuteAsync(ActivityExecutionContext context)
 79    {
 2080        var httpContextAccessor = context.GetRequiredService<IHttpContextAccessor>();
 2081        var httpContext = httpContextAccessor.HttpContext;
 82
 2083        if (httpContext == null)
 84        {
 85            // We're executing in a non-HTTP context (e.g. in a virtual actor).
 86            // Create a bookmark to allow the invoker to export the state and resume execution from there.
 187            context.CreateBookmark(OnResumeAsync, BookmarkMetadata.HttpCrossBoundary);
 188            return;
 89        }
 90
 1991        await WriteResponseAsync(context, httpContext.Response);
 2092    }
 93
 94    private async ValueTask OnResumeAsync(ActivityExecutionContext context)
 95    {
 096        var httpContextAccessor = context.GetRequiredService<IHttpContextAccessor>();
 097        var httpContext = httpContextAccessor.HttpContext;
 98
 099        if (httpContext == null)
 100        {
 101            // We're not in an HTTP context, so let's fail.
 0102            throw new FaultException(HttpFaultCodes.NoHttpContext, HttpFaultCategories.Http, DefaultFaultTypes.System, "
 103        }
 104
 0105        await WriteResponseAsync(context, httpContext.Response);
 0106    }
 107
 108    private async Task WriteResponseAsync(ActivityExecutionContext context, HttpResponse response)
 109    {
 110        // Set status code.
 19111        var statusCode = StatusCode.GetOrDefault(context, () => HttpStatusCode.OK);
 19112        response.StatusCode = (int)statusCode;
 113
 114        // Add headers.
 19115        var headers = context.GetHeaders(ResponseHeaders);
 50116        foreach (var header in headers)
 6117            response.Headers[header.Key] = header.Value;
 118
 119        // Get content and content type.
 19120        var content = context.Get(Content);
 121
 19122        if (content != null)
 123        {
 7124            var contentType = ContentType.GetOrDefault(context);
 125
 7126            if (string.IsNullOrWhiteSpace(contentType))
 3127                contentType = DetermineContentType(content);
 128
 7129            var factories = context.GetServices<IHttpContentFactory>();
 34130            var factory = factories.FirstOrDefault(httpContentFactory => httpContentFactory.SupportedContentTypes.Any(c 
 7131            var httpContent = factory.CreateHttpContent(content, contentType);
 132
 133            // Set content type.
 7134            response.ContentType = httpContent.Headers.ContentType?.ToString() ?? contentType;
 135
 136            // Write content.
 7137            if (statusCode != HttpStatusCode.NoContent)
 138            {
 139                try
 140                {
 6141                    await httpContent.CopyToAsync(response.Body);
 6142                }
 143                catch (NotSupportedException)
 144                {
 145                    // This can happen the Content property is a type that cannot be serialized or contains a type that 
 0146                    await response.WriteAsync("The response includes a type that cannot be serialized.");
 147                }
 148            }
 149        }
 150
 151        // Check if the configuration is set to flush immediatly the response to the caller.
 19152        var options = context.GetRequiredService<IOptions<HttpActivityOptions>>();
 19153        if (options.Value.WriteHttpResponseSynchronously)
 1154            await response.CompleteAsync();
 155
 156        // Complete activity.
 19157        await context.CompleteActivityAsync();
 19158    }
 159
 3160    private string DetermineContentType(object? content) => content is byte[] or Stream
 3161        ? "application/octet-stream"
 3162        : content is string
 3163            ? "text/plain"
 3164            : "application/json";
 165}