Issue with Non-conforming DI Container integration with constructor injected services
See original GitHub issueAfter I noticed the PR by Simple Injector’s Steven (@dotnetjunkie) was merged, I gave the lastest Fastendpoints a whirl with SimpleInjector as described in the PR: https://github.com/FastEndpoints/FastEndpoints/pull/243 and similar info here: https://github.com/simpleinjector/SimpleInjector/issues/955#issuecomment-1254827842
However, using the example code below, it seems if you inject an application service into your endpoint, the MS DI container will complain that it can’t resolve the service. Whilst this makes technical sense since we haven’t registered the application service in the DI container, we have instead registered it in SimpleInjector, it doesn’t help in being able to use SimpleInjector to resolve the endpoints and services.
The only work-around that I can see is to register the application services in the MS DI container as well - which doesn’t seem correct.
Whilst the new IEndpointFactory seems to work (if you work around the MS DI registration issue) and resolves with SimpleInjector, it would seem that perhaps something else is needed in the AddFastEndpoints() method to either allow a different DI service registration or to skip the registration of the Endpoints in the MS DI container ?
Example
Example has been adapted from the Fastendpoints startup example:
global using FastEndpoints;
using SimpleInjector;
var container = new Container();
var builder = WebApplication.CreateBuilder();
// Setup FastEndpoints
builder.Services
.AddFastEndpoints();
// NOTE: By this stage, MyEndpoint has been registered in the MS DI!
// Setup Simple Injector
builder.Services
.AddSingleton<IEndpointFactory, SimpleInjectorEndpointFactory>()
.AddSimpleInjector(container, options =>
{
options.AddAspNetCore();
});
// Register our service and Endpoint with SimpleInjector
container.RegisterSingleton<MyService>();
container.Register<MyEndpoint>();
// If the next line is uncommented, the MS DI container can correctly verify - but then we've essentially duplicated the
// registrations in the MS DI container and the SimpleInjector container.
//builder.Services.AddSingleton<MyService>();
// Fails here due to the application service not being registered in the MS DI container - which makes sense.
// Error:
// Some services are not able to be constructed (Error while validating the service descriptor
// 'ServiceType: MyEndpoint Lifetime: Transient ImplementationType: MyEndpoint':
// Unable to resolve service for type 'MyService' while attempting to activate 'MyEndpoint'.)
// (Error while validating the service descriptor 'ServiceType: MyWebApp.MyEndpoint Lifetime: Transient ImplementationType: MyWebApp.MyEndpoint':
// Unable to resolve service for type 'MyService' while attempting to activate 'MyWebApp.MyEndpoint'.)
var app = builder.Build();
app.UseAuthorization()
.UseFastEndpoints()
.UseSimpleInjector(container);
container.Verify();
app.Run();
public record SimpleInjectorEndpointFactory(Container Container) : IEndpointFactory
{
public BaseEndpoint Create(EndpointDefinition def, HttpContext ctx) =>
(BaseEndpoint)Container.GetInstance(def.EndpointType);
}
public class MyService
{
public async Task<string> GetVersion()
{
return await Task.FromResult("v1.0");
}
}
public class MyEndpoint : Endpoint<MyRequest>
{
private readonly MyService _svc;
// Injecting the application service, MyService into our endpoint.
public MyEndpoint(MyService svc)
{
_svc = svc;
}
public override void Configure()
{
Post("/api/user/create");
AllowAnonymous();
}
public override async Task HandleAsync(MyRequest req, CancellationToken ct)
{
var v = await _svc.GetVersion();
var response = new MyResponse()
{
FullName = req.FirstName + " " + req.LastName + " " + v,
IsOver18 = req.Age > 18
};
await SendAsync(response);
}
}
public class MyRequest
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int Age { get; set; }
}
public class MyResponse
{
public string FullName { get; set; }
public bool IsOver18 { get; set; }
}
Issue Analytics
- State:
- Created a year ago
- Comments:12 (9 by maintainers)
Top GitHub Comments
@dotnetjunkie yeah i see your point. well… it was done for a different purpose anyway.
Is that new
IServiceResolver
interface meant to be called by FastEndpoints for all its services? If so, this is likely not going to help any non-conforming containers (e.g. Simple Injector, Castle Windsor, etc), because it still assumes the built-in container is completely replaced with the non-conforming container, which will never be the case. A non-conforming container is non conforming because it can’t comply to the specification of the built-in DI abstraction, because it behaves in an incompatible manner. This can cause subtle but hard to fix issues, because FastEndpoints will for this service still expect the built-in DI behavior.Besides, there already exists an abstraction just like your
IServiceResolver
, which does the same. It’s calledIServiceProvider
. As far as I see, if you would allow theIServiceProvider
to be replaced with a custom one, you’d likely achieve exactly the same.Although allowing to replace/intercept calls to
IServiceProvider
(by providing a custom implementation for a non-conforming container) might have its use, it will typically still be rather awkward to create an implementation for a non-conforming container, because most of the FastEndpoints registrations will (and arguably should) be part of the built-in DI Container. This means that the customIServiceProvider
(orIServiceResolver
as you will) implementation must switch internally between resolving from the built-in container or the developer’s application container (i.e. the non-conforming container).I hope this makes sense. If I’m misinterpreting what
IServiceResolver
is for, please let me know.