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

153 lines
5.6 KiB
C#
Raw Normal View History

2022-01-04 08:42:12 +00:00
using System.Net;
using System.Runtime.CompilerServices;
using Elsa.Extensions;
2023-01-24 20:48:05 +00:00
using Elsa.Http.ContentWriters;
Add a more generic UIHandler to customize how inputAttributes can be handle by UI (#4688) * add a more generic UIHandler to customize how inputAttributes can be handle by the ui * Add IPropertyUIHandlerResolver and update PropertyUIHandlerResolver Introduced a new interface, IPropertyUIHandlerResolver, to resolve UI options for a property. Refactored PropertyUIHandlerResolver to implement this interface and removed the unnecessary partial class structure. Also, cleaned up some unnecessary usings in various files for better code organization. * Refactor variable name and description in InputDescriptor The 'uISpecifications' variable in the InputDescriptor model is renamed to 'uiSpecifications' for better readability. Additionally, the associated comment was revised to explain that the dictionary is used by the UI. * "Refactor codebase for improved organization and cleaner architecture" The codebase has been significantly refactored, moving several classes to more appropriate namespaces for improved organization and cleaner architecture. This includes shifting UI hint handlers, activities, and memory-related components, amongst others. The changes should improve code readability and maintainability, but as this is a broad refactoring effort, thorough regression testing is advised. * Add CheckList UIHint with associated handler and provider This update introduces a new UIHint called CheckList to the Elsa.Workflows.Core. This includes the necessary handler and provider classes. The handler is registered in the WorkflowsFeature.cs, and the CheckList UIHint key has been added to the InputUIHints.cs. Various associated files have been created in both the Elsa.Api.Client and Elsa.Workflows.Core project to support this new UIHint. --------- Co-authored-by: Jérémie DEVILLARD <jdevillard@users.noreply.github.com> Co-authored-by: Sipke Schoorstra <sipkeschoorstra@outlook.com>
2023-12-26 17:56:29 +00:00
using Elsa.Http.UIHints;
using Elsa.Workflows;
using Elsa.Workflows.Attributes;
using Elsa.Workflows.UIHints;
using Elsa.Workflows.Exceptions;
using Elsa.Workflows.Models;
2022-01-04 08:42:12 +00:00
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Options;
using Elsa.Http.Options;
2022-01-04 08:42:12 +00:00
namespace Elsa.Http;
2022-01-04 08:42:12 +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")]
public class WriteHttpResponse : Activity
2022-01-04 08:42:12 +00:00
{
/// <inheritdoc />
public WriteHttpResponse([CallerFilePath] string? source = null, [CallerLineNumber] int? line = null) : base(source, line)
{
}
/// <summary>
/// The status code to return.
/// </summary>
[Input(
DefaultValue = HttpStatusCode.OK,
Description = "The status code to return.",
UIHint = InputUIHints.DropDown
)]
2022-01-04 08:42:12 +00:00
public Input<HttpStatusCode> StatusCode { get; set; } = new(HttpStatusCode.OK);
/// <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; } = null!;
2022-01-04 08:42:12 +00:00
/// <summary>
/// The content type to use when returning the response.
/// </summary>
[Input(
Description = "The content type to write when sending the response.",
Add a more generic UIHandler to customize how inputAttributes can be handle by UI (#4688) * add a more generic UIHandler to customize how inputAttributes can be handle by the ui * Add IPropertyUIHandlerResolver and update PropertyUIHandlerResolver Introduced a new interface, IPropertyUIHandlerResolver, to resolve UI options for a property. Refactored PropertyUIHandlerResolver to implement this interface and removed the unnecessary partial class structure. Also, cleaned up some unnecessary usings in various files for better code organization. * Refactor variable name and description in InputDescriptor The 'uISpecifications' variable in the InputDescriptor model is renamed to 'uiSpecifications' for better readability. Additionally, the associated comment was revised to explain that the dictionary is used by the UI. * "Refactor codebase for improved organization and cleaner architecture" The codebase has been significantly refactored, moving several classes to more appropriate namespaces for improved organization and cleaner architecture. This includes shifting UI hint handlers, activities, and memory-related components, amongst others. The changes should improve code readability and maintainability, but as this is a broad refactoring effort, thorough regression testing is advised. * Add CheckList UIHint with associated handler and provider This update introduces a new UIHint called CheckList to the Elsa.Workflows.Core. This includes the necessary handler and provider classes. The handler is registered in the WorkflowsFeature.cs, and the CheckList UIHint key has been added to the InputUIHints.cs. Various associated files have been created in both the Elsa.Api.Client and Elsa.Workflows.Core project to support this new UIHint. --------- Co-authored-by: Jérémie DEVILLARD <jdevillard@users.noreply.github.com> Co-authored-by: Sipke Schoorstra <sipkeschoorstra@outlook.com>
2023-12-26 17:56:29 +00:00
UIHandler = typeof(HttpContentTypeOptionsProvider),
UIHint = InputUIHints.DropDown
)]
public Input<string?> ContentType { get; set; } = null!;
/// <summary>
/// The headers to return along with the response.
/// </summary>
[Input(
Description = "The headers to send along with the response.",
UIHint = InputUIHints.JsonEditor,
Category = "Advanced"
)]
public Input<HttpHeaders?> ResponseHeaders { get; set; } = new(new HttpHeaders());
/// <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;
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, BookmarkMetadata.HttpCrossBoundary);
return;
}
await WriteResponseAsync(context, httpContext.Response);
}
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 FaultException(HttpFaultCodes.NoHttpContext, HttpFaultCategories.Http, DefaultFaultTypes.System, "Cannot execute in a non-HTTP context");
}
await WriteResponseAsync(context, httpContext.Response);
}
2022-01-04 08:42:12 +00:00
private async Task WriteResponseAsync(ActivityExecutionContext context, HttpResponse response)
{
2023-01-24 20:48:05 +00:00
// Set status code.
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.
var headers = context.GetHeaders(ResponseHeaders);
foreach (var header in headers)
response.Headers[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);
if (content != null)
{
var contentType = ContentType.GetOrDefault(context);
if (string.IsNullOrWhiteSpace(contentType))
contentType = DetermineContentType(content);
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
// Set content type.
response.ContentType = httpContent.Headers.ContentType?.ToString() ?? contentType;
// Write content.
if (statusCode != HttpStatusCode.NoContent)
{
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.");
}
}
Implement multitenant HTTP routing (#6031) * Add tenant awareness to bookmark handling and route resolution Added tenant ID support across various components, including bookmark updates, route resolution, and middleware processing. This ensures that bookmark and route operations can now appropriately handle tenant-specific data, improving the system's multitenancy capabilities. * Add Multitenant HTTP Routing feature to Tenants module Introduced a new MultitenantHttpRoutingFeature class to the Elsa.Tenants.AspNetCore module, enhancing the tenant resolution capabilities. Moved RoutePrefixTenantResolver from Elsa.Http to Elsa.Tenants.AspNetCore and updated relevant project references and namespaces accordingly. This refactor improves modularity and separation of concerns between HTTP and tenancy features. * Refactor route handling and tenant configuration Removed redundant `RouteTableExtensions` and replaced with new route providers and updaters, enhancing flexibility and modularity. Introduced tenant-specific HTTP endpoint configurations for better customization and configuration management. * Rename HttpEndpointBookmarkStimulus to HttpEndpointBookmarkPayload Refactor various classes and methods to reflect the renaming from `HttpEndpointBookmarkStimulus` to `HttpEndpointBookmarkPayload`. Add and configure new extension methods for tenant route handling, update the route provider to support multi-tenancy, and adjust the tenants provider to bind configuration properly. * Add HeaderTenantResolver and refactor Http namespace. Introduce HeaderTenantResolver to resolve tenants via HTTP headers. Refactor multiple classes and interfaces to move from the Elsa.Http.Models namespace directly into Elsa.Http for clarity and consistency. * Add Host-based tenant resolution Implemented a HostTenantResolver to resolve tenants based on the request's host and updated tenant configurations with host information. Modified the tenant resolver pipeline and added the new host resolver to the service registrations. * Add tenant-aware caching and accessor support Enhanced caching by incorporating tenant identifiers into cache keys for more granular cache management. Introduced ITenantAccessor dependencies in various services to retrieve the current tenant information. This ensures that cache entries are correctly isolated per tenant. * Reorder tenant resolvers for pipeline setup. Reordered the tenant resolvers in the pipeline to prioritize HostTenantResolver before RoutePrefixTenantResolver. This ensures that tenant resolution is correctly aligned with host-based resolving before checking the route prefix. * Remove unused imports This commit eliminates redundant `using` directives across multiple files to streamline the codebase. This cleanup helps improve code readability and maintainability by removing unnecessary dependencies.
2024-10-14 19:27:11 +00:00
}
// Check if the configuration is set to flush immediatly the response to the caller.
var options = context.GetRequiredService<IOptions<HttpActivityOptions>>();
if (options.Value.WriteHttpResponseSynchronously)
await response.CompleteAsync();
// Complete activity.
await context.CompleteActivityAsync();
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"
: content is string
? "text/plain"
: "application/json";
}