feat: Assembly search nested handler type filtering
Fix the assembly handler scanning and do not allow nested handlers to be present.
This commit is contained in:
parent
b1d2b749a4
commit
e3d16c9f56
7 changed files with 249 additions and 15 deletions
|
|
@ -6,6 +6,7 @@
|
||||||
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.6" />
|
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.6" />
|
||||||
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="10.0.6" />
|
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="10.0.6" />
|
||||||
<PackageVersion Include="Microsoft.Extensions.Options" Version="10.0.6" />
|
<PackageVersion Include="Microsoft.Extensions.Options" Version="10.0.6" />
|
||||||
|
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.14.0" />
|
||||||
<PackageVersion Include="Microsoft.SourceLink.Gitea" Version="10.0.102" />
|
<PackageVersion Include="Microsoft.SourceLink.Gitea" Version="10.0.102" />
|
||||||
<PackageVersion Include="TUnit" Version="1.11.51" />
|
<PackageVersion Include="TUnit" Version="1.11.51" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
|
||||||
10
README.md
10
README.md
|
|
@ -29,7 +29,7 @@ public static Task<int> Main()
|
||||||
{
|
{
|
||||||
var collection = new ServiceCollection();
|
var collection = new ServiceCollection();
|
||||||
collection.AddRequestDispatcher(builder => builder
|
collection.AddRequestDispatcher(builder => builder
|
||||||
.Add(typeof(ScalarHandler))
|
.SearchHandlerInAssembly(typeof(ScalarHandler).Assembly)
|
||||||
.Add(typeof(ScalarBehavior)));
|
.Add(typeof(ScalarBehavior)));
|
||||||
await using var provider = collection.BuildServiceProvider();
|
await using var provider = collection.BuildServiceProvider();
|
||||||
var dispatcher = provider.GetRequiredService<IRequestDispatcher>();
|
var dispatcher = provider.GetRequiredService<IRequestDispatcher>();
|
||||||
|
|
@ -46,17 +46,17 @@ public class ScalarRequest : IScalarRequest<string>
|
||||||
public string Value { get; set; } = string.Empty;
|
public string Value { get; set; } = string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
public class ScalarHandler : IScalarRequestHandler<ScalarTestRequest, string>
|
public class ScalarHandler : IScalarRequestHandler<ScalarRequest, string>
|
||||||
{
|
{
|
||||||
public Task<string> HandleAsync(ScalarTestRequest request, CancellationToken cancellationToken)
|
public Task<string> HandleAsync(ScalarRequest request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
return Task.FromResult($"{request.Value} World");
|
return Task.FromResult($"{request.Value} World");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class ScalarBehavior : IScalarRequestBehavior<ScalarTestRequest, string>
|
public class ScalarBehavior : IScalarRequestBehavior<ScalarRequest, string>
|
||||||
{
|
{
|
||||||
public async Task<string> HandleAsync(ScalarTestRequest request, ScalarHandlerDelegate<string> next, CancellationToken cancellationToken)
|
public async Task<string> HandleAsync(ScalarRequest request, ScalarHandlerDelegate<string> next, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
Console.WriteLine("Before");
|
Console.WriteLine("Before");
|
||||||
var result = await next(request, cancellationToken);
|
var result = await next(request, cancellationToken);
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="TUnit" />
|
<PackageReference Include="TUnit" />
|
||||||
<!-- additional packages -->
|
<!-- additional packages -->
|
||||||
|
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" />
|
||||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|
|
||||||
124
src/request.tests/SearchHandlerInAssemblyTests.cs
Normal file
124
src/request.tests/SearchHandlerInAssemblyTests.cs
Normal file
|
|
@ -0,0 +1,124 @@
|
||||||
|
// Copyright (c) The Geekeey Authors
|
||||||
|
// SPDX-License-Identifier: EUPL-1.2
|
||||||
|
|
||||||
|
using System.Collections;
|
||||||
|
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
|
namespace Geekeey.Request.Tests;
|
||||||
|
|
||||||
|
internal sealed class SearchHandlerInAssemblyTests
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
public async Task I_can_search_handlers_in_an_assembly()
|
||||||
|
{
|
||||||
|
const string source =
|
||||||
|
"""
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Geekeey.Request;
|
||||||
|
|
||||||
|
namespace Sample;
|
||||||
|
|
||||||
|
public sealed class Ping : IScalarRequest<string>
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class PingHandler : IScalarRequestHandler<Ping, string>
|
||||||
|
{
|
||||||
|
public Task<string> HandleAsync(Ping request, CancellationToken cancellationToken) => Task.FromResult("pong");
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
using var assembly = DynamicTestAssembly.Compile(source);
|
||||||
|
|
||||||
|
var services = new ServiceCollection();
|
||||||
|
services.AddRequestDispatcher(builder => builder
|
||||||
|
.SearchHandlerInAssembly(assembly.Assembly));
|
||||||
|
await using var provider = services.BuildServiceProvider();
|
||||||
|
var options = provider.GetRequiredService<IOptions<RequestDispatcherOptions>>().Value;
|
||||||
|
var requestType = assembly.Assembly.GetType("Sample.Ping", throwOnError: true)!;
|
||||||
|
var handlerType = assembly.Assembly.GetType("Sample.PingHandler", throwOnError: true)!;
|
||||||
|
var handlerInterface = typeof(IScalarRequestHandler<,>).MakeGenericType(requestType, typeof(string));
|
||||||
|
var handlers = GetRequestHandlers(options, handlerInterface, provider).ToArray();
|
||||||
|
|
||||||
|
using var scope = Assert.Multiple();
|
||||||
|
await Assert.That(handlers).Count().IsEqualTo(1);
|
||||||
|
await Assert.That(handlers.Single()!.GetType()).IsEqualTo(handlerType);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task I_can_search_handlers_in_an_assembly_with_a_lifetime()
|
||||||
|
{
|
||||||
|
const string source =
|
||||||
|
"""
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Geekeey.Request;
|
||||||
|
|
||||||
|
namespace Sample;
|
||||||
|
|
||||||
|
public sealed class Ping : IScalarRequest<string>
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class PingHandler : IScalarRequestHandler<Ping, string>
|
||||||
|
{
|
||||||
|
public Task<string> HandleAsync(Ping request, CancellationToken cancellationToken) => Task.FromResult("pong");
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
using var assembly = DynamicTestAssembly.Compile(source);
|
||||||
|
|
||||||
|
var services = new ServiceCollection();
|
||||||
|
services.AddRequestDispatcher(builder => builder
|
||||||
|
.SearchHandlerInAssembly(assembly.Assembly, ServiceLifetime.Singleton));
|
||||||
|
var handlerType = assembly.Assembly.GetType("Sample.PingHandler", throwOnError: true)!;
|
||||||
|
var descriptor = services.SingleOrDefault(service => service.ServiceType == handlerType);
|
||||||
|
|
||||||
|
using var scope = Assert.Multiple();
|
||||||
|
await Assert.That(descriptor).IsNotNull();
|
||||||
|
await Assert.That(descriptor!.Lifetime).IsEqualTo(ServiceLifetime.Singleton);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task I_get_an_exception_when_nested_handlers_are_present()
|
||||||
|
{
|
||||||
|
const string source =
|
||||||
|
"""
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Geekeey.Request;
|
||||||
|
|
||||||
|
namespace Sample;
|
||||||
|
|
||||||
|
public sealed class Ping : IScalarRequest<string>
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class Container
|
||||||
|
{
|
||||||
|
public sealed class PingHandler : IScalarRequestHandler<Ping, string>
|
||||||
|
{
|
||||||
|
public Task<string> HandleAsync(Ping request, CancellationToken cancellationToken) => Task.FromResult("pong");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
using var assembly = DynamicTestAssembly.Compile(source);
|
||||||
|
|
||||||
|
var services = new ServiceCollection();
|
||||||
|
var builder = services.AddRequestDispatcher();
|
||||||
|
|
||||||
|
await Assert.That(() => builder.SearchHandlerInAssembly(assembly.Assembly))
|
||||||
|
.Throws<InvalidOperationException>().And.HasMessageContaining("Sample.Container+PingHandler");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IEnumerable<object?> GetRequestHandlers(RequestDispatcherOptions options, Type handlerInterface,
|
||||||
|
IServiceProvider provider)
|
||||||
|
{
|
||||||
|
return ((IEnumerable)typeof(RequestDispatcherOptions)
|
||||||
|
.GetMethod(nameof(RequestDispatcherOptions.GetRequestHandlers))!
|
||||||
|
.MakeGenericMethod(handlerInterface)
|
||||||
|
.Invoke(options, [provider])!)
|
||||||
|
.Cast<object?>();
|
||||||
|
}
|
||||||
|
}
|
||||||
67
src/request.tests/_helpers/DynamicTestAssembly.cs
Normal file
67
src/request.tests/_helpers/DynamicTestAssembly.cs
Normal file
|
|
@ -0,0 +1,67 @@
|
||||||
|
// Copyright (c) The Geekeey Authors
|
||||||
|
// SPDX-License-Identifier: EUPL-1.2
|
||||||
|
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Runtime.Loader;
|
||||||
|
|
||||||
|
using Microsoft.CodeAnalysis;
|
||||||
|
using Microsoft.CodeAnalysis.CSharp;
|
||||||
|
|
||||||
|
namespace Geekeey.Request.Tests;
|
||||||
|
|
||||||
|
internal sealed class DynamicTestAssembly : IDisposable
|
||||||
|
{
|
||||||
|
private readonly WeakReference _context;
|
||||||
|
|
||||||
|
private DynamicTestAssembly(Assembly assembly, CollectibleAssemblyLoadContext context)
|
||||||
|
{
|
||||||
|
Assembly = assembly;
|
||||||
|
_context = new WeakReference(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Assembly Assembly { get; }
|
||||||
|
|
||||||
|
public static DynamicTestAssembly Compile(string source)
|
||||||
|
{
|
||||||
|
var references = ((string)AppContext.GetData("TRUSTED_PLATFORM_ASSEMBLIES")!)
|
||||||
|
.Split(Path.PathSeparator)
|
||||||
|
.Concat([typeof(IRequestDispatcherBuilder).Assembly.Location])
|
||||||
|
.Distinct(StringComparer.Ordinal)
|
||||||
|
.Select(static path => MetadataReference.CreateFromFile(path));
|
||||||
|
|
||||||
|
var syntax = CSharpSyntaxTree.ParseText(source, CSharpParseOptions.Default
|
||||||
|
.WithLanguageVersion(LanguageVersion.Preview));
|
||||||
|
var options = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary);
|
||||||
|
var compilation = CSharpCompilation.Create($"Dynamic_{Guid.NewGuid():N}", [syntax], references, options);
|
||||||
|
|
||||||
|
using var stream = new MemoryStream();
|
||||||
|
|
||||||
|
if (compilation.Emit(stream) is { Success: false, Diagnostics: var diagnostics })
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(string.Join(Environment.NewLine,
|
||||||
|
diagnostics.Select(static diagnostic => diagnostic.ToString())));
|
||||||
|
}
|
||||||
|
|
||||||
|
stream.Seek(0, SeekOrigin.Begin);
|
||||||
|
|
||||||
|
var context = new CollectibleAssemblyLoadContext();
|
||||||
|
return new DynamicTestAssembly(context.LoadFromStream(stream), context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (_context.Target is AssemblyLoadContext context)
|
||||||
|
{
|
||||||
|
context.Unload();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var iteration = 0; iteration < 10 && _context.IsAlive; iteration++)
|
||||||
|
{
|
||||||
|
GC.Collect();
|
||||||
|
GC.WaitForPendingFinalizers();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class CollectibleAssemblyLoadContext()
|
||||||
|
: AssemblyLoadContext(nameof(DynamicTestAssembly), isCollectible: true);
|
||||||
|
}
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
// SPDX-License-Identifier: EUPL-1.2
|
// SPDX-License-Identifier: EUPL-1.2
|
||||||
|
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||||
|
|
@ -20,6 +21,12 @@ public static class RequestDispatcherBuilderExtensions
|
||||||
/// Searches for request handler types within the specified assembly and adds them to the request dispatcher
|
/// Searches for request handler types within the specified assembly and adds them to the request dispatcher
|
||||||
/// configuration.
|
/// configuration.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Prefer the generated <c>Add<Name>(builder)</c> extensions for assemblies that directly reference
|
||||||
|
/// <c>Geekeey.Request</c>. This runtime scanning API remains supported, but nested request handlers are rejected
|
||||||
|
/// during registration and generated registration should not be mixed with <see cref="SearchHandlerInAssembly(IRequestDispatcherBuilder, Assembly)"/>
|
||||||
|
/// for the same assembly.
|
||||||
|
/// </remarks>
|
||||||
/// <param name="builder">The <see cref="IRequestDispatcherBuilder"/> to configure.</param>
|
/// <param name="builder">The <see cref="IRequestDispatcherBuilder"/> to configure.</param>
|
||||||
/// <param name="assembly">The assembly to search for request handler types.</param>
|
/// <param name="assembly">The assembly to search for request handler types.</param>
|
||||||
/// <returns>The <see cref="IRequestDispatcherBuilder"/> instance for further configuration.</returns>
|
/// <returns>The <see cref="IRequestDispatcherBuilder"/> instance for further configuration.</returns>
|
||||||
|
|
@ -29,7 +36,7 @@ public static class RequestDispatcherBuilderExtensions
|
||||||
|
|
||||||
var exports = assembly.GetTypes()
|
var exports = assembly.GetTypes()
|
||||||
.Where(type => type is { IsClass: true, IsAbstract: false })
|
.Where(type => type is { IsClass: true, IsAbstract: false })
|
||||||
.Where(IsRequestHandlerType);
|
.Where(IsRequestHandlerImplementationType);
|
||||||
|
|
||||||
builder.Add(exports);
|
builder.Add(exports);
|
||||||
|
|
||||||
|
|
@ -40,6 +47,12 @@ public static class RequestDispatcherBuilderExtensions
|
||||||
/// Searches for request handler types within the specified assembly and adds them to the request dispatcher
|
/// Searches for request handler types within the specified assembly and adds them to the request dispatcher
|
||||||
/// configuration with the given service lifetime.
|
/// configuration with the given service lifetime.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Prefer the generated <c>Add<Name>(builder, lifetime)</c> extensions for assemblies that directly reference
|
||||||
|
/// <c>Geekeey.Request</c>. This runtime scanning API remains supported, but nested request handlers are rejected
|
||||||
|
/// during registration and generated registration should not be mixed with <see cref="SearchHandlerInAssembly(IRequestDispatcherBuilder, Assembly, ServiceLifetime)"/>
|
||||||
|
/// for the same assembly.
|
||||||
|
/// </remarks>
|
||||||
/// <param name="builder">The <see cref="IRequestDispatcherBuilder"/> to configure.</param>
|
/// <param name="builder">The <see cref="IRequestDispatcherBuilder"/> to configure.</param>
|
||||||
/// <param name="assembly">The assembly to search for request handler types.</param>
|
/// <param name="assembly">The assembly to search for request handler types.</param>
|
||||||
/// <param name="lifetime">The lifetime with which the request handlers are registered in the dependency injection container.</param>
|
/// <param name="lifetime">The lifetime with which the request handlers are registered in the dependency injection container.</param>
|
||||||
|
|
@ -50,7 +63,7 @@ public static class RequestDispatcherBuilderExtensions
|
||||||
|
|
||||||
var exports = assembly.GetTypes()
|
var exports = assembly.GetTypes()
|
||||||
.Where(type => type is { IsClass: true, IsAbstract: false })
|
.Where(type => type is { IsClass: true, IsAbstract: false })
|
||||||
.Where(IsRequestHandlerType);
|
.Where(IsRequestHandlerImplementationType);
|
||||||
|
|
||||||
builder.Add(exports, lifetime);
|
builder.Add(exports, lifetime);
|
||||||
|
|
||||||
|
|
@ -66,6 +79,7 @@ public static class RequestDispatcherBuilderExtensions
|
||||||
public static IRequestDispatcherBuilder Add(this IRequestDispatcherBuilder builder, Type type)
|
public static IRequestDispatcherBuilder Add(this IRequestDispatcherBuilder builder, Type type)
|
||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(builder);
|
ArgumentNullException.ThrowIfNull(builder);
|
||||||
|
ValidateNoNestedRequestHandlers([type]);
|
||||||
|
|
||||||
builder.Services.AddOptions<RequestDispatcherOptions>()
|
builder.Services.AddOptions<RequestDispatcherOptions>()
|
||||||
.Configure(options => options.Inspect([type]));
|
.Configure(options => options.Inspect([type]));
|
||||||
|
|
@ -85,6 +99,7 @@ public static class RequestDispatcherBuilderExtensions
|
||||||
public static IRequestDispatcherBuilder Add(this IRequestDispatcherBuilder builder, Type type, ServiceLifetime lifetime)
|
public static IRequestDispatcherBuilder Add(this IRequestDispatcherBuilder builder, Type type, ServiceLifetime lifetime)
|
||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(builder);
|
ArgumentNullException.ThrowIfNull(builder);
|
||||||
|
ValidateNoNestedRequestHandlers([type]);
|
||||||
|
|
||||||
builder.Services.AddOptions<RequestDispatcherOptions>()
|
builder.Services.AddOptions<RequestDispatcherOptions>()
|
||||||
.Configure(options => options.Inspect([type]));
|
.Configure(options => options.Inspect([type]));
|
||||||
|
|
@ -104,8 +119,10 @@ public static class RequestDispatcherBuilderExtensions
|
||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(builder);
|
ArgumentNullException.ThrowIfNull(builder);
|
||||||
|
|
||||||
|
var types = ValidateNoNestedRequestHandlers([.. type]);
|
||||||
|
|
||||||
builder.Services.AddOptions<RequestDispatcherOptions>()
|
builder.Services.AddOptions<RequestDispatcherOptions>()
|
||||||
.Configure(options => options.Inspect(type));
|
.Configure(options => options.Inspect(types));
|
||||||
|
|
||||||
return builder;
|
return builder;
|
||||||
}
|
}
|
||||||
|
|
@ -123,11 +140,35 @@ public static class RequestDispatcherBuilderExtensions
|
||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(builder);
|
ArgumentNullException.ThrowIfNull(builder);
|
||||||
|
|
||||||
builder.Services.AddOptions<RequestDispatcherOptions>()
|
var types = ValidateNoNestedRequestHandlers([.. type]);
|
||||||
.Configure(options => options.Inspect(type));
|
|
||||||
|
|
||||||
builder.Services.Add(type.Select(export => new ServiceDescriptor(export, export, lifetime)));
|
builder.Services.AddOptions<RequestDispatcherOptions>()
|
||||||
|
.Configure(options => options.Inspect(types));
|
||||||
|
|
||||||
|
builder.Services.Add(types.Select(export => new ServiceDescriptor(export, export, lifetime)));
|
||||||
|
|
||||||
return builder;
|
return builder;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static IReadOnlyCollection<Type> ValidateNoNestedRequestHandlers(IReadOnlyCollection<Type> types, [CallerMemberName] string? invoker = null)
|
||||||
|
{
|
||||||
|
var nestedHandlers = types
|
||||||
|
.Where(type => type is { IsClass: true, IsAbstract: false, IsNested: true })
|
||||||
|
.Where(IsRequestHandlerImplementationType)
|
||||||
|
.Select(type => type.FullName ?? type.Name)
|
||||||
|
.OrderBy(static name => name, StringComparer.Ordinal)
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
if (nestedHandlers.Length > 0)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"Nested request handlers are not supported by {invoker}: {string.Join(", ", nestedHandlers)}");
|
||||||
|
}
|
||||||
|
|
||||||
|
return types;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsRequestHandlerImplementationType(Type type)
|
||||||
|
{
|
||||||
|
return type.GetInterfaces().Any(IsRequestHandlerType);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ public static Task<int> Main()
|
||||||
{
|
{
|
||||||
var collection = new ServiceCollection();
|
var collection = new ServiceCollection();
|
||||||
collection.AddRequestDispatcher(builder => builder
|
collection.AddRequestDispatcher(builder => builder
|
||||||
.Add(typeof(ScalarHandler))
|
.SearchHandlerInAssembly(typeof(ScalarHandler).Assembly)
|
||||||
.Add(typeof(ScalarBehavior)));
|
.Add(typeof(ScalarBehavior)));
|
||||||
await using var provider = collection.BuildServiceProvider();
|
await using var provider = collection.BuildServiceProvider();
|
||||||
var dispatcher = provider.GetRequiredService<IRequestDispatcher>();
|
var dispatcher = provider.GetRequiredService<IRequestDispatcher>();
|
||||||
|
|
@ -42,17 +42,17 @@ public class ScalarRequest : IScalarRequest<string>
|
||||||
public string Value { get; set; } = string.Empty;
|
public string Value { get; set; } = string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
public class ScalarHandler : IScalarRequestHandler<ScalarTestRequest, string>
|
public class ScalarHandler : IScalarRequestHandler<ScalarRequest, string>
|
||||||
{
|
{
|
||||||
public Task<string> HandleAsync(ScalarTestRequest request, CancellationToken cancellationToken)
|
public Task<string> HandleAsync(ScalarRequest request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
return Task.FromResult($"{request.Value} World");
|
return Task.FromResult($"{request.Value} World");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class ScalarBehavior : IScalarRequestBehavior<ScalarTestRequest, string>
|
public class ScalarBehavior : IScalarRequestBehavior<ScalarRequest, string>
|
||||||
{
|
{
|
||||||
public async Task<string> HandleAsync(ScalarTestRequest request, ScalarHandlerDelegate<string> next, CancellationToken cancellationToken)
|
public async Task<string> HandleAsync(ScalarRequest request, ScalarHandlerDelegate<string> next, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
Console.WriteLine("Before");
|
Console.WriteLine("Before");
|
||||||
var result = await next(request, cancellationToken);
|
var result = await next(request, cancellationToken);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue