Update HTTP module with route data support
This commit is contained in:
parent
6f9c6fd87d
commit
5d5cb8ed68
|
|
@ -15,7 +15,13 @@
|
|||
<PackageReference Include="Microsoft.AspNetCore.Authorization" Version="6.0.3" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Http" Version="2.2.2" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Http.Extensions" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Routing" Version="2.2.2" />
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="6.0.0" />
|
||||
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Scripting" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,31 @@
|
|||
// Taken & adapted from https://github.com/markvincze/Stubbery/blob/main/src/Stubbery/RequestMatching/RouteMatcher.cs
|
||||
|
||||
using Elsa.Modules.Http.Services;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.AspNetCore.Routing.Template;
|
||||
|
||||
namespace Elsa.Modules.Http.Implementations
|
||||
{
|
||||
public class RouteMatcher : IRouteMatcher
|
||||
{
|
||||
public RouteValueDictionary? Match(string routeTemplate, string requestPath)
|
||||
{
|
||||
var template = TemplateParser.Parse(routeTemplate);
|
||||
var matcher = new TemplateMatcher(template, GetDefaults(template));
|
||||
var values = new RouteValueDictionary();
|
||||
|
||||
return matcher.TryMatch(requestPath, values) ? values : null;
|
||||
}
|
||||
|
||||
private static RouteValueDictionary GetDefaults(RouteTemplate parsedTemplate)
|
||||
{
|
||||
var result = new RouteValueDictionary();
|
||||
|
||||
foreach (var parameter in parsedTemplate.Parameters)
|
||||
if (parameter.DefaultValue != null)
|
||||
result.Add(parameter.Name, parameter.DefaultValue);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
31
src/modules/Elsa.Modules.Http/Implementations/RouteTable.cs
Normal file
31
src/modules/Elsa.Modules.Http/Implementations/RouteTable.cs
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
using System.Collections;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using Elsa.Modules.Http.Services;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
|
||||
namespace Elsa.Modules.Http.Implementations;
|
||||
|
||||
public class RouteTable : IRouteTable
|
||||
{
|
||||
private static readonly object Key = new();
|
||||
private readonly IMemoryCache _cache;
|
||||
|
||||
public RouteTable(IMemoryCache cache) => _cache = cache;
|
||||
private ConcurrentDictionary<string, string> Routes => _cache.GetOrCreate(Key, _ => new ConcurrentDictionary<string, string>());
|
||||
public void Add(string path) => Routes.TryAdd(path, path);
|
||||
public void Remove(string path) => Routes.TryRemove(path, out _);
|
||||
|
||||
public void AddRange(IEnumerable<string> paths)
|
||||
{
|
||||
foreach (var path in paths) Add(path);
|
||||
}
|
||||
|
||||
public void RemoveRange(IEnumerable<string> paths)
|
||||
{
|
||||
foreach (var path in paths) Remove(path);
|
||||
}
|
||||
|
||||
public IEnumerator<string> GetEnumerator() => Routes.Values.GetEnumerator();
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
}
|
||||
|
|
@ -3,14 +3,18 @@ using System.Collections.Generic;
|
|||
using System.Linq;
|
||||
using System.Net.Mime;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Elsa.Helpers;
|
||||
using Elsa.Modules.Http.Models;
|
||||
using Elsa.Modules.Http.Services;
|
||||
using Elsa.Runtime.Models;
|
||||
using Elsa.Runtime.Services;
|
||||
using Elsa.Services;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http.Extensions;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Elsa.Modules.Http.Middleware;
|
||||
|
||||
|
|
@ -25,7 +29,7 @@ public class HttpTriggerMiddleware
|
|||
_hasher = hasher;
|
||||
}
|
||||
|
||||
public async Task InvokeAsync(HttpContext httpContext, IWorkflowService workflowService)
|
||||
public async Task InvokeAsync(HttpContext httpContext, IWorkflowService workflowService, IRouteMatcher routeMatcher)
|
||||
{
|
||||
var path = GetPath(httpContext);
|
||||
var request = httpContext.Request;
|
||||
|
|
@ -33,7 +37,17 @@ public class HttpTriggerMiddleware
|
|||
var abortToken = httpContext.RequestAborted;
|
||||
var hash = _hasher.Hash(new HttpBookmarkData(path, method));
|
||||
var activityTypeName = TypeNameHelper.GenerateTypeName<HttpEndpoint>();
|
||||
var requestModel = new HttpRequestModel(new Uri(request.GetEncodedUrl()));
|
||||
var routeData = GetRouteData(httpContext, routeMatcher, path);
|
||||
|
||||
var requestModel = new HttpRequestModel(
|
||||
new Uri(request.GetEncodedUrl()),
|
||||
request.Path,
|
||||
request.Method,
|
||||
request.Query.ToDictionary(x => x.Key, x => x.Value.ToString()),
|
||||
routeData.Values,
|
||||
request.Headers.ToDictionary(x => x.Key, x => x.Value.ToString())
|
||||
);
|
||||
|
||||
var input = new Dictionary<string, object>() { [HttpEndpoint.InputKey] = requestModel };
|
||||
var stimulus = Stimulus.Standard(activityTypeName, hash, input);
|
||||
var executionResults = (await workflowService.ExecuteStimulusAsync(stimulus, abortToken)).ToList();
|
||||
|
|
@ -44,6 +58,11 @@ public class HttpTriggerMiddleware
|
|||
return;
|
||||
}
|
||||
|
||||
await WriteResponseAsync(httpContext, executionResults, abortToken);
|
||||
}
|
||||
|
||||
private static async Task WriteResponseAsync(HttpContext httpContext, IEnumerable<ExecuteWorkflowInstructionResult> executionResults, CancellationToken cancellationToken)
|
||||
{
|
||||
var response = httpContext.Response;
|
||||
|
||||
if (!response.HasStarted)
|
||||
|
|
@ -57,9 +76,31 @@ public class HttpTriggerMiddleware
|
|||
};
|
||||
|
||||
var json = JsonSerializer.Serialize(model);
|
||||
await response.WriteAsync(json, abortToken);
|
||||
await response.WriteAsync(json, cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
private static RouteData GetRouteData(HttpContext httpContext, IRouteMatcher routeMatcher, string path)
|
||||
{
|
||||
var routeData = httpContext.GetRouteData();
|
||||
var routeTable = httpContext.RequestServices.GetRequiredService<IRouteTable>();
|
||||
|
||||
var matchingRouteQuery =
|
||||
from route in routeTable
|
||||
let routeValues = routeMatcher.Match(route, path)
|
||||
where routeValues != null
|
||||
select new { route, routeValues };
|
||||
|
||||
var matchingRoute = matchingRouteQuery.FirstOrDefault();
|
||||
|
||||
if (matchingRoute == null)
|
||||
return routeData;
|
||||
|
||||
foreach (var (key, value) in matchingRoute.routeValues!)
|
||||
routeData.Values[key] = value;
|
||||
|
||||
return routeData;
|
||||
}
|
||||
|
||||
private string GetPath(HttpContext httpContext) => httpContext.Request.Path.Value.ToLowerInvariant();
|
||||
}
|
||||
|
|
@ -1,5 +1,13 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Elsa.Modules.Http.Models;
|
||||
|
||||
public record HttpRequestModel(Uri RequestUri);
|
||||
public record HttpRequestModel(
|
||||
Uri RequestUri,
|
||||
string Path,
|
||||
string Method,
|
||||
IDictionary<string, string> QueryString,
|
||||
IDictionary<string, object> RouteValues,
|
||||
IDictionary<string, string> Headers
|
||||
);
|
||||
14
src/modules/Elsa.Modules.Http/Services/IRouteMatcher.cs
Normal file
14
src/modules/Elsa.Modules.Http/Services/IRouteMatcher.cs
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
using Microsoft.AspNetCore.Routing;
|
||||
|
||||
namespace Elsa.Modules.Http.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Matches a given request path against the specified route template.
|
||||
/// </summary>
|
||||
public interface IRouteMatcher
|
||||
{
|
||||
/// <summary>
|
||||
/// Matches a given request path against the specified route template.
|
||||
/// </summary>
|
||||
RouteValueDictionary? Match(string routeTemplate, string requestPath);
|
||||
}
|
||||
14
src/modules/Elsa.Modules.Http/Services/IRouteTable.cs
Normal file
14
src/modules/Elsa.Modules.Http/Services/IRouteTable.cs
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace Elsa.Modules.Http.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Stores a list of all routes provided by <see cref="HttpEndpoint"/> activities.
|
||||
/// </summary>
|
||||
public interface IRouteTable : IEnumerable<string>
|
||||
{
|
||||
void Add(string path);
|
||||
void Remove(string path);
|
||||
void AddRange(IEnumerable<string> paths);
|
||||
void RemoveRange(IEnumerable<string> paths);
|
||||
}
|
||||
Loading…
Reference in a new issue