3

I have a method in my NuGet package library that looks like this:

public async Task<string> GetLogoutUrl(HttpContext httpContext, string encodedUserJwt, string relativeRedirectUrl)
{
    var redirectUrl = $"{httpContext.Request.Scheme}://{httpContext.Request.Host}/{relativeRedirectUrl}";
    var logoutUrlParameters = $"id_token_hint={encodedUserJwt}&post_logout_redirect_uri={redirectUrl}";
    var logoutUrl = $"{await DiscoveryDocument.GetLogoutEndpoint()}?{logoutUrlParameters}";
    return logoutUrl;
}

I would really like to make it an extension method of HttpContext so the users of my NuGet can call it like this: HttpContext.GetLogoutUrl(encodedUserJwt, "Home");

But the problem with that is that this method uses DiscoveryDocument that is injected into the class where this method currently resides. I have been trying to think of a way I could get the nice syntax of an extension method, but still be able to get the DiscoveryDocument from dependency injection while not breaking so many best practices that I can't sleep at night.

Is there a way in C# to have an extension method and still connect to a dependency injected object?

NOTE: I don't want to make the developers that use my NuGet to have to pass in an instance of DiscoveryDocument. For the architecture of my NuGet that would be worse than just having GetLogoutUrl not be an extension method.

Vaccano
  • 78,325
  • 149
  • 468
  • 850
  • 1
    Extension method -> static class -> no DI :) – Camilo Terevinto Dec 16 '20 at 19:20
  • 1
    Why *not* require it as a parameter? `DiscoveryDocument` is effectively an input to that method, even if it's by implicit `this.`. It's a dependency. – madreflection Dec 16 '20 at 19:21
  • @madreflection - While public, it is at a deeper level than what I want my users to have to interact with. As I said, it will be better to keep it in the class it is in. (The class it is in is at a "medium" level where as `DiscoveryDocument` is at a "Low" level.) – Vaccano Dec 16 '20 at 19:32
  • Related: https://stackoverflow.com/questions/16741876/how-to-avoid-service-locator-in-net-extension-methods – Steven Dec 17 '20 at 08:30
  • Related: https://stackoverflow.com/questions/16282223/implementing-dependency-injection-static-methods – Steven Dec 17 '20 at 08:31
  • Related: https://stackoverflow.com/questions/52325438/how-to-inject-dependency-to-static-class (duplicate?) – Steven Dec 17 '20 at 08:32

1 Answers1

3

Service locator via

IServiceProvider HttpContext.RequestServices { get }

will have to be used since this is a static call , which does not usually play well with DI since it is usually centered around instances.

The potential extension method would end up looking something like this

public static async Task<string> GetLogoutUrl(this HttpContext httpContext, 
    string encodedUserJwt, string relativeRedirectUrl) {
    var redirectUrl = $"{httpContext.Request.Scheme}://{httpContext.Request.Host}/{relativeRedirectUrl}";
    var logoutUrlParameters = $"id_token_hint={encodedUserJwt}&post_logout_redirect_uri={redirectUrl}";

    var DiscoveryDocument = httpContext.RequestServices
        .GetRequiredService<DiscoveryDocumentTypeHere>();

    var logoutUrl = $"{await DiscoveryDocument.GetLogoutEndpoint()}?{logoutUrlParameters}";
    return logoutUrl;
}
Nkosi
  • 235,767
  • 35
  • 427
  • 472
  • Thank you for the answer! I get that Service Locator is an anti-pattern, but how does this not play well with DI? (If I go this way, I need to know what problems in my DI system I may see.) – Vaccano Dec 16 '20 at 19:49
  • None provided you are the one registering the known types in the composition root. I assume your Nuget has some configuration extension that adds all the necessary dependencies. – Nkosi Dec 16 '20 at 19:50
  • My NuGet does add the dependencies. I had considered using a singleton to dependency inject into, and then accessing the singleton from the static extension method. I think this is much cleaner. Though still using an anti-pattern, I think the trade off may be worth it. Thank you for the answer! – Vaccano Dec 16 '20 at 19:54
  • 5
    the anti-pattern in this scope is that the implementation needs DI types registered and configured for it's usability. Before you commit to this pattern, do a unit test of your extension method. – Brett Caswell Dec 16 '20 at 19:59