Oğuzhan Açıkgöz Computacenter AG & Co. oHG

Aus Erfahrung vieler Refactorings, insbesondere im Enterprise-Umfeld mit vielen vielen Zeilen Code, fällt mir immer wieder auf, dass sehr oft redundanter Code produziert wird, der viel mehr Zeit kostet und die Wartung, Erweiterung und jede Änderung immenser erschwert als ein Filterattribut. Die meisten dieser Redundanzen lassen sich durch wiederverwendbare Filter oder Eingriffe in die Pipeline reduzieren.

In ASP.NET Core besteht die Anfragepipeline aus so genannter Middleware. Neben bereits mitgelieferter Middleware wie Routing, Authentication oder MVC Middleware, lässt sich durch selbstimplementierte Middleware Einfluss auf die Anfragepipeline nehmen. ASP.NET MVS hatte zu diesem Zweck u. a. Filter zur Verfügung gestellt, die in ASP.NET Core weiterhin existieren. Im Folgenden werden wir die Unterschiede von Middleware und Filtern betrachten und klären, welche der beiden Methoden für welche Szenarien geeignet ist.

„Hiermit lässt sich die Anfragepipeline manipulieren und ermöglicht die auf eigene Ansprüche zugeschnittene Reaktion der Anwendung auf bestimmte Anfragen“. Diese Aussage trifft erstmal sowohl auf Filter als auch Middleware zu. Durch beide Methoden lassen sich die gleichen Ziele erreichen: Man kann mit ihnen einen Eingriff in die Standardpipeline von ASP.NET Core vornehmen. Zunächst betrachten wir jeweils Middleware und Filter separat, um im Anschluss die Differenzen besser erkennen zu können.

Middleware

Middleware stellt eine einzelne, in sich abgeschlossene Komponente der HTTP-Request-Pipelines dar. Damit ist der Lebenszyklus einer Anfrage an unsere Anwendung gemeint, die schlussendlich mit einem HTTP Response endet. Auch das MVC-Template ist in ASP.NET Core eine Middleware, die beim Anlegen eines neuen ASP.NET-Core-Projekts automatisch generiert wird (Abb. 1).

Abb. 1: Neues ASP.NET-Projekt

Abb. 1: Neues ASP.NET-Projekt

Die Middleware wird innerhalb der Startup-Klasse in der Configure-Methode definiert. Eine beispielhafte Darstellung dafür bietet Listing 1.

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
  app.UseStaticFiles();

    app.UseMvc(routes =>
    {
      routes.MapRoute(
        name: "default",
        template: "{controller=Home}/{action=Index}/{id?}");
    });
}

Die Middleware wird der definierten Reihenfolge entsprechend ausgeführt. Im obigen Beispiel wird zuerst die Static-Files-Middleware und anschließend die MVC-Middleware definiert und in dieser Reihenfolge beim Start ausgeführt. Dieser Pipeline kann ohne großen Aufwand eigene Middleware hinzugefügt werden. Um einen direkten Vergleich zum Filter zu haben, erstellen wir eine banale Logging-Middleware, die zu Beginn jeder Anfrage einen Log-in der Konsole ausgibt. Dazu erstellen wir eine Klasse namens LoggingMiddleware – vorzugsweise in einem neuen Ordner, den wir Middleware nennen – und fügen den in Listing 2 angegebenen Code ein.

public class LoggingMiddleware
{
  private readonly RequestDelegate next;
  private readonly ILogger logger;

  public LoggingMiddleware(RequestDelegate next, ILoggerFactory loggerFactory)
  {
    this.next = next;
    this.logger = loggerFactory.CreateLogger(); ;
  }

  public async Task Invoke(HttpContext context)
  {
    this.logger.LogDebug("Middleware started");

    await next.Invoke(context);

    this.logger.LogDebug("Middleware ended");
  }
}

public static class LoggingMiddlewareExtension
{
  public static IApplicationBuilder UseLoggingMiddleware(this IApplicationBuilder builder)
  {
    return builder.UseMiddleware();
  }
}

Nun gehen wir den Code schrittweise durch:

  1. Zuerst ist immer ein Konstruktor zu erstellen, der mindestens einen Parameter vom Typ RequestDelegate beinhaltet. Grund dafür ist das Explicit Dependency Principle, d. h. per Dependency Injection wird sichergestellt, dass jede Middleware ein RequestDelegate besitzt. Hinter dem RequestDelegate steckt, wie der Name schon sagt, ein Delegat, der auf die nächste Middleware zeigt. Jede Middleware muss die Methode Invoke(…) implementieren, wodurch sich mithilfe des RequestDelegate die nächste Middleware per Invoke(…) ausführen lässt.
  2. Wie im ersten Punkt bereits erwähnt, muss jede Middleware eine Invoke-Methode mit folgender Signatur implementieren: Invoke(HttpContext context). Die Middleware wird mit der Invoke-Methode aufgerufen, d. h. die Logik der Middleware ist hier zu implementieren. In unserem Szenario erzeugen wir einen Logeintrag mit der Information, dass die Middleware gestartet wurde, und rufen im Anschluss die nächste Middleware auf, die wiederum weitere Middleware aufrufen oder die Pipeline beenden könnte. In beiden Fällen durchläuft die Pipeline erneut unsere Logging-Middleware und informiert per Logeintrag, dass die Middleware beendet ist. Wichtig ist, sich vor Augen zu führen, dass innerhalb der Middleware ausschließlich HttpContext zur Verfügung steht. Implizit ist die Middleware auf die Informationen von HttpContext beschränkt.

Den vollständigen Artikel lesen Sie in der Ausgabe:

Windows Developer 6.17 - "ASP.NET Core im Einsatz"

Alle Infos zum Heft
579797394ASP.NET Core: Middleware vs. Filter
X
- Gib Deinen Standort ein -
- or -