1

Let's take an example of a very simple middleware which is the WelcomePageMiddleWare. If we want to use it we need to add the line app.UseWelcomePage() to the Configure method of the Startup class. By sneaking into the source code of ASP.NET Core, I have found that the method UseWelcomePage() is an extension method on IApplicationBuilder as shown here: https://github.com/dotnet/aspnetcore/blob/a450cb69b5e4549f5515cdb057a68771f56cefd7/src/Middleware/Diagnostics/src/WelcomePage/WelcomePageExtensions.cs#L79

That method call another extension method: app.UseMiddleware<WelcomePageMiddleware>(); and UseMiddleware<T> is another extension method.

Question: Why do we have these two levels? Why not simply have a single extension method on the IApplicationBuilder that contains the whole implementation?

Best_fit
  • 165
  • 7
  • 3
    Separation of concerns and extensibility come to mind. You want each method to be focused and at each level something new is added. It takes a while to wrap your head around why when you start from the top, but when you want to support multiple runtimes or allow developers to extend it replace different aspects of the functionality then that is what we'll designed code starts to look like under the hood. The main point to you is that it doesn't matter, to the designers it allows different elements to be individually tested and shipped to the rest of the team. – Chris Schaller Dec 08 '21 at 21:31
  • There also is testability. Falling back on code that has test coverage means you have to test less edge cases in your (copy) of the code. – TomTom Dec 08 '21 at 21:35
  • @ChrisSchaller Sorry, I am very junior, I am not sure what you mean by your answer. It's too abstract for me. Could you be more clear, a small example would be helpful, if possible? – Best_fit Dec 08 '21 at 21:40
  • @TomTom Do you have an example please? – Best_fit Dec 08 '21 at 21:41
  • 2
    The main point is that it doesn't matter at this point of your career @Best_fit. As you work with increasingly more complex code, you'll start to see more abstract patterns that are just hard to understand in the early stages – Camilo Terevinto Dec 08 '21 at 21:42
  • 2
    The key in this case is not to forget that you alone are not the targets audience for this code, we all are, those with projects that have been upgraded from legacy versions of the runtime are likely to be using different extensions to what you or the documentation will use today. It's this style of code that makes .net easier to consume. You get used to it and after a while you will come to understand and appreciate it, but for today just accept that it is a good design to support compatibility with previous documented ways to show the welcome page. – Chris Schaller Dec 08 '21 at 21:47
  • @ChrisSchaller I will try to re-phrase what you've said to see if I get your idea correctly. The inner-most extension method is `public static IApplicationBuilder UseMiddleware(this IApplicationBuilder app, [DynamicallyAccessedMembers(MiddlewareAccessibility)] Type middleware, params object?[] args)`. Indded It has some exotic types. There are different consumers of the Framework, it would be a pain for some users to cast their params in order to respect this method signature. The wrapper , i.e. outer-most extension method `UseWelcomePage` will avoid this pain for us. Is this correct? – Best_fit Dec 08 '21 at 21:57
  • @CamiloTerevinto I would like to know your opinion about my explanation in the comment above. – Best_fit Dec 08 '21 at 21:59
  • That's pretty much correct @Best_fit - notice that there's also another layer if I remember correctly, `public static IApplicationBuilder UseMiddleware` or similar. In the end though, it's just a "better way" (due to the name) of calling "app.Use(...)` (which exists for many other purposes unrelated to the middleware concept) – Camilo Terevinto Dec 08 '21 at 23:11

1 Answers1

2

Why not simply have a single extension method on the IApplicationBuilder that contains the whole implementation?

Basically that is what the first Extension method does, you are forced in that case to pass through the WelcomePageOptions which contains the whole implementation or at least all the optional arguments.

public static IApplicationBuilder UseWelcomePage(this IApplicationBuilder app, WelcomePageOptions options)
{
    if (app == null)
    {
        throw new ArgumentNullException(nameof(app));
    }
    if (options == null)
    {
        throw new ArgumentNullException(nameof(options));
    }

    return app.UseMiddleware<WelcomePageMiddleware>(Options.Create(options));
}

Ultimately it calls the OWIN UseMiddleware registration method which requires you to know the specific implementation syntax and the required types because UseMiddleware accepts an untyped and unspecified number of arguments via the params object[] args parameter. You can call this OWIN registration yourself but without type validation your run the risk of runtime errors when you pass in incompatible options.

The extension method is a helper that validates and enforces the specific input types and improves the natural readability of the code. That alone is reason enough to use the extension methods but this makes it discoverable via intellisense which helps us to rapidly consume middleware without needing to know what all of the possible options might be.

Adding Middleware as Extension method to IAppBuilder
Understanding and Creating OWIN Middlewares - Part 1
This older documentation on how to author Middleware outlines the different styles of implementation, what you see today is a simplification of all of those options presented as a Strongly Typed Extension to IAppBuilder.

The other 3 extensions in WelcomePageExtensions.cs offer common implementations to reduce the boiler-plate code that you might write and ultimately get wrong.

Every line of code I write to consume someone else's assemblies has the potential to fail, but it will be MY fault. A good framework author will provide different implementation options that suit standard patterns of compatibility so that callers are less likely to make silly mistakes.

UseWelcomePage(this IApplicationBuilder app, PathString path)

PathString is an AspNetCore concept that encapsulates the management of escaping, processing or constructing URI strings, for a pure ASP.Net Core codebase, this is the standard way to define an unescaped route to a resource.

UseWelcomePage(this IApplicationBuilder app, string path)

The string typed path is provided for lazier implementations (IMO) or codebases that have been migrated from ASP.Net. These two overloads cover most use cases for the WelcomePage.

The last Extension in that class is a simple overload that allows you to omit the configuration options altogether.

UseWelcomePage(this IApplicationBuilder app)

This style of overloading the extension method, instead of making the configuration parameter optional is preferred in this case because there are already multiple overloads with the same name. Mixing overloads and optional parameters can make the resolution of which method to execute ambiguous, so as a design pattern, use one technique, not both. This comment thread is an example of the discussions that go on if you get the balance wrong: https://thedailywtf.com/articles/comments/extending-yourself


Extensions methods are used in this way throughout the framework, not just in middleware, it allows the author of the intended functionality to write a single method with the optimal parameters to get the job done, or as in the case with middleware, we are forced to expose untyped or generic prototypes conforming to specific interfaces which have ambiguous usage.

The wrapper extensions are then added to make it easier to discover the functionality or to use it in different contexts without forcing the end user to cast their current variables into what might seem exotic types which might then be passed through to untyped (object) parameters.

This pattern also emerges when new versions of the framework improve the core functionality but they want to maintain backward compatibility with exiting type runtimes.

For more background on the extension method pattern in general have a read over Extension Methods Best Practises on the VB devblog.

Chris Schaller
  • 13,704
  • 3
  • 43
  • 81
  • Thanks a lot for the detailed answer! Which one of the two is the "first-level"? I guess the one without the generic type, ie `UseWelcomePage()`, right? – Best_fit Dec 08 '21 at 21:44
  • Also, could you tell me the source of the quote, please? – Best_fit Dec 08 '21 at 22:01
  • The quote is mine, I'll update my post with a reference to something more authorative and I'll give you a more direct answer. – Chris Schaller Dec 09 '21 at 02:19