Closed nhaberl closed 1 year ago
Short answer is: nope. The design of Minimal API is tightly coupled to the built-in DI system and it doesn't allow "non conformers" such as Simple Injector to hook into that pipeline easily.
I would, therefore, advise an application architecture that makes it easy to forward calls back to Simple Injector. This can be done, for instance, by designing the application around message-based patterns, such as the command handlers and query handlers I described on my blog.
This makes mapping an incoming API call to a handler -provided by Simple Injector- fairly easy. You can either call the container from within your function -or- you can register a mediator class in the built-in container that depends on Simple Injector and forwards the call.
I hope this helps
Thanks a lot and yes I do that but want to be sure that I can use FastEndpoints and Injecting my handlers there. Otherwise I will stay with controllers (and no minimal APIs) which should work as is ? And maybe you could provide a sample within your docs for ASP.NET 6 guys ...
Otherwise I will stay with controllers (and no minimal APIs) which should work as is ?
Absolutely. Just follow the guidance from the documentation
And maybe you could provide a sample within your docs for ASP.NET 6 guys ..
Good point. Should certainly be documented.
BTW, you might be interested in the SOLID Services implementation using Minimal API. It can benfound here: https://github.com/dotnetjunkie/solidservices/blob/master/src/WebCore6Service/Program.cs
One more thing please, does SI request scope guarantee that there is only one instance per request ? Because I am queuing commands during request and commit them at the end so my queue should only be valid for this scope.
Dont know why Microsoft acts so differently there.
So does SI always respect the scope no matter where it gets called ?
I'm unsure I understand your question. Can you give an example? How does MS.DI act different?
In the meantime, as long as FastEndpoints doesn't support the proper interception points to plugin to, I'd suggest adding an extra layer of abstraction that can be used to implement your endpoints. For instance:
// Single application interface for your request handlers
public interface IRequestHandler<TRequest, TResponse>
{
Task<TResponse> HandleAsync(TRequest request, CancellationToken ct);
}
Instead of implementing the logic inside FastEndpoints derived Endpoint<T>
classes, instead implement the logic inside your IRequestHandler<TRequest, TResponse>
implementations. For instance:
// This class is created by Simple Injector
public sealed class MyRequestHandler : IRequestHandler<MyRequest, MyResponse>
{
public MyRequestHandler(/* place dependencies here */)
{
}
public async Task<MyResponse> HandleAsync(MyRequest req, CancellationToken ct)
{
return new MyResponse()
{
FullName = req.FirstName + " " + req.LastName,
IsOver18 = req.Age > 18
};
}
}
This reduces your endpoint class to the following:
// This class is now a Humble Object and will be created using MS.DI
[HttpPost("/api/user/create")]
[AllowAnonymous]
public class MyEndpoint : Endpoint<MyRequest>
{
private readonly IRequestHandler<MyRequest, MyResponse> handler;
public MyEndpoint(IRequestHandler<MyRequest, MyResponse> handler) =>
this.handler = handler;
public override async Task HandleAsync(MyRequest req, CancellationToken ct) =>
await SendAsync(await handler.HandleAsync(req, ct));
}
The only thing now missing is a proxy that bridges the gab between MS.DI and Simple Injector. That can be achieved using the following implementation:
// Gets constructed by MS.DI and forwards a request to a handler constructed by SI.
public sealed record SimpleInjectorRequestHandlerDispatcher<TRequest, TResponse>(
SimpleInjector.Container Container) : IRequestHandler<TRequest, TResponse>
{
public Task<TResponse> HandleAsync(TRequest request, CancellationToken ct) =>
Container.GetInstance<IRequestHandler<TRequest, TResponse>>()
.HandleAsync(request, ct);
}
And everything can be tied together as follows:
builder.Services.AddSingleton(
typeof(IRequestHandler<,>),
typeof(SimpleInjectorRequestHandlerDispatcher<,>));
container.Register(typeof(IRequestHandler<,>), typeof(MyRequestHandler).Assembly);
That's all. If you wish to keep all the logic together, you could also place the requesthandler class inside its corresponding endpoint.
For completeness, this would be your complete Program file:
global using FastEndpoints;
var container = new SimpleInjector.Container();
container.Register(typeof(IRequestHandler<,>), typeof(MyRequestHandler).Assembly);
var builder = WebApplication.CreateBuilder();
builder.Services
.AddFastEndpoints()
.AddSimpleInjector(container, _ => _.AddAspNetCore());
builder.Services.AddSingleton(
typeof(IRequestHandler<,>),
typeof(SimpleInjectorRequestHandlerDispatcher<,>));
var app = builder.Build();
app.UseAuthorization();
app.UseFastEndpoints();
app.UseSimpleInjector();
app.Run();
The following PR was merged to the main branch. This likely means that the work of the PR will be available in the next minor release (v5.2) of FastEndpoints. With that release, you can use the following code to integrate Simple Injector with FastEndpoints:
using SimpleInjector;
using FastEndpoints;
var container = new Container();
var builder = WebApplication.CreateBuilder();
builder.Services
.AddFastEndpoints()
.AddSingleton<IEndpointFactory, SimpleInjectorEndpointFactory>()
.AddSimpleInjector(container, options =>{ options.AddAspNetCore(); });
var endpoints = builder.Services.Where(s => typeof(BaseEndpoint).IsAssignableFrom(s.ServiceType));
foreach (var endpoint in endpoints.ToArray())
{
// Move endpoint registration to Simple Injector
builder.Services.Remove(endpoint);
container.Register(endpoint.ImplementationType!);
}
var app = builder.Build();
app
.UseAuthorization()
.UseFastEndpoints()
.UseSimpleInjector(container);
app.Run();
public record SimpleInjectorEndpointFactory(Container Container) : IEndpointFactory
{
public BaseEndpoint Create(EndpointDefinition definition, HttpContext ctx) =>
(BaseEndpoint)Container.GetInstance(definition.EndpointType);
}
Thank you very much !!!
I ran into this yesterday as I was trying to add FastEndpoints to an existing ASPNET Core MVC project that was using SimpleInjector. I wasn't able to get both of them working together as I would have liked because I couldn't call .UseSimpleInjector(container)
twice, once for FastEndpoints and once for Controllers. It throws an exception if it's called twice.
My current workaround was to pass Container
into my new FastEndpoint and then resolve needed classes manually in the constructor. This works but is obviously not ideal.
I would like to use https://fast-endpoints.com but have wired up SimpleInjector for my business logic a lot so my question is if minimal APIs are supported without any hassle ? I am asking because of https://github.com/dotnet/aspnetcore/issues/41863 and https://github.com/FastEndpoints/Library/issues/218