HTTP Response Activity (#16)
* Updated Readme with a few more bits * Implemented basic version of HTTP Response activity
This commit is contained in:
parent
bf7a70e7c4
commit
009bceff6f
|
|
@ -1,10 +1,13 @@
|
|||

|
||||
|
||||
## Elsa Workflows
|
||||
|
||||
Elsa Workflows is a set of lean & mean workflow components that you can use in your .NET Core applications.
|
||||
With Elsa, you can invoke and trigger workflows from your own application. Workflows can be expressed as JSON, YAML, XML or in code.
|
||||
|
||||
You can manually handcraft workflows or use the web-based workflow designer.
|
||||
|
||||

|
||||

|
||||
|
||||
## Why Elsa Workflows?
|
||||
|
||||
|
|
@ -112,3 +115,4 @@ In order to run Elsa on your local machine, follow these steps:
|
|||
- Implement more activities (fork, join, script, HTTP request, loops, etc.)
|
||||
- Implement integration with Orchard Core (separate repo)
|
||||
- Detailed documentation
|
||||
|
||||
|
|
|
|||
BIN
doc/elsa-cover.png
Normal file
BIN
doc/elsa-cover.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 963 KiB |
|
|
@ -19,13 +19,18 @@ namespace Elsa.Activities.Http.Activities
|
|||
public string Method { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The body to send along with the request
|
||||
/// The body to send along with the request.
|
||||
/// </summary>
|
||||
public WorkflowExpression<string> Body { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The body to send along with the request
|
||||
/// The headers to send along with the request.
|
||||
/// </summary>
|
||||
public IDictionary<string, string> RequestHeaders { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A list of HTTP status codes this activity cam handle.
|
||||
/// </summary>
|
||||
public ICollection<int> SupportedStatusCodes { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using Elsa.Expressions;
|
||||
using Elsa.Models;
|
||||
|
||||
namespace Elsa.Activities.Http.Activities
|
||||
{
|
||||
public class HttpResponseAction : Activity
|
||||
{
|
||||
/// <summary>
|
||||
/// The HTTP status code to return.
|
||||
/// </summary>
|
||||
public HttpStatusCode StatusCode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The body to send along with the response
|
||||
/// </summary>
|
||||
public WorkflowExpression<string> Body { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The Content-Type header to send along with the response.
|
||||
/// </summary>
|
||||
public WorkflowExpression<string> ContentType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The headers to send along with the response, one header: value pair per line.
|
||||
/// </summary>
|
||||
public WorkflowExpression<string> ResponseHeaders { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,8 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Elsa.Activities.Http.Activities;
|
||||
using Elsa.Models;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Localization;
|
||||
|
||||
namespace Elsa.Activities.Http
|
||||
|
|
@ -23,10 +25,18 @@ namespace Elsa.Activities.Http
|
|||
T["Triggers when an incoming HTTP request is received."],
|
||||
T["Done"]);
|
||||
|
||||
yield return ActivityDescriptor.ForAction<HttpRequestAction>(
|
||||
yield return ActivityDescriptor.For<HttpRequestAction>(
|
||||
Category,
|
||||
T["HTTP Request"],
|
||||
T["Execute a HTTP request."],
|
||||
true,
|
||||
true,
|
||||
a => a.SupportedStatusCodes.Select(x => T[x.ToString()]));
|
||||
|
||||
yield return ActivityDescriptor.ForAction<HttpResponseAction>(
|
||||
Category,
|
||||
T["HTTP Response"],
|
||||
T["Write a HTTP response."],
|
||||
T["Done"]);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,59 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Elsa.Activities.Http.Activities;
|
||||
using Elsa.Expressions;
|
||||
using Elsa.Handlers;
|
||||
using Elsa.Models;
|
||||
using Elsa.Results;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Elsa.Activities.Http.Drivers
|
||||
{
|
||||
public class HttpResponseActionDriver : ActivityDriver<HttpResponseAction>
|
||||
{
|
||||
private readonly IWorkflowExpressionEvaluator expressionEvaluator;
|
||||
private readonly IHttpContextAccessor httpContextAccessor;
|
||||
|
||||
public HttpResponseActionDriver(IWorkflowExpressionEvaluator expressionEvaluator, IHttpContextAccessor httpContextAccessor)
|
||||
{
|
||||
this.expressionEvaluator = expressionEvaluator;
|
||||
this.httpContextAccessor = httpContextAccessor;
|
||||
}
|
||||
|
||||
protected override async Task<ActivityExecutionResult> OnExecuteAsync(HttpResponseAction activity, WorkflowExecutionContext workflowContext, CancellationToken cancellationToken)
|
||||
{
|
||||
var response = httpContextAccessor.HttpContext.Response;
|
||||
|
||||
response.StatusCode = (int) activity.StatusCode;
|
||||
response.ContentType = await expressionEvaluator.EvaluateAsync(activity.ContentType, workflowContext, cancellationToken);
|
||||
|
||||
var headersText = await expressionEvaluator.EvaluateAsync(activity.ResponseHeaders, workflowContext, cancellationToken);
|
||||
|
||||
if (headersText != null)
|
||||
{
|
||||
var headersQuery =
|
||||
from line in Regex.Split(headersText, "\\n", RegexOptions.Multiline)
|
||||
let pair = line.Split(':', '=')
|
||||
select new KeyValuePair<string, string>(pair[0], pair[1]);
|
||||
|
||||
foreach (var header in headersQuery)
|
||||
{
|
||||
var headerValueExpression = new WorkflowExpression<string>(activity.ResponseHeaders.Syntax, header.Value);
|
||||
response.Headers[header.Key] = await expressionEvaluator.EvaluateAsync(headerValueExpression, workflowContext, cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
var bodyText = await expressionEvaluator.EvaluateAsync(activity.Body, workflowContext, cancellationToken);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(bodyText))
|
||||
{
|
||||
await response.WriteAsync(bodyText, cancellationToken);
|
||||
}
|
||||
|
||||
return TriggerEndpoint("Done");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -20,12 +20,15 @@ namespace Elsa.Activities.Http.Extensions
|
|||
public static IServiceCollection AddHttpWorkflowDrivers(this IServiceCollection services)
|
||||
{
|
||||
services.AddHttpWorkflowDescriptors();
|
||||
services.TryAddSingleton<IHttpWorkflowCache, DefaultHttpWorkflowCache>();
|
||||
services.AddAsyncInitialization();
|
||||
services.TryAddSingleton<IHttpWorkflowCache, DefaultHttpWorkflowCache>();
|
||||
services.TryAddTransient<IAsyncInitializer, HttpWorkflowCacheInitializer>();
|
||||
services.AddActivityDriver<HttpRequestTriggerDriver>();
|
||||
services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
|
||||
|
||||
services
|
||||
.AddActivityDriver<HttpRequestTriggerDriver>()
|
||||
.AddActivityDriver<HttpResponseActionDriver>();
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,6 +34,15 @@ namespace Elsa.Models
|
|||
params LocalizedString[] endpoints) =>
|
||||
new ActivityDescriptor(typeof(T), category, displayText, description, isTrigger, isBrowsable, endpoints);
|
||||
|
||||
public static ActivityDescriptor For<T>(
|
||||
LocalizedString category,
|
||||
LocalizedString displayText,
|
||||
LocalizedString description,
|
||||
bool isTrigger,
|
||||
bool isBrowsable,
|
||||
Func<T, IEnumerable<LocalizedString>> getEndpoints) where T : IActivity =>
|
||||
new ActivityDescriptor(typeof(T), category, displayText, description, isTrigger, isBrowsable, a => getEndpoints((T) a));
|
||||
|
||||
public ActivityDescriptor()
|
||||
{
|
||||
}
|
||||
|
|
@ -46,15 +55,34 @@ namespace Elsa.Models
|
|||
bool isTrigger,
|
||||
bool isBrowsable,
|
||||
params LocalizedString[] endpoints)
|
||||
: this(
|
||||
activityType,
|
||||
category,
|
||||
displayText,
|
||||
description,
|
||||
isTrigger,
|
||||
isBrowsable,
|
||||
a => endpoints)
|
||||
{
|
||||
}
|
||||
|
||||
public ActivityDescriptor(
|
||||
Type activityType,
|
||||
LocalizedString category,
|
||||
LocalizedString displayText,
|
||||
LocalizedString description,
|
||||
bool isTrigger,
|
||||
bool isBrowsable,
|
||||
Func<IActivity, IEnumerable<LocalizedString>> getEndpoints)
|
||||
{
|
||||
ActivityType = activityType;
|
||||
Name = activityType.Name;
|
||||
Category = category;
|
||||
DisplayText = displayText;
|
||||
Description = description;
|
||||
GetEndpoints = a => endpoints;
|
||||
IsTrigger = isTrigger;
|
||||
IsBrowsable = isBrowsable;
|
||||
GetEndpoints = getEndpoints;
|
||||
}
|
||||
|
||||
public bool IsBrowsable { get; set; } = true;
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Jint" Version="3.0.0-beta-1457" />
|
||||
<PackageReference Include="Jint" Version="3.0.0-beta-1466" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="2.1.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Localization.Abstractions" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="2.1.1" />
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
@model Elsa.Web.ViewModels.ActivityShapeModel<Elsa.Activities.Console.Activities.ReadLine>
|
||||
@model ActivityShapeModel<ReadLine>
|
||||
@{
|
||||
var activityDescriptor = Model.Activity.Descriptor;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
@model Elsa.Web.ViewModels.ActivityShapeModel<Elsa.Activities.Console.Activities.ReadLine>
|
||||
@model ActivityShapeModel<ReadLine>
|
||||
|
||||
<header>
|
||||
<h4><i class="fa fa-glasses"></i>@Model.DisplayText</h4>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
@model Elsa.Web.ViewModels.ActivityShapeModel<Elsa.Activities.Console.Activities.WriteLine>
|
||||
@model ActivityShapeModel<WriteLine>
|
||||
@{
|
||||
var activityDescriptor = Model.Activity.Descriptor;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
@model Elsa.Web.ViewModels.ActivityShapeModel<Elsa.Activities.Console.Activities.WriteLine>
|
||||
@model ActivityShapeModel<WriteLine>
|
||||
|
||||
<header>
|
||||
<h4><i class="fa fa-pen"></i>@Model.DisplayText</h4>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
@model Elsa.Web.Activities.Console.ViewModels.WriteLineViewModel
|
||||
@model WriteLineViewModel
|
||||
<div class="form-group">
|
||||
<label asp-for="TextExpression">@T["Text Expression"]</label>
|
||||
<input asp-for="TextExpression" class="form-control"/>
|
||||
|
|
|
|||
|
|
@ -1,2 +1,5 @@
|
|||
@using Elsa.Activities.Console.Activities
|
||||
@using Elsa.Web.Activities.Console.ViewModels
|
||||
@using Elsa.Web.ViewModels
|
||||
@inherits OrchardCore.DisplayManagement.Razor.RazorPage<TModel>
|
||||
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
using System.Net;
|
||||
using Elsa.Activities.Http.Activities;
|
||||
using Elsa.Web.Activities.Http.ViewModels;
|
||||
using Elsa.Web.Components.ViewModels;
|
||||
using Elsa.Web.Drivers;
|
||||
|
||||
namespace Elsa.Web.Activities.Http.Display
|
||||
{
|
||||
public class HttpResponseActionDisplay : ActivityDisplayDriver<HttpResponseAction, HttpResponseActionViewModel>
|
||||
{
|
||||
protected override void EditActivity(HttpResponseAction activity, HttpResponseActionViewModel model)
|
||||
{
|
||||
model.StatusCode = (int)activity.StatusCode;
|
||||
model.Body = new ExpressionViewModel(activity.Body);
|
||||
model.ResponseHeaders = new ExpressionViewModel(activity.ResponseHeaders);
|
||||
model.ContentType = new ExpressionViewModel(activity.ContentType);
|
||||
}
|
||||
|
||||
protected override void UpdateActivity(HttpResponseActionViewModel model, HttpResponseAction activity)
|
||||
{
|
||||
activity.StatusCode = (HttpStatusCode)model.StatusCode;
|
||||
activity.Body = model.Body.ToWorkflowExpression<string>();
|
||||
activity.ResponseHeaders = model.ResponseHeaders.ToWorkflowExpression<string>();
|
||||
activity.ContentType = model.ContentType.ToWorkflowExpression<string>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -13,7 +13,8 @@ namespace Elsa.Web.Activities.Http
|
|||
services
|
||||
.AddHttpWorkflowDescriptors()
|
||||
.AddActivityDisplay<HttpRequestTriggerDisplay>()
|
||||
.AddActivityDisplay<HttpRequestActionDisplay>();
|
||||
.AddActivityDisplay<HttpRequestActionDisplay>()
|
||||
.AddActivityDisplay<HttpResponseActionDisplay>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
using Elsa.Expressions;
|
||||
using Elsa.Web.Components.ViewModels;
|
||||
|
||||
namespace Elsa.Web.Activities.Http.ViewModels
|
||||
{
|
||||
public class HttpResponseActionViewModel
|
||||
{
|
||||
public HttpResponseActionViewModel()
|
||||
{
|
||||
StatusCode = 200;
|
||||
ContentType = new ExpressionViewModel("application/json", PlainTextEvaluator.SyntaxName);
|
||||
}
|
||||
|
||||
public int StatusCode { get; set; }
|
||||
public ExpressionViewModel Body { get; set; }
|
||||
public ExpressionViewModel ContentType { get; set; }
|
||||
public ExpressionViewModel ResponseHeaders { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
@model ActivityShapeModel<HttpResponseAction>
|
||||
@{
|
||||
var activityDescriptor = Model.Activity.Descriptor;
|
||||
}
|
||||
<h4 class="card-title"><i class="fas fa-globe"></i>@activityDescriptor.DisplayText</h4>
|
||||
<p>@activityDescriptor.Description</p>
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
@model ActivityShapeModel<HttpResponseAction>
|
||||
|
||||
<header>
|
||||
<h4><i class="fas fa-globe"></i>@Model.DisplayText</h4>
|
||||
</header>
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
@model HttpResponseActionViewModel
|
||||
<div class="form-group">
|
||||
<label asp-for="StatusCode">@T["HTTP Status Code"]</label>
|
||||
<input asp-for="StatusCode" class="form-control" type="number" min="0" max="999"/>
|
||||
<span asp-validation-for="StatusCode" class="text-danger"></span>
|
||||
<small class="form-text text-muted">@T["The HTTP status code to return."]</small>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label asp-for="Body">@T["Body"]</label>
|
||||
<vc:expression-editor model="@Model.Body" prefix="@Html.NameFor(m => m.Body)"></vc:expression-editor>
|
||||
<span asp-validation-for="Body" class="text-danger"></span>
|
||||
<small class="form-text text-muted">@T["The HTTP request body to send along with the response."]</small>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label asp-for="ContentType">@T["Content Type"]</label>
|
||||
<vc:expression-editor model="@Model.ContentType" prefix="@Html.NameFor(m => m.ContentType)"></vc:expression-editor>
|
||||
<span asp-validation-for="ContentType" class="text-danger"></span>
|
||||
<small class="form-text text-muted">@T["The Content-Type header value to return."]</small>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label asp-for="ResponseHeaders">@T["Response Headers"]</label>
|
||||
<vc:expression-editor model="@Model.ResponseHeaders" prefix="@Html.NameFor(m => m.ResponseHeaders)"></vc:expression-editor>
|
||||
<span asp-validation-for="ResponseHeaders" class="text-danger"></span>
|
||||
<small class="form-text text-muted">@T["Any additional header to return, one per line. For example: my-header-one=foo\r\nmy-header-two=bar"]</small>
|
||||
</div>
|
||||
|
|
@ -7,11 +7,16 @@ namespace Elsa.Web.Components.ViewModels
|
|||
public ExpressionViewModel()
|
||||
{
|
||||
}
|
||||
|
||||
public ExpressionViewModel(WorkflowExpression workflowExpression)
|
||||
|
||||
public ExpressionViewModel(string expression, string syntax)
|
||||
{
|
||||
Expression = expression;
|
||||
Syntax = syntax;
|
||||
}
|
||||
|
||||
public ExpressionViewModel(WorkflowExpression workflowExpression)
|
||||
: this(workflowExpression?.Expression, workflowExpression?.Syntax)
|
||||
{
|
||||
Expression = workflowExpression?.Expression;
|
||||
Syntax = workflowExpression?.Syntax;
|
||||
}
|
||||
|
||||
public string Expression { get; set; }
|
||||
|
|
|
|||
Loading…
Reference in a new issue