diff --git a/src/modules/Elsa.Modules.Http/Elsa.Modules.Http.csproj b/src/modules/Elsa.Modules.Http/Elsa.Modules.Http.csproj
index d80a392e1..a53b476b7 100644
--- a/src/modules/Elsa.Modules.Http/Elsa.Modules.Http.csproj
+++ b/src/modules/Elsa.Modules.Http/Elsa.Modules.Http.csproj
@@ -15,7 +15,13 @@
+
+
+
+
+
+
diff --git a/src/modules/Elsa.Modules.Http/Implementations/RouteMatcher.cs b/src/modules/Elsa.Modules.Http/Implementations/RouteMatcher.cs
new file mode 100644
index 000000000..67b26ba2d
--- /dev/null
+++ b/src/modules/Elsa.Modules.Http/Implementations/RouteMatcher.cs
@@ -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;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/modules/Elsa.Modules.Http/Implementations/RouteTable.cs b/src/modules/Elsa.Modules.Http/Implementations/RouteTable.cs
new file mode 100644
index 000000000..9018ea6cd
--- /dev/null
+++ b/src/modules/Elsa.Modules.Http/Implementations/RouteTable.cs
@@ -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 Routes => _cache.GetOrCreate(Key, _ => new ConcurrentDictionary());
+ public void Add(string path) => Routes.TryAdd(path, path);
+ public void Remove(string path) => Routes.TryRemove(path, out _);
+
+ public void AddRange(IEnumerable paths)
+ {
+ foreach (var path in paths) Add(path);
+ }
+
+ public void RemoveRange(IEnumerable paths)
+ {
+ foreach (var path in paths) Remove(path);
+ }
+
+ public IEnumerator GetEnumerator() => Routes.Values.GetEnumerator();
+ IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
+}
\ No newline at end of file
diff --git a/src/modules/Elsa.Modules.Http/Middleware/HttpTriggerMiddleware.cs b/src/modules/Elsa.Modules.Http/Middleware/HttpTriggerMiddleware.cs
index 05f5cce3f..d7f7f76d0 100644
--- a/src/modules/Elsa.Modules.Http/Middleware/HttpTriggerMiddleware.cs
+++ b/src/modules/Elsa.Modules.Http/Middleware/HttpTriggerMiddleware.cs
@@ -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();
- 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() { [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 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();
+
+ 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();
}
\ No newline at end of file
diff --git a/src/modules/Elsa.Modules.Http/Models/HttpRequestModel.cs b/src/modules/Elsa.Modules.Http/Models/HttpRequestModel.cs
index 1f616c640..ca02a2bc0 100644
--- a/src/modules/Elsa.Modules.Http/Models/HttpRequestModel.cs
+++ b/src/modules/Elsa.Modules.Http/Models/HttpRequestModel.cs
@@ -1,5 +1,13 @@
using System;
+using System.Collections.Generic;
namespace Elsa.Modules.Http.Models;
-public record HttpRequestModel(Uri RequestUri);
\ No newline at end of file
+public record HttpRequestModel(
+ Uri RequestUri,
+ string Path,
+ string Method,
+ IDictionary QueryString,
+ IDictionary RouteValues,
+ IDictionary Headers
+);
\ No newline at end of file
diff --git a/src/modules/Elsa.Modules.Http/Services/IRouteMatcher.cs b/src/modules/Elsa.Modules.Http/Services/IRouteMatcher.cs
new file mode 100644
index 000000000..e87e1045b
--- /dev/null
+++ b/src/modules/Elsa.Modules.Http/Services/IRouteMatcher.cs
@@ -0,0 +1,14 @@
+using Microsoft.AspNetCore.Routing;
+
+namespace Elsa.Modules.Http.Services;
+
+///
+/// Matches a given request path against the specified route template.
+///
+public interface IRouteMatcher
+{
+ ///
+ /// Matches a given request path against the specified route template.
+ ///
+ RouteValueDictionary? Match(string routeTemplate, string requestPath);
+}
\ No newline at end of file
diff --git a/src/modules/Elsa.Modules.Http/Services/IRouteTable.cs b/src/modules/Elsa.Modules.Http/Services/IRouteTable.cs
new file mode 100644
index 000000000..7073d1077
--- /dev/null
+++ b/src/modules/Elsa.Modules.Http/Services/IRouteTable.cs
@@ -0,0 +1,14 @@
+using System.Collections.Generic;
+
+namespace Elsa.Modules.Http.Services;
+
+///
+/// Stores a list of all routes provided by activities.
+///
+public interface IRouteTable : IEnumerable
+{
+ void Add(string path);
+ void Remove(string path);
+ void AddRange(IEnumerable paths);
+ void RemoveRange(IEnumerable paths);
+}
\ No newline at end of file