From 1b438c8bd4ac124d543b1fad259ad4fbe32b40f9 Mon Sep 17 00:00:00 2001 From: Macocian Alexandru Victor Date: Wed, 4 May 2022 08:22:32 +0200 Subject: [PATCH] Introduce async extensions for RouteFilters (#16) Rework filter logic to reuse filter objects on the scope of a request --- .github/workflows/cd.yaml | 10 +-- MTSC.UnitTests/E2ETests.cs | 2 + .../NonActioningFilterAttribute.cs | 15 +++++ .../Http/Attributes/RouteFilterAttribute.cs | 9 ++- MTSC/Common/Http/RouteContext.cs | 4 +- MTSC/Common/Http/RouteEnablerAsyncResponse.cs | 27 ++++++++ MTSC/MTSC.csproj | 6 +- .../ServerSide/Handlers/HttpRoutingHandler.cs | 65 ++++++++++++++----- 8 files changed, 112 insertions(+), 26 deletions(-) create mode 100644 MTSC/Common/Http/RouteEnablerAsyncResponse.cs diff --git a/.github/workflows/cd.yaml b/.github/workflows/cd.yaml index 5d998f5..d735c32 100644 --- a/.github/workflows/cd.yaml +++ b/.github/workflows/cd.yaml @@ -44,9 +44,9 @@ jobs: - name: Build MTSC project run: dotnet build MTSC -c $env:Configuration - - name: Push nuget package - uses: brandedoutcast/publish-nuget@v2.5.5 - with: - PROJECT_FILE_PATH: MTSC\MTSC.csproj - NUGET_KEY: ${{secrets.NUGET_API_KEY}} + - name: Package + run: dotnet pack -c Release -o . $env:Source_Project_Path + + - name: Publish + run: dotnet nuget push *.nupkg --api-key ${{ secrets.NUGET_API_KEY }} --source https://api.nuget.org/v3/index.json --skip-duplicate \ No newline at end of file diff --git a/MTSC.UnitTests/E2ETests.cs b/MTSC.UnitTests/E2ETests.cs index 29fdab8..c469b1c 100644 --- a/MTSC.UnitTests/E2ETests.cs +++ b/MTSC.UnitTests/E2ETests.cs @@ -98,6 +98,8 @@ namespace MTSC.UnitTests Assert.IsTrue(NonActioningFilterAttribute.RequestCalled); Assert.IsTrue(NonActioningFilterAttribute.ResponseCalled); + Assert.IsTrue(NonActioningFilterAttribute.RequestAsyncCalled); + Assert.IsTrue(NonActioningFilterAttribute.ResponseAsyncCalled); } [TestMethod] diff --git a/MTSC.UnitTests/RoutingModules/NonActioningFilterAttribute.cs b/MTSC.UnitTests/RoutingModules/NonActioningFilterAttribute.cs index afb24aa..2e57a58 100644 --- a/MTSC.UnitTests/RoutingModules/NonActioningFilterAttribute.cs +++ b/MTSC.UnitTests/RoutingModules/NonActioningFilterAttribute.cs @@ -1,5 +1,6 @@ using MTSC.Common.Http; using MTSC.Common.Http.Attributes; +using System.Threading.Tasks; namespace MTSC.UnitTests.RoutingModules { @@ -7,6 +8,8 @@ namespace MTSC.UnitTests.RoutingModules { public static bool RequestCalled { get; private set; } public static bool ResponseCalled { get; private set; } + public static bool RequestAsyncCalled { get; private set; } + public static bool ResponseAsyncCalled { get; private set; } public override RouteEnablerResponse HandleRequest(RouteContext routeContext) { @@ -19,5 +22,17 @@ namespace MTSC.UnitTests.RoutingModules ResponseCalled = true; base.HandleResponse(routeContext); } + + public override Task HandleRequestAsync(RouteContext routeContext) + { + RequestAsyncCalled = true; + return base.HandleRequestAsync(routeContext); + } + + public override Task HandleResponseAsync(RouteContext routeContext) + { + ResponseAsyncCalled = true; + return base.HandleResponseAsync(routeContext); + } } } diff --git a/MTSC/Common/Http/Attributes/RouteFilterAttribute.cs b/MTSC/Common/Http/Attributes/RouteFilterAttribute.cs index 34e4272..a14be09 100644 --- a/MTSC/Common/Http/Attributes/RouteFilterAttribute.cs +++ b/MTSC/Common/Http/Attributes/RouteFilterAttribute.cs @@ -1,16 +1,21 @@ using System; +using System.Threading.Tasks; namespace MTSC.Common.Http.Attributes { [AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = true)] public abstract class RouteFilterAttribute : Attribute { - public virtual RouteEnablerResponse HandleRequest(RouteContext routeFilterContext) => RouteEnablerResponse.Accept; + public virtual RouteEnablerResponse HandleRequest(RouteContext routeContext) => RouteEnablerResponse.Accept; - public virtual void HandleResponse(RouteContext routeFilterContext) + public virtual Task HandleRequestAsync(RouteContext routeContext) => Task.FromResult(RouteEnablerAsyncResponse.Accept); + + public virtual void HandleResponse(RouteContext routeContext) { } + public virtual Task HandleResponseAsync(RouteContext routeContext) => Task.CompletedTask; + public virtual RouteFilterExceptionHandlingResponse HandleException(RouteContext routeFilterContext, Exception exception) { return RouteFilterExceptionHandlingResponse.NotHandled; diff --git a/MTSC/Common/Http/RouteContext.cs b/MTSC/Common/Http/RouteContext.cs index e0fc880..111574f 100644 --- a/MTSC/Common/Http/RouteContext.cs +++ b/MTSC/Common/Http/RouteContext.cs @@ -1,4 +1,5 @@ -using MTSC.ServerSide; +using MTSC.Common.Http.Attributes; +using MTSC.ServerSide; using Slim; using System.Collections.Generic; using System.Threading; @@ -12,6 +13,7 @@ namespace MTSC.Common.Http public ClientData Client { get; } public Dictionary UrlValues { get; } public IServiceProvider ScopedServiceProvider { get; } + public List RouteFilters { get; } = new List(); public HttpResponse HttpResponse { get; set; } public CancellationToken CancelRequest => this.Client.CancellationToken; public Dictionary Resources { get; set; } = new(); diff --git a/MTSC/Common/Http/RouteEnablerAsyncResponse.cs b/MTSC/Common/Http/RouteEnablerAsyncResponse.cs new file mode 100644 index 0000000..e594ed6 --- /dev/null +++ b/MTSC/Common/Http/RouteEnablerAsyncResponse.cs @@ -0,0 +1,27 @@ +namespace MTSC.Common.Http +{ + public abstract class RouteEnablerAsyncResponse + { + public static RouteEnablerAsyncResponseAccept Accept { get; } = new RouteEnablerAsyncResponseAccept(); + public static RouteEnablerAsyncResponseError Error(HttpResponse responseMessage) + { + return new RouteEnablerAsyncResponseError(responseMessage); + } + + public sealed class RouteEnablerAsyncResponseAccept : RouteEnablerAsyncResponse + { + internal RouteEnablerAsyncResponseAccept() + { + } + } + + public sealed class RouteEnablerAsyncResponseError : RouteEnablerAsyncResponse + { + public HttpResponse Response { get; } + internal RouteEnablerAsyncResponseError(HttpResponse responseMessage) + { + this.Response = responseMessage; + } + } + } +} diff --git a/MTSC/MTSC.csproj b/MTSC/MTSC.csproj index 71dada8..8aab0f0 100644 --- a/MTSC/MTSC.csproj +++ b/MTSC/MTSC.csproj @@ -5,13 +5,13 @@ netstandard2.0 - 5.3.0 + 5.4.0 latest Alexandru-Victor Macocian MTSC Modular TCP Server and Client - 5.3.0.0 - 5.3.0.0 + 5.4.0.0 + 5.4.0.0 true AnyCPU;x64 https://github.com/AlexMacocian/MTSC diff --git a/MTSC/ServerSide/Handlers/HttpRoutingHandler.cs b/MTSC/ServerSide/Handlers/HttpRoutingHandler.cs index d5ea069..7c84908 100644 --- a/MTSC/ServerSide/Handlers/HttpRoutingHandler.cs +++ b/MTSC/ServerSide/Handlers/HttpRoutingHandler.cs @@ -235,9 +235,9 @@ namespace MTSC.ServerSide.Handlers client, module.ScopedServiceProvider, urlValues.ToDictionary(u => u.Placeholder, u => u.Value)); - foreach(var filterType in filterTypes) + routeContext.RouteFilters.AddRange(filterTypes.Select(filterType => module.ScopedServiceProvider.GetService(filterType) as RouteFilterAttribute)); + foreach(var filter in routeContext.RouteFilters) { - var filter = module.ScopedServiceProvider.GetService(filterType) as RouteFilterAttribute; var filterResponse = filter.HandleRequest(routeContext); if (filterResponse is RouteEnablerResponse.RouteEnablerResponseAccept) { @@ -258,11 +258,23 @@ namespace MTSC.ServerSide.Handlers } } - - this.RouteHandleRequest(module, routeContext, filterTypes).ContinueWith(task => - { - this.QueueResponse(client, task.Result); - }); + this.FiltersHandleRequestAsync(routeContext) + .ContinueWith(task => + { + var routeEnablerAsyncResponse = task.Result; + if (routeEnablerAsyncResponse is RouteEnablerAsyncResponse.RouteEnablerAsyncResponseError) + { + return; + } + + this.RouteHandleRequest(module, routeContext, filterTypes) + .ContinueWith(async task => + { + await FiltersHandleResponseAsync(routeContext); + this.QueueResponse(client, task.Result); + return Task.CompletedTask; + }); + }); return true; } @@ -369,6 +381,37 @@ namespace MTSC.ServerSide.Handlers } } + private async Task FiltersHandleRequestAsync(RouteContext routeContext) + { + foreach (var filter in routeContext.RouteFilters) + { + var filterResponse = await filter.HandleRequestAsync(routeContext); + if (filterResponse is RouteEnablerAsyncResponse.RouteEnablerAsyncResponseAccept) + { + continue; + } + else if (filterResponse is RouteEnablerAsyncResponse.RouteEnablerAsyncResponseError errorResponse) + { + this.QueueResponse(routeContext.Client, errorResponse.Response); + return errorResponse; + } + else + { + throw new InvalidOperationException($"RouteEnablerAsyncResponse should be one of the types {typeof(RouteEnablerAsyncResponse.RouteEnablerAsyncResponseAccept)} or {typeof(RouteEnablerAsyncResponse.RouteEnablerAsyncResponseError)}!"); + } + } + + return RouteEnablerAsyncResponse.Accept; + } + + private async Task FiltersHandleResponseAsync(RouteContext routeContext) + { + foreach(var filter in routeContext.RouteFilters) + { + await filter.HandleResponseAsync(routeContext); + } + } + private async Task RouteHandleRequest( HttpRouteBase httpRouteBase, RouteContext routeContext, @@ -419,7 +462,6 @@ namespace MTSC.ServerSide.Handlers return module; } - private void RegisterRoute(HttpMethods method, string uri, Type routeType) { if (!typeof(HttpRouteBase).IsAssignableFrom(routeType)) @@ -429,7 +471,6 @@ namespace MTSC.ServerSide.Handlers this.moduleDictionary[method].Add((new ExtendedUrl(uri), routeType, new List())); } - private void PrepareRoutePropertyCache(Type routeType) { if (this.routePropertyCache.ContainsKey(routeType)) @@ -452,7 +493,6 @@ namespace MTSC.ServerSide.Handlers this.routePropertyCache[routeType] = propertyAndAttributesList; } - private bool TryMatchUrl(HttpMethods method, string uri, out List urlValues, out Type type, out List filters) { urlValues = null; @@ -472,7 +512,6 @@ namespace MTSC.ServerSide.Handlers return false; } - private void SetModuleProperties(HttpRouteBase module, RouteContext routeContext) { foreach ((var attribute, var propertyInfo) in this.routePropertyCache[module.GetType()]) @@ -495,8 +534,6 @@ namespace MTSC.ServerSide.Handlers } } - - private static void SetPropertyValue(PropertyInfo propertyInfo, object value, HttpRouteBase module) { if (propertyInfo.CanWrite is false) @@ -510,8 +547,6 @@ namespace MTSC.ServerSide.Handlers } } - - private static HttpResponse NotFound404 => new() { StatusCode = StatusCodes.NotFound