Juni 2016

Band 31, Nummer 6

ASP.NET – Verwenden benutzerdefinierter Middleware zum Erkennen und Beheben von Fehlern des Typs „404“ in ASP.NET Core-Apps

Von Steve Smith:

Wenn Sie schon einmal Dinge in der Schule oder in einem Vergnügungspark verloren haben, können Sie diese mit etwas Glück im zuständigen Fundbüro abholen. In Webanwendungen geben Benutzer häufig Anforderungen für Pfade aus, die nicht vom Server verarbeitet werden. Das Ergebnis sind Antwortcodes vom Typ „404 Nicht gefunden“ (und gelegentlich humorvolle Seiten, die dem Benutzer das Problem beschreiben). Normalerweise muss der Benutzer dann selbst das Gesuchte durch wiederholtes Raten oder mithilfe einer Suchmaschine ermitteln. Durch das Hinzufügen einer Middlewarekomponente zu Ihrer ASP.NET Core-App können Sie jedoch ein „Fundbüro“ einrichten, das Benutzer bei der Suche nach den gewünschten Ressourcen unterstützt.

Was ist Middleware?

Die Dokumentation zu ASP.NET Core definiert Middleware als „Komponenten, die in eine Anwendungspipeline assembliert werden, um Anforderungen und Antworten zu verarbeiten“. Im Grunde handelt es sich bei Middleware um einen Anforderungsdelegaten, der als Lambdaausdruck wie im folgenden Beispiel gezeigt dargestellt wird:

app.Run(async context => {
  await context.Response.WriteAsync(“Hello world”);
});

Wenn Ihre Anwendung nur aus dieser kleinen Middlewarekomponente besteht, wird „Hello world“ für jede Anforderung zurückgegeben. Da kein Verweis auf die nächste Middlewarekomponente erfolgt, beendet dieses Beispiel die Pipeline – Code, der anschließend definiert wurde, wird nicht ausgeführt. Dass es sich um das Ende der Pipeline handelt, bedeutet jedoch nicht, dass sie nicht von weiterer Middleware „umschlossen“ sein kann. Sie können z. B. Middleware hinzufügen, die der vorherigen Antwort einen Header hinzufügt:

app.Use(async (context, next) =>
{
  context.Response.Headers.Add("Author", "Steve Smith");
  await next.Invoke();
});
app.Run(async context =>
{
  await context.Response.WriteAsync("Hello world ");
});

Der Aufruf von „app.Use“ umschließt den Aufruf von „app.Run“. Der Aufruf erfolgt mithilfe von „next.Invoke“. Wenn Sie Ihre eigene Middleware schreiben, können Sie entscheiden, ob Vorgänge vor, nach oder vor und nach der nächsten Middleware in der Pipeline ausgeführt werden sollen. Sie können die Pipeline auch kurzschließen, indem kein Aufruf von „next“ erfolgt. Ich werde demonstrieren, wie dies Sie beim Erstellen von Middleware unterstützen kann, die Probleme des Typs „404“ behebt.

Wenn Sie die MVC Core-Standardvorlage verwenden, enthält Ihre anfängliche Startdatei keinen solchen delegatbasierten Low-Level-Middlewarecode. Es wird empfohlen, Middleware in ihren eigenen Klassen zu verkapseln und Erweiterungsmethoden (namens „UseMiddlewareName“) zu verwenden, die aus der Startdatei aufgerufen werden können. Die integrierte ASP.NET-Middleware hält sich an diese Konvention, wie die folgenden Aufrufe zeigen:

if (env.IsDevelopment())
{
  app.UseDeveloperExceptionPage();
}
app.UseStaticFiles()
app.UseMvc();

Die Reihenfolge Ihrer Middleware ist wichtig. Im oben aufgeführten Code sollte der Aufruf von „UseDeveloperExceptionPage“ (nur konfiguriert, wenn die App in einer Entwicklungsumgebung ausgeführt wird) die andere Middleware umschließen, die ggf. einen Fehler verursachen kann (also zuvor hinzugefügt werden).

In einer eigenen Klasse

Ich möchte meine Startklasse nicht mit allen Lambdaausdrücken und der ausführlichen Implementierung meiner Middleware vollstopfen. Meine Middleware soll genau wie die integrierte Middleware der Pipeline mit nur einer Codezeile hinzugefügt werden. Außerdem gehe ich davor aus, dass Dienste in meine Middleware mithilfe von DI (Dependency Injection, Abhängigkeitsinjektion) injiziert werden müssen. Dies kann auf einfache Weise erreicht werden, sobald das Refactoring der Middleware in ihre eigene Klasse erfolgt. (Weitere Informationen zu DI in ASP.NET Core finden Sie in meinem Artikel aus dem Monat Mai unter msdn.com/magazine/mt703433.)

Da ich Visual Studio verwende, kann ich Middleware hinzufügen, indem ich „Neues Element hinzufügen“ verwende und die Vorlage der Middlewareklasse auswähle. Abbildung 1 zeigt den Standardinhalt, den diese Vorlage generiert, einschließlich einer Erweiterungsmethode zum Hinzufügen der Middleware zur Pipeline über „UseMiddleware“.

Abbildung 1: Vorlage der Middlewareklasse

public class MyMiddleware
{
  private readonly RequestDelegate _next;
  public MyMiddleware(RequestDelegate next)
  {
    _next = next;
  }
  public Task Invoke(HttpContext httpContext)
  {
    return _next(httpContext);
  }
}
// Extension method used to add the middleware to the HTTP request pipeline.
public static class MyMiddlewareExtensions
{
  public static IApplicationBuilder UseMyMiddleware(this IApplicationBuilder builder)
  {
    return builder.UseMiddleware<MyMiddleware>();
  }
}

Normalerweise kennzeichne ich die Invoke-Methodensignatur als asynchron und ändere den Code dann folgendermaßen:

await _next(httpContext);

Auf diese Weise wird der Aufruf asynchron.

Nachdem ich eine separate Middlewareklasse erstellt habe, verschiebe ich die Delegatprogrammlogik in die Invoke-Methode. Dann ersetze ich den Aufruf in „Configure“ durch einen Aufruf der UseMyMiddleware-Erweiterungsmethode. Wenn Sie die App nun ausführen, sollte sich bestätigen, dass das Verhalten der Middleware unverändert ist, Außerdem kann die Configure-Klasse wesentlich einfacher verfolgt werden, wenn sie aus einer Reihe von UseSomeMiddleware-Anweisungen besteht.

Erkennen und Aufzeichnen von Antworten des Typs „404 Nicht gefunden“

Wenn in einer ASP.NET-Anwendung eine Anforderung erfolgt, die mit keinem der Handler übereinstimmt, enthält die Antwort einen „StatusCode“, der auf den Wert 404 festgelegt ist. Ich kann eine kleine Middlewarekomponente erstellen, die das Vorhandensein dieses Antwortcodes überprüft (nach dem Aufruf von „_next“) und eine Aktion zum Aufzeichnen der Details der Anforderung ausführt:

await _next(httpContext);
if (httpContext.Response.StatusCode == 404)
{
  _requestTracker.Record(httpContext.Request.Path);
}

Ich möchte die Anzahl der 404-Antwortcodes nachverfolgen können, die für einen bestimmten Pfad aufgetreten sind, damit ich die häufigsten beheben und meine korrektiven Maßnahmen optimal nutzen kann. Zu diesem Zweck erstelle ich einen Dienst namens „RequestTracker“, der Instanzen von 404-Anforderungen basierend auf ihrem Pfad aufzeichnet. „RequestTracker“ wird an meine Middleware mithilfe von DI übergeben. Abbildung2 zeigt dies.

Abbildung 2: Übergeben von „RequestTracker“ an die Middleware mithilfe von DI (Dependency Injection, Abhängigkeitsinjektion)

public class NotFoundMiddleware
{
  private readonly RequestDelegate _next;
  private readonly RequestTracker _requestTracker;
  private readonly ILogger _logger;
  public NotFoundMiddleware(RequestDelegate next,
    ILoggerFactory loggerFactory,
    RequestTracker requestTracker)
  {
    _next = next;
    _requestTracker = requestTracker;
    _logger = loggerFactory.CreateLogger<NotFoundMiddleware>();
  }
}

Ich rufe die Erweiterungsmethode „UseNotFoundMiddleware“ auf, um „NotFoundMiddleware“ meiner Pipeline hinzuzufügen. Da nun ein benutzerdefinierter Dienst im Dienstecontainer konfiguriert sein muss, muss ich jedoch auch sicherstellen, dass dieser registriert ist. Ich erstelle eine Erweiterungsmethode für „IServiceCollection“ namens „AddNotFoundMiddleware“ und rufe diese Methode dann in „ConfigureServices“ in der Startdatei auf:

public static IServiceCollection AddNotFoundMiddleware(
  this IServiceCollection services)
{
  services.AddSingleton<INotFoundRequestRepository,
    InMemoryNotFoundRequestRepository>();
  return services.AddSingleton<RequestTracker>();
}

In diesem Fall stellt meine AddNotFoundMiddleware-Methode sicher, dass eine Instanz von „RequestTracker“ als Singleton im Dienstecontainer konfiguriert ist, damit diese für die Injektion in die „NotFoundMiddleware“ bei deren Erstellung verfügbar ist. Außerdem wird eine In-Memory-Implementierung von „INotFoundRequestRepository“ eingebunden. Dieses Objekt verwendet „RequestTracker“ zum persistenten Speichern seiner Daten.

Da zahlreiche gleichzeitige Anforderungen für den gleichen fehlenden Pfad eingehen können, verwendet der Code in Abbildung 3 eine einfache Sperre, um sicherzustellen, dass keine doppelten Instanzen von „NotFoundRequest“ hinzugefügt werden und die Anzahl ordnungsgemäß inkrementiert wird.

Abbildung 3: „RequestTracker“

public class RequestTracker
{
  private readonly INotFoundRequestRepository _repo;
  private static object _lock = new object();
  public RequestTracker(INotFoundRequestRepository repo)
  {
    _repo = repo;
  }
  public void Record(string path)
  {
    lock(_lock)
    {
      var request = _repo.GetByPath(path);
      if (request != null)
      {
        request.IncrementCount();
      }
      else
      {
        request = new NotFoundRequest(path);
        request.IncrementCount();
        _repo.Add(request);
      }
    }
  }
  public IEnumerable<NotFoundRequest> ListRequests()
  {
    return _repo.List();
  }
  // Other methods
}

Anzeigen von Anforderungen des Typs „Nicht gefunden“

Nachdem ich nun 404-Antwortcodes aufzeichnen kann, benötige ich eine Möglichkeit zum Anzeigen dieser Daten. Zu diesem Zweck erstelle ich eine weitere kleine Middlewarekomponente, die eine Seite mit allen aufgezeichneten „NotFoundRequests“ in der Reihenfolge der Häufigkeit ihres Auftretens anzeigt. Diese Middleware überprüft, ob die aktuelle Anforderung mit einem bestimmten Pfad übereinstimmt, und ignoriert (und übergibt) alle Anforderungen, die nicht mit dem Pfad übereinstimmen. Für übereinstimmende Pfade gibt die Middleware eine Seite mit einer Tabelle zurück, die die NotFound-Anforderungen in der Reihenfolge ihrer Häufigkeit enthält. Dort kann der Benutzer einzelnen Anforderungen einen korrigierten Pfad zuordnen, der von zukünftigen Anforderungen verwendet wird, anstatt einen 404-Antwortcode zurückzugeben.

Abbildung 4 zeigt, wie einfach es ist, die „NotFoundPageMiddleware“ die Überprüfung auf einen bestimmten Pfad ausführen zu lassen und Aktualisierungen basierend auf Werten der Abfragezeichenfolge unter Verwendung des gleichen Pfads auszuführen. Aus Sicherheitsgründen sollte der Zugriff auf den NotFoundPageMiddleware-Pfad auf Administratoren eingeschränkt werden.

Abbildung 4: „NotFoundPageMiddleware“

public async Task Invoke(HttpContext httpContext)
{
  if (!httpContext.Request.Path.StartsWithSegments("/fix404s"))
  {
    await _next(httpContext);
    return;
  }
  if (httpContext.Request.Query.Keys.Contains("path") &&
      httpContext.Request.Query.Keys.Contains("fixedpath"))
  {
    var request = _requestTracker.GetRequest(httpContext.Request.Query["path"]);
    request.SetCorrectedPath(httpContext.Request.Query["fixedpath"]);
    _requestTracker.UpdateRequest(request);
  }
  Render404List(httpContext);
}

In der vorliegenden Form ist die Middleware für das Lauschen auf den Pfad „/fix404s“ hartcodiert. Es ist sinnvoll, diese Angabe konfigurierbar zu gestalten, damit andere Apps jeden gewünschten Pfad angeben können. Die gerenderte Liste der Anforderungen zeigt alle Anforderungen in der Reihenfolge der Anzahl der aufgezeichneten 404-Antwortcodes an. Dies erfolgt unabhängig davon, ob ein korrigierter Pfad eingerichtet wurde. Es wäre keine schwierige Aufgabe, die Middleware durch Filterfunktionen zu optimieren. Ein weiteres interessantes Feature könnte die Aufzeichnung ausführlicherer Informationen (damit Sie die beliebtesten Umleitungen identifizieren können) oder die Ermittlung der häufigsten 404-Antwortcodes während der letzten sieben Tage sein. Diese Aufgaben bieten sich als Übung für den Leser (oder die Open Source-Community) an.

Abbildung 5 zeigt ein Beispiel für das Aussehen der gerenderten Seite.

Die Fix 404s-Seite
Abbildung 5: Die Fix 404s-Seite

Hinzufügen von Optionen

Ich möchte einen anderen Pfad für die Fix 404s-Seite in anderen Apps angeben können. Zu diesem Zweck erstelle ich am besten eine Options-Klasse und übergeben diese dann mithilfe von DI an die Middleware. Für diese Middleware erstelle ich eine Klasse („NotFoundMiddlewareOptions“), die eine Eigenschaft namens „Path“ mit einem Wert enthält, der standardmäßig „/fix404s“ lautet. Diese kann ich mithilfe der IOptions<T>-Schnittstelle an „NotFoundPageMiddleware“ übergeben und dann ein lokales Feld auf die Eigenschaft „Value“ dieses Typs festlegen. Anschließend kann mein „magischer“ Zeichenfolgenverweis auf „fix404s“ aktualisiert werden:

if (!httpContext.Request.Path.StartsWithSegments(_options.Path))

Beheben von 404-Antwortcodes

Wenn eine Anforderung eingeht, die mit einer „NotFoundRequest“ mit einer „CorrectedUrl“ übereinstimmt, sollte die „NotFoundMiddleware“ die Anforderung so ändern, dass die „CorrectedUrl“ verwendet wird. Dies kann durch einfaches Aktualisieren der Eigenschaft „path“ der Anforderung geschehen:

string path = httpContext.Request.Path;
string correctedPath = _requestTracker.GetRequest(path)?.CorrectedPath;
if(correctedPath != null)
{
  httpContext.Request.Path = correctedPath; // Rewrite the path
}
await _next(httpContext);

Mit dieser Implementierung funktioniert jede korrigierte URL so, als ob diese Anforderung direkt für den korrigierten Pfad erfolgt wäre. Die Anforderungspipeline wird anschließend mit dem nun neu geschriebenen Pfad fortgesetzt. Dies kann das gewünschte Verhalten darstellen, muss es aber nicht, weil sich doppelt indizierter Inhalt für mehrere URLs ggf. negativ auf die Suchmaschinenergebnisse auswirkt. Dieser Ansatz kann dazu führen, dass Dutzende von URLs alle dem gleichen zugrunde liegenden Anwendungspfad zugeordnet werden. Aufs diesem Grund ist es häufig günstiger, 404-Antwortcodes mithilfe einer permanenten Weiterleitung (Statuscode 301) zu beheben.

Wenn ich die Middleware so ändere, dass eine Umleitung gesendet wird, kann ich die Middleware in diesem Fall kurzschließen, weil die Ausführung der restlichen Pipeline nicht erforderlich ist, wenn ich mich dafür entschieden habe, nur einen 301-Statuscode zurückzugeben:

if(correctedPath != null)
{
  httpContext.Response. Redirect(httpContext.Request.PathBase + correctedPath +
    httpContext.Request.QueryString, permanent: true);
  return;
}
await _next(httpContext);

Denken Sie unbedingt daran, keine korrigierten Pfade festzulegen, die zu unendlichen Umleitungsschleifen führen.

Idealerweise sollte die „NotFoundMiddleware“ das Neuschreiben von Pfaden sowie permanente Umleitungen unterstützen. Ich kann dies mithilfe meiner „NotFoundMiddlewareOptions“ implementieren und eine der beiden Optionen für alle Anforderungen festlegen, oder ich kann „CorrectedPath“ für den NotFoundRequest-Pfad so ändern, dass der Pfad und der zu verwendende Mechanismus angegeben werden. Im Moment aktualisiere ich nur die Klasse „options“ so, dass das Verhalten unterstützt wird, und übergebe „IOptions<NotFoundMiddleOptions>“ an die „NotFoundMiddleware“ (genau so, wie es bereits für die „NotFoundPageMiddleware“ geschieht). Nachdem diese Optionen eingerichtet wurden, sieht die Umleitungs-/Neuschreibprogrammlogik wie folgt aus:

if(correctedPath != null)
{
  if (_options.FixPathBehavior == FixPathBehavior.Redirect)
  {
    httpContext.Response.Redirect(correctedPath, permanent: true);
    return;
  }
  if(_options.FixPathBehavior == FixPathBehavior.Rewrite)
  {
    httpContext.Request.Path = correctedPath; // Rewrite the path
  }
}

An diesem Punkt weist die NotFoundMiddlewareOptions-Klasse zwei Eigenschaften auf. Eine dieser Eigenschaften ist eine Enumeration

public enum FixPathBehavior
{
  Redirect,
  Rewrite
}
public class NotFoundMiddlewareOptions
{
  public string Path { get; set; } = "/fix404s";
  public FixPathBehavior FixPathBehavior { get; set; } 
    = FixPathBehavior.Redirect;
}

Konfigurieren der Middleware

Nachdem Sie die Optionen für Ihre Middleware eingerichtet haben, übergeben Sie eine Instanz dieser Optionen an Ihre Middleware, wenn Sie sie in der Startdatei konfigurieren. Alternativ können Sie die Optionen auch an Ihre Konfiguration binden. Die ASP.NET-Konfiguration ist sehr flexibel und kann an Umgebungsvariablen bzw. Einstellungsdateien gebunden oder programmgesteuert erstellt werden. Unabhängig davon, wo die Konfiguration festgelegt wird, können die Optionen mit einer einzigen Codezeile an die Konfiguration gebunden werden:

services.Configure<NotFoundMiddlewareOptions>(
  Configuration.GetSection("NotFoundMiddleware"));

Nachdem dies nun implementiert wurde, kann ich mein NotFoundMiddleware-Verhalten konfigurieren, indem ich die Datei „appsettings.json“ (die Konfiguration, die ich in dieser Instanz verwende) aktualisiere:

"NotFoundMiddleware": {
  "FixPathBehavior": "Redirect",
  "Path": "/fix404s"
}

Beachten Sie, dass die Konvertierung aus zeichenfolgenbasierten JSON-Werten in der Einstellungsdatei in die Enumeration für „FixPathBehavior“ automatisch durch das Framework vorgenommen wird.

Dauerhaftigkeit

Bisher funktioniert alles hervorragend. Leider sind meine Liste der 404-Antwortcodes und ihre korrigierten Pfade jedoch in einer In-Memory-Sammlung gespeichert. Dies bedeutet, dass diese Daten bei jedem Neustart meiner Anwendung verloren gehen. Es kann sinnvoll sein, dass meine App die Anzahl der 404-Antwortcodes regelmäßig zurücksetzt, damit ich einschätzen kann, welche zurzeit am häufigsten auftreten – ich möchte aber gewiss nicht die korrigierten Pfade verlieren, die ich festgelegt habe.

Das ich den „RequestTracker“ so konfiguriert habe, dass er eine Abstraktion für seine Dauerhaftigkeit verwendet („INotFoundRequestRepository“), ist es jedoch glücklicherweise relativ einfach, Unterstützung zum Speichern der Ergebnisse in einer Datenbank mithilfe von Entity Framework Core (EF) hinzuzufügen. Darüber hinaus kann ich einzelnen Apps auf einfache Weise die Auswahl zwischen der Verwendung von EF und der In-Memory-Konfiguration ermöglichen (sinnvoll zum Testen), indem separate Hilfsmethoden bereitgestellt werden.

Zunächst benötige ich einen „DbContext“, um EF zum Speichern und Abrufen von „NotFoundRequests“ zu verwenden. Ich möchte keinen „DbContext“ verwenden, den die App ggf. konfiguriert hat. Ich erstelle daher einen „DbContext“ nur für die „NotFoundMiddleware“:

public class NotFoundMiddlewareDbContext : DbContext
{
  public DbSet<NotFoundRequest> NotFoundRequests { get; set; }
  protected override void OnModelCreating(ModelBuilder modelBuilder)
  {
    base.OnModelCreating(modelBuilder);
    modelBuilder.Entity<NotFoundRequest>().HasKey(r => r.Path);
  }
}

Sobald der „DbContext“ vorhanden ist, muss ich die Repositoryschnittstelle implementieren. Ich erstelle ein „EfNotFoundRequestRepository“, das eine Instanz von „NotFoundMiddlewareDbContext“ in seinem Konstruktor anfordert und einem privaten Feld („_dbContext“) zuweist. Die Implementierung der einzelnen Methoden ist einfach, wie das folgende Beispiel zeigt:

public IEnumerable<NotFoundRequest> List()
{
  return _dbContext.NotFoundRequests.AsEnumerable();
}
public void Update(NotFoundRequest notFoundRequest)
{
  _dbContext.Entry(notFoundRequest).State = EntityState.Modified;
  _dbContext.SaveChanges();
}

An diesem Punkt müssen nur noch „DbContext“ und das EF-Repository im Dienstecontainer der App eingerichtet werden. Dies geschieht in einer neuen Erweiterungsmethode (außerdem benenne ich die ursprüngliche Erweiterungsmethode so um, dass sie sich auf die In-Memory-Version bezieht):

public static IServiceCollection AddNotFoundMiddlewareEntityFramework(
  this IServiceCollection services, string connectionString)
{
    services.AddEntityFramework()
      .AddSqlServer()
      .AddDbContext<NotFoundMiddlewareDbContext>(options =>
        options.UseSqlServer(connectionString));
  services.AddSingleton<INotFoundRequestRepository,
    EfNotFoundRequestRepository>();
  return services.AddSingleton<RequestTracker>();
}

Ich habe mich dafür entschieden, die Verbindungszeichenfolge zu übergeben, anstatt sie in „NotFoundMiddlewareOptions“ zu speichern, weil die meisten ASP.NET-Apps, die EF verwenden, bereits eine Verbindungszeichenfolge in der ConfigureServices-Methode bereitstellen. Wenn gewünscht, kann die gleiche Variable beim Aufrufen von „services.AddNotFoundMiddleware­EntityFramework(connectionString)“ verwendet werden.

Die letzte Aktion, die eine neue App ausführen muss, bevor sie die EF-Version dieser Middleware verwenden kann, besteht im Ausführen der Migrationen, damit sichergestellt wird, dass die Struktur der Datenbanktabelle ordnungsgemäß konfiguriert ist. Ich muss den „DbContext“ der Middelware bei diesem Vorgang angeben, weil die App (in einem Fall) bereits einen eigenen „DbContext“ aufweist. Der folgende Befehl wird aus dem Stamm des Projekts ausgeführt:

dotnet ef database update --context NotFoundMiddlewareContext

Wenn Sie einen Fehler zu einem Datenbankanbieter erhalten, stellen Sie sicher, dass „services.AddNotFoundMiddlewareEntityFramework“ in der Startdatei aufgerufen wird.

Nächste Schritte

Das hier gezeigte Beispiel funktioniert gut und enthält eine In-Memory-Implementierung sowie eine Implementierung, die EF zum Speichern der Anzahl der Nicht gefunden-Anforderungen sowie der festen Pfade in einer Datenbank verwendet. Die Liste der 404-Antwortcodes sowie die Funktion korrigierter Pfade sollten so gesichert werden, dass nur Administratoren Zugriff darauf besitzen. Die aktuelle EF-Implementierung enthält darüber hinaus keine Programmlogik für Zwischenspeicherung. Dies führt dazu, dass bei jeder Anforderung der App eine Datenbankabfrage ausgeführt wird. Aus Leistungsgründen würde ich eine Funktion für Zwischenspeicherung mithilfe des CachedRepository-Musters hinzufügen.

Der aktualisierte Quellcode für dieses Beispiel ist unter bit.ly/1VUcY0J verfügbar.


Steve Smithist ein unabhängiger Trainer, Mentor und Berater sowie ein ASP.NET MVP. Er hat Dutzende von Artikeln für die offizielle ASP.NET Core-Dokumentation (docs.asp.net) verfasst und unterstützt Teams dabei, sich schnell mit ASP.NET Core vertraut zu machen. Nehmen Sie unter ardalis.com Kontakt mit ihm auf.

Unser Dank gilt dem folgenden technischen Experten bei Microsoft für die Durchsicht dieses Artikels: Chris Ross
Chris Ross ist ein Entwickler aus dem ASP.NET-Team bei Microsoft. Zurzeit interessiert er sich vorrangig für Middleware.