question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Issue with Non-conforming DI Container integration with constructor injected services

See original GitHub issue

After 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:closed
  • Created a year ago
  • Comments:12 (9 by maintainers)

github_iconTop GitHub Comments

1reaction
dj-nitehawkcommented, Nov 8, 2022

@dotnetjunkie yeah i see your point. well… it was done for a different purpose anyway.

0reactions
dotnetjunkiecommented, Nov 8, 2022

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 called IServiceProvider. As far as I see, if you would allow the IServiceProvider 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 custom IServiceProvider (or IServiceResolver 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.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Add support for non-conforming containers by adding a ...
The current implementation of grpc-dotnet takes a hard dependency on the framework's IServiceProvider abstraction.
Read more >
Dependency Injection container in constructor
The problem is that we are about to use a third party software which can rely on objects that use only parameterless (default)...
Read more >
Criticism and disadvantages of dependency injection
The problem with DI in this regard is that it's opaque. The container is a black box. Objects simply appear from somewhere and...
Read more >
.NET Dependency Injection With Constructor Parameters
In this article, we'll look at different ways to use dependency injection with constructor parameters in .NET Core.
Read more >
Dependency injection in ASP.NET Core
Dependency injection addresses these problems through: The use of an interface or base class to abstract the dependency implementation.
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found