Carter icon indicating copy to clipboard operation
Carter copied to clipboard

Consider making ICarterModule.AddRoutes a static abstract method

Open mariusz96 opened this issue 1 year ago • 8 comments

Hi,

Being accustomed to controllers I installed Carter and ran into an issue similar to these when injecting a scoped service into a module's constructor threw an exception:

  • https://github.com/CarterCommunity/Carter/issues/279
  • https://github.com/CarterCommunity/Carter/issues/288 .

And I also think that this unknowingly affects many people who inject transient services (ie MediatR library) into a module's constructor.

It appears to me that with an interface like this:

public interface ICarterModuleV2 // Naming things is hard.
{
    static abstract void AddRoutes(IEndpointRouteBuilder app);
}

this could be completely avoided and AddRoutes could still be called through reflection (easy, less efficient) or a source generator (hard, as efficient as hard-coded by a dev).

What do you think? Would this be a worthwhile addition?

EDIT: Turns out there's even a tutorial for this exact source generator: https://dev.to/joaofbantunes/mapping-aspnet-core-minimal-api-endpoints-with-c-source-generators-3faj.

mariusz96 avatar Mar 16 '24 10:03 mariusz96

As #279 says, you shouldn't be injecting services into your classes constructors, minimal API does not work like that. They should be in the RequestDelegate


public class MyModule : ICarterModule
{
    public void AddRoutes(IEndpointRouteBuilder app)
    {

        app.MapGet("/home", IService service => {
          
          var result = service.Foo();
          
          return Results.Ok(result);
        });
   }
}

jchannon avatar May 18 '24 17:05 jchannon

As #279 says, you shuldn't be injecting services into your cla*** constructors, minimal API does not work like that. They sh*uld be in the RequestDelegate

public cla** MyModule : ICarterModule
{
    public void AddRoutes(IEndpointRouteBuilder app)
    {

        app.MapGet("/h*me", IService service => {
          
          var result = service.Foo();
          
          return Results.Ok(result);
        });
   }
}

Yes, I understand that. But with a static abstract method: static abstract void AddRoutes(IEndpointRouteBuilder app), using a dependency injected into a constructor would not even compile. So it would prevent me from making an error and enforce correct usage.

Which is good for a library interface, no?

mariusz96 avatar May 18 '24 19:05 mariusz96

I agree but making a method on an interface abstract static is not going to cause a compiler error if a class implements it and injects something into a constructor

On Sat, 18 May 2024 at 20:03, Mariusz Stępień @.***> wrote:

As #279 https://github.com/CarterCommunity/Carter/issues/279 says, you shuldn't be injecting services into your cla*** constructors, minimal API does not work like that. They sh*uld be in the RequestDelegate

public cla** MyModule : ICarterModule{ public void AddRoutes(IEndpointRouteBuilder app) {

    app.MapGet("/h*me", IService service => {                    var result = service.Foo();                    return Results.Ok(result);        });

}}

Yes, I understand that. But with a static abstract method, using a dependency injected into a constructor would not even compile. So it would prevent me from making an error and enforce correct usage. Which is good for a library interface, no?

— Reply to this email directly, view it on GitHub https://github.com/CarterCommunity/Carter/issues/338#issuecomment-2118973884, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAAZVJTWIPQE2M3W7RPJZBTZC6QYLAVCNFSM6AAAAABEZGK6J6VHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDCMJYHE3TGOBYGQ . You are receiving this because you commented.Message ID: @.***>

jchannon avatar May 18 '24 19:05 jchannon

I've had a play and making the interface have the abstract static method would be an aid to help this.

As it's a breaking change I will do this for .NET9. For now I will add some XML docs as a pointer not to inject dependencies into classes.

What it will look like:


/// <summary>
/// An interface to define HTTP routes
/// </summary>
/// <remarks>Implementations of <see cref="ICarterModule"/> should not inject constructor dependencies. All dependencies should be supplied in the route <see cref="RequestDelegate"/></remarks>
public interface ICarterModule
{
    /// <summary>
    /// Invoked at startup to add routes to the HTTP pipeline
    /// </summary>
    /// <remarks>Implementations of <see cref="ICarterModule"/> should not inject constructor dependencies. All dependencies should be supplied in the route <see cref="RequestDelegate"/></remarks>
    /// <param name="app">An instance of <see cref="IEndpointRouteBuilder"/></param>
    static abstract void AddRoutes(IEndpointRouteBuilder app);
}

jchannon avatar May 20 '24 09:05 jchannon

@mariusz96 can you test the latest Carter version, it has an analyzer that warns if you have constructor dependencies 😄

jchannon avatar May 30 '24 15:05 jchannon

@mariusz96 can you test the latest Carter version, it has an analyzer that warns if you have constructor dependencies 😄

If you mean #349 then I think it's a great solution and this issue can be closed.

But I was not able to test the analyzer because though it gets installed under Dependencies\Analyzers, it throws an exception during build. Could be something on my end - maybe I just need to update something on my machine?

mariusz96 avatar Jun 03 '24 20:06 mariusz96

Is it a Windows machine?

There’s an open issue for this unfortunately

On Mon, 3 Jun 2024 at 21:20, Mariusz Stępień @.***> wrote:

@mariusz96 https://github.com/mariusz96 can you test the latest Carter version, it has an analyzer that warns if you have constructor dependencies 😄

If you mean #349 https://github.com/CarterCommunity/Carter/pull/349 then I think it's a great solution and this issue can be closed.

But I was not able to test the analyzer because although it gets installed under Dependencies\Analyzers, it throws an exception during build. Could be something on my end - maybe I just need to update something on my machine?

— Reply to this email directly, view it on GitHub https://github.com/CarterCommunity/Carter/issues/338#issuecomment-2146051641, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAAZVJXOLMTCRQONTAE3YH3ZFTFZJAVCNFSM6AAAAABEZGK6J6VHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDCNBWGA2TCNRUGE . You are receiving this because you commented.Message ID: @.***>

jchannon avatar Jun 03 '24 21:06 jchannon

Is it a Windows machine? There’s an open issue for this unfortunately

Yes, it is and the exception is the same as in the other issue.

For what it's worth, xunit analyzers that get installed when creating new unit test project in Visual Studio work fine over here and are probably "battle-tested". Maybe their setup could be stolen/leveraged somehow?

mariusz96 avatar Jun 04 '24 06:06 mariusz96