Closed dermaine-learn closed 6 months ago
The referenced StructuredMinimalApi repository provides the following example:
public class GetWithParamEndpoint : IEndpoint<string, string>
{
public void AddRoute(IEndpointRouteBuilder app)
{
app.MapGet("/Todo/2/{param1}", (string param1) =>
HandleAsync(param1));
}
public Task<string> HandleAsync(string request)
{
return Task.FromResult($"Hello World! 2 {request}");
}
}
As long as you stick with that example, there is simply no way to intercept the call to HandleAsync
. Decorating an IEndpoint<TRequest, TResponse>
has no effect, because the HandleAsync
method is never called through the IEndpoint
interface. As the example shows, the HandleAsync
method is called from within the AddRoute
method. This tightly couples the method implementation to the app.MapGet
registration. Interception through decoration will have no effect whatsoever.
Please also note that there is a design issue with this library especially concerning the injection of Scoped dependencies into endpoints. I just reported this here.
If you were to do this, you are better of defining your endpoint classes as Humble Objects instead. That means, doing the least amount of code in that class, which should only consist of infrastructure code, and extract any relevant business logic out of the endpoint, into a new class, and wrap that class behind and abstraction. That abstraction can than be decorated.
For instance, you can define the following abstractions:
public interface IRequest<TResponse> { }
public interface IRequestHandler<TRequest, TResponse>
where TRequest : IRequest<TResponse> { }
public interface IMessageDispatcher
{
Task<TResponse> HandleAsync<TResponse>(IRequest<TResponse> req);
}
With that, you can create a HelloWorld
request message and HelloWolrdHandler
class:
public record HelloWorld(string Request) : IRequest<string> { }
public class HelloWorldHandler : IRequestHandler<HelloWorld, string>
{
public Task<string> HandleAsync(HelloWorld request)
{
return Task.FromResult($"Hello World! 2 {request.Request}");
}
}
These handlers can be registered in Simple Injector as follows:
container.Register(typeof(IRequestHandler<,>),
AppDomain.CurrentDomain.GetAssemblies());
This would allow you to simplify your endpoint classes to the following:
public record GetWithParamEndpoint(IMessageDispatcher Dispatcher)
: IEndpoint
{
public void AddRoute(IEndpointRouteBuilder app)
{
app.MapGet("/Todo/2/{param1}",
(string param1) => this.Dispatcher.HandleAsync(
new HelloWorld(param1)));
}
}
This endpoint class makes use of the IMessageDispatcher
. It will be responsible for accepting any arbitrary IRequest<TResponse>
instances and forwarding it to the correct underlying IRequestHandler<TRequest, TResponse>
implementation. You'll need a custom Simple Injector-specific implementation for this:
record SimpleInjectorMessageDispatcher(Container Container)
IMessageDispatcher
{
public Task<TResponse> HandleAsync<TResponse>(
IRequest<TResponse> request)
{
var handlerType = typeof(IRequestHandler<,>)
.MakeGenericType(request.GetType(), typeof(TResponse));
dynamic handler = container.GetInstance(handlerType);
return handler.HandleAsync((dynamic)request);
}
}
This dispatcher can be registered as follows:
// Add to MS.DI
service.AddSingleton<IRequestDispatcher>(
new SimpleInjectorRequestDispatcher(container));
This wired the complete infrastructure together. With this, you can now wrap decorators around IRequestHandler<,>
implementations:
container.RegisterDecorator(
typeof(IRequestHandler<,>),
typeof(LoggingRequestHandlerDecorator<,>));
I hope this helps.
hi dotnetjunkie,
Thanks for your very detail response. This helps a ton.
I have a question based on the issue you submitted for the library, the issue with the scope not being disposed is still a problem correct? Is it just a matter of registering the endpoints as singletons and MapEndpoinst updated to match below?
public static class IEndpointRouteBuilderExtensions
{
public static void MapEndpoints(this WebApplication builder)
{
var endpoints = builder.Services.GetServices<IEndpoint>();
foreach (var endpoint in endpoints)
{
endpoint.AddRoute(builder);
}
}
}
Thanks in advance.
Is it just a matter of registering the endpoints as singletons and MapEndpoinst updated to match below?
Well... it depends. This prevents dependencies not being disposed when the application stops and undetected problems due to captive dependencies. It still prevents scoped dependencies (such as DbContext instances) from being injected into your endpoints though. But when you use the endpoints as Humble Objects, this will not be limiting restriction.
Thanks for clarifying, I will use the endpoint as humble object approach along with the other recommenadations your listed: Register Endpoints as singletons Resolve endpoints using container root.
Also sorry for repeating above, I went back through issue you raised and relaized you already included the above block for resolving using root container in MapEndpoints.
Thanks for your speedy response as usual.
hi,
I started migrating an existing API to use the new Minimal APIs in AP.NET Core. I am using the library below for better organizing my API's and was having issues using Simple Injector to inject dependencies.
https://github.com/michelcedric/StructuredMinimalApi
I am trying to find a way to use Simple Injector to intercept and resolve dependencies for types implementing IEndpoint interface.
Is there an efficient way for handing this using simple injector. I also saw your recommendation in issue. Would it be a similar approach?
Thanks in advance.