Migrer des gestionnaires et des modules HTTP vers des middlewares ASP.NET Core

Cet article explique comment migrer les modules et gestionnaires HTTP ASP.NET existants de system.webserver vers le middleware ASP.NET Core.

Modules et gestionnaires revisités

Avant de passer au middleware ASP.NET Core, récapitulons d'abord le fonctionnement des modules et des gestionnaires HTTP :

Gestionnaire de modules

Les gestionnaires sont :

  • Les classes qui implémentent IHttpHandler

  • Utilisé pour gérer les requêtes avec un nom de fichier ou une extension donnés, comme .report

  • Configuré dans Web.config

Les modules sont :

  • Les classes qui implémentent IHttpModule

  • Invoqué pour chaque requête

  • Capable de court-circuiter (arrêter le traitement ultérieur d'une requête)

  • Capable d'ajouter à la réponse HTTP, ou de créer leur propre

  • Configuré dans Web.config

L'ordre dans lequel les modules traitent les requêtes entrantes est déterminé par :

  1. Une série d'événements déclenchés par ASP.NET, tels que BeginRequest et AuthenticateRequest. Pour obtenir une liste complète, consultez System.Web.HttpApplication. Chaque module peut créer un gestionnaire pour un ou plusieurs événements.

  2. Pour le même événement, l'ordre dans lequel ils sont configurés dans Web.config.

En plus des modules, vous pouvez ajouter des gestionnaires pour les événements du cycle de vie à votre fichier Global.asax.cs. Ces gestionnaires s'exécutent après les gestionnaires dans les modules configurés.

Des gestionnaires et modules au middleware

Les intergiciels sont plus simples que les modules et les gestionnaires HTTP :

  • Les modules, les gestionnaires, Global.asax.cs, Web.config (sauf pour la configuration IIS) et le cycle de vie de l'application ont disparu

  • Les rôles des modules et des gestionnaires ont été repris par le middleware

  • Les intergiciels sont configurés à l'aide de code plutôt que dans Web.config

  • La création de branches de pipeline vous permet d'envoyer des requêtes à un middleware spécifique, en fonction non seulement de l'URL, mais également des en-têtes de requête, des chaînes de requête, etc.
  • La création de branches de pipeline vous permet d'envoyer des requêtes à un middleware spécifique, en fonction non seulement de l'URL, mais également des en-têtes de requête, des chaînes de requête, etc.

Les intergiciels sont très similaires aux modules :

Le middleware et les modules sont traités dans un ordre différent :

  • L'ordre des intergiciels est basé sur l'ordre dans lequel ils sont insérés dans le pipeline de requêtes, tandis que l'ordre des modules est principalement basé sur les événements System.Web.HttpApplication.

  • L'ordre du middleware pour les réponses est l'inverse de celui des requêtes, tandis que l'ordre des modules est le même pour les requêtes et les réponses

  • Voir Créer un pipeline middleware avec IApplicationBuilder

L'intergiciel d'autorisation court-circuite une requête pour un utilisateur qui n'est pas autorisé. Une requête de page d'index est autorisée et traitée par MVC Middleware. Une requête de rapport de ventes est autorisée et traitée par un middleware de rapport personnalisé.

Notez comment dans l'image ci-dessus, le middleware d'authentification a court-circuité la requête.

Migrer le code du module vers le middleware

Un module HTTP existant ressemblera à ceci :

// ASP.NET 4 module

using System;
using System.Web;

namespace MyApp.Modules
{
    public class MyModule : IHttpModule
    {
        public void Dispose()
        {
        }

        public void Init(HttpApplication application)
        {
            application.BeginRequest += (new EventHandler(this.Application_BeginRequest));
            application.EndRequest += (new EventHandler(this.Application_EndRequest));
        }

        private void Application_BeginRequest(Object source, EventArgs e)
        {
            HttpContext context = ((HttpApplication)source).Context;

            // Do something with context near the beginning of request processing.
        }

        private void Application_EndRequest(Object source, EventArgs e)
        {
            HttpContext context = ((HttpApplication)source).Context;

            // Do something with context near the end of request processing.
        }
    }
}

Comme indiqué dans la page Middleware, un middleware ASP.NET Core est une classe qui expose une méthode Invoke prenant un HttpContext et retournant un Task. Votre nouveau middleware ressemblera à ceci :

// ASP.NET Core middleware

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using System.Threading.Tasks;

namespace MyApp.Middleware
{
    public class MyMiddleware
    {
        private readonly RequestDelegate _next;

        public MyMiddleware(RequestDelegate next)
        {
            _next = next;
        }

        public async Task Invoke(HttpContext context)
        {
            // Do something with context near the beginning of request processing.

            await _next.Invoke(context);

            // Clean up.
        }
    }

    public static class MyMiddlewareExtensions
    {
        public static IApplicationBuilder UseMyMiddleware(this IApplicationBuilder builder)
        {
            return builder.UseMiddleware<MyMiddleware>();
        }
    }
}

Le modèle de middleware précédent a été extrait de la section sur l'écriture de middleware.

La classe d'assistance MyMiddlewareExtensions facilite la configuration de votre middleware dans votre classe Startup. La méthode UseMyMiddleware ajoute votre classe middleware au pipeline de requêtes. Les services requis par le middleware sont injectés dans le constructeur du middleware.

Votre module peut mettre fin à une requête, par exemple si l'utilisateur n'est pas autorisé :

// ASP.NET 4 module that may terminate the request

private void Application_BeginRequest(Object source, EventArgs e)
{
    HttpContext context = ((HttpApplication)source).Context;

    // Do something with context near the beginning of request processing.

    if (TerminateRequest())
    {
        context.Response.End();
        return;
    }
}

Un middleware gère cela en n'appelant pas Invoke sur le middleware suivant dans le pipeline. Gardez à l'esprit que cela ne met pas complètement fin à la requête, car les middlewares précédents seront toujours invoqués lorsque la réponse reviendra dans le pipeline.

// ASP.NET Core middleware that may terminate the request

public async Task Invoke(HttpContext context)
{
    // Do something with context near the beginning of request processing.

    if (!TerminateRequest())
        await _next.Invoke(context);

    // Clean up.
}

Lorsque vous migrez les fonctionnalités de votre module vers votre nouveau middleware, vous pouvez constater que votre code ne se compile pas car la classe HttpContext a considérablement changé dans ASP.NET Core. Plus tard, vous verrez comment migrer vers le nouveau HttpContext ASP.NET Core.

Migration de l'insertion du module dans le pipeline de requêtes

Les modules HTTP sont généralement ajoutés au pipeline de requêtes à l'aide de Web.config :

<?xml version="1.0" encoding="utf-8"?>
<!--ASP.NET 4 web.config-->
<configuration>
  <system.webServer>
    <modules>
      <add name="MyModule" type="MyApp.Modules.MyModule"/>
    </modules>
  </system.webServer>
</configuration>

Convertissez-le en ajoutant votre nouveau middleware au pipeline de requêtes de votre classe Startup :

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
    loggerFactory.AddConsole(Configuration.GetSection("Logging"));
    loggerFactory.AddDebug();

    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
        app.UseBrowserLink();
    }
    else
    {
        app.UseExceptionHandler("/Home/Error");
    }

    app.UseMyMiddleware();

    app.UseMyMiddlewareWithParams();

    var myMiddlewareOptions = Configuration.GetSection("MyMiddlewareOptionsSection").Get<MyMiddlewareOptions>();
    var myMiddlewareOptions2 = Configuration.GetSection("MyMiddlewareOptionsSection2").Get<MyMiddlewareOptions>();
    app.UseMyMiddlewareWithParams(myMiddlewareOptions);
    app.UseMyMiddlewareWithParams(myMiddlewareOptions2);

    app.UseMyTerminatingMiddleware();

    // Create branch to the MyHandlerMiddleware. 
    // All requests ending in .report will follow this branch.
    app.MapWhen(
        context => context.Request.Path.ToString().EndsWith(".report"),
        appBranch => {
            // ... optionally add more middleware to this branch
            appBranch.UseMyHandler();
        });

    app.MapWhen(
        context => context.Request.Path.ToString().EndsWith(".context"),
        appBranch => {
            appBranch.UseHttpContextDemoMiddleware();
        });

    app.UseStaticFiles();

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

L'endroit exact dans le pipeline où vous insérez votre nouveau middleware dépend de l'événement qu'il a géré en tant que module (BeginRequest, EndRequest, etc.) et de son ordre dans votre liste de modules dans Web.config.

Comme indiqué précédemment, il n'y a pas de cycle de vie d'application dans ASP.NET Core et l'ordre dans lequel les réponses sont traitées par le middleware diffère de l'ordre utilisé par les modules. Cela pourrait rendre votre décision de commande plus difficile.

Si la commande devient un problème, vous pouvez diviser votre module en plusieurs composants middleware qui peuvent être commandés indépendamment.

Migrer le code du gestionnaire vers le middleware

Un gestionnaire HTTP ressemble à ceci :

// ASP.NET 4 handler

using System.Web;

namespace MyApp.HttpHandlers
{
    public class MyHandler : IHttpHandler
    {
        public bool IsReusable { get { return true; } }

        public void ProcessRequest(HttpContext context)
        {
            string response = GenerateResponse(context);

            context.Response.ContentType = GetContentType();
            context.Response.Output.Write(response);
        }

        // ...

        private string GenerateResponse(HttpContext context)
        {
            string title = context.Request.QueryString["title"];
            return string.Format("Title of the report: {0}", title);
        }

        private string GetContentType()
        {
            return "text/plain";
        }
    }
}

Dans votre projet ASP.NET Core, vous traduiriez ceci en un middleware similaire à ceci :

// ASP.NET Core middleware migrated from a handler

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using System.Threading.Tasks;

namespace MyApp.Middleware
{
    public class MyHandlerMiddleware
    {

        // Must have constructor with this signature, otherwise exception at run time
        public MyHandlerMiddleware(RequestDelegate next)
        {
            // This is an HTTP Handler, so no need to store next
        }

        public async Task Invoke(HttpContext context)
        {
            string response = GenerateResponse(context);

            context.Response.ContentType = GetContentType();
            await context.Response.WriteAsync(response);
        }

        // ...

        private string GenerateResponse(HttpContext context)
        {
            string title = context.Request.Query["title"];
            return string.Format("Title of the report: {0}", title);
        }

        private string GetContentType()
        {
            return "text/plain";
        }
    }

    public static class MyHandlerExtensions
    {
        public static IApplicationBuilder UseMyHandler(this IApplicationBuilder builder)
        {
            return builder.UseMiddleware<MyHandlerMiddleware>();
        }
    }
}

Ce middleware est très similaire au middleware correspondant aux modules. La seule vraie différence est qu'ici il n'y a pas d'appel à _next.Invoke(context). Cela a du sens, car le gestionnaire se trouve à la fin du pipeline de requêtes, il n'y aura donc pas de prochain middleware à invoquer.

Migration de l'insertion du gestionnaire dans le pipeline de requêtes

La configuration d'un gestionnaire HTTP se fait dans Web.config et ressemble à ceci :

<?xml version="1.0" encoding="utf-8"?>
<!--ASP.NET 4 web.config-->
<configuration>
  <system.webServer>
    <handlers>
      <add name="MyHandler" verb="*" path="*.report" type="MyApp.HttpHandlers.MyHandler" resourceType="Unspecified" preCondition="integratedMode"/>
    </handlers>
  </system.webServer>
</configuration>

Vous pouvez convertir cela en ajoutant votre nouveau middleware de gestionnaire au pipeline de requêtes de votre classe Startup, similaire au middleware converti à partir de modules. Le problème avec cette approche est qu'elle enverrait toutes les requêtes à votre nouveau middleware de gestionnaire. Cependant, vous souhaitez que seules les requêtes avec une extension donnée atteignent votre middleware. Cela vous donnerait la même fonctionnalité que vous aviez avec votre gestionnaire HTTP.

Une solution consiste à brancher le pipeline pour les demandes avec une extension donnée, en utilisant la méthode d'extension MapWhen. Vous faites cela dans la même méthode Configure où vous ajoutez l'autre middleware :

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
    loggerFactory.AddConsole(Configuration.GetSection("Logging"));
    loggerFactory.AddDebug();

    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
        app.UseBrowserLink();
    }
    else
    {
        app.UseExceptionHandler("/Home/Error");
    }

    app.UseMyMiddleware();

    app.UseMyMiddlewareWithParams();

    var myMiddlewareOptions = Configuration.GetSection("MyMiddlewareOptionsSection").Get<MyMiddlewareOptions>();
    var myMiddlewareOptions2 = Configuration.GetSection("MyMiddlewareOptionsSection2").Get<MyMiddlewareOptions>();
    app.UseMyMiddlewareWithParams(myMiddlewareOptions);
    app.UseMyMiddlewareWithParams(myMiddlewareOptions2);

    app.UseMyTerminatingMiddleware();

    // Create branch to the MyHandlerMiddleware. 
    // All requests ending in .report will follow this branch.
    app.MapWhen(
        context => context.Request.Path.ToString().EndsWith(".report"),
        appBranch => {
            // ... optionally add more middleware to this branch
            appBranch.UseMyHandler();
        });

    app.MapWhen(
        context => context.Request.Path.ToString().EndsWith(".context"),
        appBranch => {
            appBranch.UseHttpContextDemoMiddleware();
        });

    app.UseStaticFiles();

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

MapWhen prend ces paramètres :

  1. Un lambda qui prend le HttpContext et retourne true si la requête doit descendre dans la branche. Cela signifie que vous pouvez créer des branches de requêtes non seulement en fonction de leur extension, mais également des en-têtes de requête, des paramètres de chaîne de requête, etc.

  2. Un lambda qui prend un IApplicationBuilder et ajoute tout le middleware pour la branche. Cela signifie que vous pouvez ajouter un middleware supplémentaire à la branche devant votre middleware de gestionnaire.

Le middleware ajouté au pipeline avant que la branche ne soit appelée sur toutes les requêtes ; la succursale n'aura aucun impact sur eux.

Chargement des options du middleware à l'aide du modèle d'options

Certains modules et gestionnaires ont des options de configuration qui sont stockées dans Web.config. Cependant, dans ASP.NET Core, un nouveau modèle de configuration est utilisé à la place de Web.config.

Le nouveau système de configuration vous offre ces options pour résoudre ce problème :

  1. Créez une classe pour contenir vos options de middleware, par exemple :

    public class MyMiddlewareOptions
    {
        public string Param1 { get; set; }
        public string Param2 { get; set; }
    }
    
  2. Stocker les valeurs d'option

    Le système de configuration vous permet de stocker les valeurs des options où vous le souhaitez. Cependant, la plupart des sites utilisent appsettings.json, nous allons donc adopter cette approche :

    {
      "MyMiddlewareOptionsSection": {
        "Param1": "Param1Value",
        "Param2": "Param2Value"
      }
    }
    

    MyMiddlewareOptionsSection ici est un nom de section. Il ne doit pas nécessairement être le même que le nom de votre classe d'options.

  3. Associez les valeurs d'option à la classe d'options

    Le modèle d'options utilise le framework d'injection de dépendances d'ASP.NET Core pour associer le type d'options (tel que MyMiddlewareOptions) à un objet MyMiddlewareOptions qui possède les options réelles.

    Mettez à jour votre classe Startup :

    1. Si vous utilisez appsettings.json, ajoutez-le au générateur de configuration dans le constructeur Startup :

      public Startup(IHostingEnvironment env)
      {
          var builder = new ConfigurationBuilder()
              .SetBasePath(env.ContentRootPath)
              .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
              .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
              .AddEnvironmentVariables();
          Configuration = builder.Build();
      }
      
    2. Configurez le service d'options :

      public void ConfigureServices(IServiceCollection services)
      {
          // Setup options service
          services.AddOptions();
      
          // Load options from section "MyMiddlewareOptionsSection"
          services.Configure<MyMiddlewareOptions>(
              Configuration.GetSection("MyMiddlewareOptionsSection"));
      
          // Add framework services.
          services.AddMvc();
      }
      
    3. Associez vos options à votre classe d'options :

      public void ConfigureServices(IServiceCollection services)
      {
          // Setup options service
          services.AddOptions();
      
          // Load options from section "MyMiddlewareOptionsSection"
          services.Configure<MyMiddlewareOptions>(
              Configuration.GetSection("MyMiddlewareOptionsSection"));
      
          // Add framework services.
          services.AddMvc();
      }
      
  4. Injectez les options dans votre constructeur de middleware. Ceci est similaire à l'injection d'options dans un contrôleur.

    public class MyMiddlewareWithParams
    {
        private readonly RequestDelegate _next;
        private readonly MyMiddlewareOptions _myMiddlewareOptions;
    
        public MyMiddlewareWithParams(RequestDelegate next,
            IOptions<MyMiddlewareOptions> optionsAccessor)
        {
            _next = next;
            _myMiddlewareOptions = optionsAccessor.Value;
        }
    
        public async Task Invoke(HttpContext context)
        {
            // Do something with context near the beginning of request processing
            // using configuration in _myMiddlewareOptions
    
            await _next.Invoke(context);
    
            // Do something with context near the end of request processing
            // using configuration in _myMiddlewareOptions
        }
    }
    

    La méthode d'extension UseMiddleware qui ajoute votre middleware à IApplicationBuilder s'occupe de l'injection de dépendance.

    Cela ne se limite pas aux objets IOptions. Tout autre objet requis par votre middleware peut être injecté de cette manière.

Chargement des options middleware par injection directe

Le modèle d'options a l'avantage de créer un couplage lâche entre les valeurs des options et leurs consommateurs. Une fois que vous avez associé une classe d'options aux valeurs d'options réelles, toute autre classe peut accéder aux options via le framework d'injection de dépendances. Il n'est pas nécessaire de transmettre les valeurs des options.

Cela tombe en panne si vous souhaitez utiliser deux fois le même middleware, avec des options différentes. Par exemple un middleware d'autorisation utilisé dans différentes branches permettant différents rôles. Vous ne pouvez pas associer deux objets d'options différents à la même classe d'options.

La solution consiste à obtenir les objets d'options avec les valeurs d'options réelles de votre classe Startup et à les transmettre directement à chaque instance de votre middleware.

  1. Ajouter une deuxième clé à appsettings.json

    Pour ajouter un deuxième ensemble d'options au fichier appsettings.json, utilisez une nouvelle clé pour l'identifier de manière unique :

    {
      "MyMiddlewareOptionsSection2": {
        "Param1": "Param1Value2",
        "Param2": "Param2Value2"
      },
      "MyMiddlewareOptionsSection": {
        "Param1": "Param1Value",
        "Param2": "Param2Value"
      }
    }
    
  2. Récupérez les valeurs des options et transmettez-les au middleware. La méthode d'extension Use... (qui ajoute votre middleware au pipeline) est un endroit logique pour passer les valeurs d'option :

    public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
    {
        loggerFactory.AddConsole(Configuration.GetSection("Logging"));
        loggerFactory.AddDebug();
    
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
            app.UseBrowserLink();
        }
        else
        {
            app.UseExceptionHandler("/Home/Error");
        }
    
        app.UseMyMiddleware();
    
        app.UseMyMiddlewareWithParams();
    
        var myMiddlewareOptions = Configuration.GetSection("MyMiddlewareOptionsSection").Get<MyMiddlewareOptions>();
        var myMiddlewareOptions2 = Configuration.GetSection("MyMiddlewareOptionsSection2").Get<MyMiddlewareOptions>();
        app.UseMyMiddlewareWithParams(myMiddlewareOptions);
        app.UseMyMiddlewareWithParams(myMiddlewareOptions2);
    
        app.UseMyTerminatingMiddleware();
    
        // Create branch to the MyHandlerMiddleware. 
        // All requests ending in .report will follow this branch.
        app.MapWhen(
            context => context.Request.Path.ToString().EndsWith(".report"),
            appBranch => {
                // ... optionally add more middleware to this branch
                appBranch.UseMyHandler();
            });
    
        app.MapWhen(
            context => context.Request.Path.ToString().EndsWith(".context"),
            appBranch => {
                appBranch.UseHttpContextDemoMiddleware();
            });
    
        app.UseStaticFiles();
    
        app.UseMvc(routes =>
        {
            routes.MapRoute(
                name: "default",
                template: "{controller=Home}/{action=Index}/{id?}");
        });
    }
    
  3. Activer le middleware pour prendre un paramètre d'options. Fournissez une surcharge de la méthode d'extension Use... (qui prend le paramètre options et le passe à UseMiddleware). Lorsque UseMiddleware est appelé avec des paramètres, il transmet les paramètres à votre constructeur de middleware lorsqu'il instancie l'objet middleware.

    public static class MyMiddlewareWithParamsExtensions
    {
        public static IApplicationBuilder UseMyMiddlewareWithParams(
            this IApplicationBuilder builder)
        {
            return builder.UseMiddleware<MyMiddlewareWithParams>();
        }
    
        public static IApplicationBuilder UseMyMiddlewareWithParams(
            this IApplicationBuilder builder, MyMiddlewareOptions myMiddlewareOptions)
        {
            return builder.UseMiddleware<MyMiddlewareWithParams>(
                new OptionsWrapper<MyMiddlewareOptions>(myMiddlewareOptions));
        }
    }
    

    Notez comment cela enveloppe l'objet options dans un objet OptionsWrapper. Cela implémente IOptions, comme prévu par le constructeur du middleware.

Migration vers le nouveau HttpContext

Vous avez vu précédemment que la méthode Invoke de votre middleware prend un paramètre de type HttpContext :

public async Task Invoke(HttpContext context)

HttpContext a considérablement changé dans ASP.NET Core. Cette section montre comment traduire les propriétés les plus couramment utilisées de System.Web.HttpContext vers le nouveau Microsoft.AspNetCore.Http.HttpContext.

HttpContext

HttpContext.Items se traduit par :

IDictionary<object, object> items = httpContext.Items;

ID de requête unique (pas d'équivalent System.Web.HttpContext)

Vous donne un identifiant unique pour chaque requête. Très utile à inclure dans vos logs.

string requestId = httpContext.TraceIdentifier;

HttpContext.Request

HttpContext.Request.HttpMethod se traduit par :

string httpMethod = httpContext.Request.Method;

HttpContext.Request.QueryString se traduit par :

IQueryCollection queryParameters = httpContext.Request.Query;

// If no query parameter "key" used, values will have 0 items
// If single value used for a key (...?key=v1), values will have 1 item ("v1")
// If key has multiple values (...?key=v1&key=v2), values will have 2 items ("v1" and "v2")
IList<string> values = queryParameters["key"];

// If no query parameter "key" used, value will be ""
// If single value used for a key (...?key=v1), value will be "v1"
// If key has multiple values (...?key=v1&key=v2), value will be "v1,v2"
string value = queryParameters["key"].ToString();

HttpContext.Request.Url et HttpContext.Request.RawUrl se traduit par :

// using Microsoft.AspNetCore.Http.Extensions;
var url = httpContext.Request.GetDisplayUrl();

HttpContext.Request.IsSecureConnection se traduit par :

var isSecureConnection = httpContext.Request.IsHttps;

HttpContext.Request.UserHostAddress se traduit par :

var userHostAddress = httpContext.Connection.RemoteIpAddress?.ToString();

HttpContext.Request.Cookies se traduit par :

IRequestCookieCollection cookies = httpContext.Request.Cookies;
string unknownCookieValue = cookies["unknownCookie"]; // will be null (no exception)
string knownCookieValue = cookies["cookie1name"];     // will be actual value

HttpContext.Request.RequestContext.RouteData se traduit par :

var routeValue = httpContext.GetRouteValue("key");

HttpContext.Request.Headers se traduit par :

// using Microsoft.AspNetCore.Http.Headers;
// using Microsoft.Net.Http.Headers;

IHeaderDictionary headersDictionary = httpContext.Request.Headers;

// GetTypedHeaders extension method provides strongly typed access to many headers
var requestHeaders = httpContext.Request.GetTypedHeaders();
CacheControlHeaderValue cacheControlHeaderValue = requestHeaders.CacheControl;

// For unknown header, unknownheaderValues has zero items and unknownheaderValue is ""
IList<string> unknownheaderValues = headersDictionary["unknownheader"];
string unknownheaderValue = headersDictionary["unknownheader"].ToString();

// For known header, knownheaderValues has 1 item and knownheaderValue is the value
IList<string> knownheaderValues = headersDictionary[HeaderNames.AcceptLanguage];
string knownheaderValue = headersDictionary[HeaderNames.AcceptLanguage].ToString();

HttpContext.Request.UserAgent se traduit par :

string userAgent = headersDictionary[HeaderNames.UserAgent].ToString();

HttpContext.Request.UrlReferrer se traduit par :

string urlReferrer = headersDictionary[HeaderNames.Referer].ToString();

HttpContext.Request.ContentType se traduit par :

// using Microsoft.Net.Http.Headers;

MediaTypeHeaderValue mediaHeaderValue = requestHeaders.ContentType;
string contentType = mediaHeaderValue?.MediaType.ToString();   // ex. application/x-www-form-urlencoded
string contentMainType = mediaHeaderValue?.Type.ToString();    // ex. application
string contentSubType = mediaHeaderValue?.SubType.ToString();  // ex. x-www-form-urlencoded

System.Text.Encoding requestEncoding = mediaHeaderValue?.Encoding;

HttpContext.Request.Form se traduit par :

if (httpContext.Request.HasFormContentType)
{
    IFormCollection form;

    form = httpContext.Request.Form; // sync
    // Or
    form = await httpContext.Request.ReadFormAsync(); // async

    string firstName = form["firstname"];
    string lastName = form["lastname"];
}

Avertissement

Lire les valeurs de formulaire uniquement si le sous-type de contenu est x-www-form-urlencoded ou form-data.

HttpContext.Request.InputStream se traduit par :

string inputBody;
using (var reader = new System.IO.StreamReader(
    httpContext.Request.Body, System.Text.Encoding.UTF8))
{
    inputBody = reader.ReadToEnd();
}

Avertissement

Utilisez ce code uniquement dans un middleware de type handler, à la fin d'un pipeline.

Vous ne pouvez lire le corps brut comme indiqué ci-dessus qu'une seule fois par requête. L'intergiciel essayant de lire le corps après la première lecture lira un corps vide.

Cela ne s'applique pas à la lecture d'un formulaire comme indiqué précédemment, car cela se fait à partir d'un tampon.

HttpContext.Response

HttpContext.Response.Status et HttpContext.Response.StatusDescription se traduit par :

// using Microsoft.AspNetCore.Http;
httpContext.Response.StatusCode = StatusCodes.Status200OK;

HttpContext.Response.ContentEncoding et HttpContext.Response.ContentType se traduit par :

// using Microsoft.Net.Http.Headers;
var mediaType = new MediaTypeHeaderValue("application/json");
mediaType.Encoding = System.Text.Encoding.UTF8;
httpContext.Response.ContentType = mediaType.ToString();

HttpContext.Response.ContentType à lui seul se traduit également par :

httpContext.Response.ContentType = "text/html";

HttpContext.Response.Output se traduit par :

string responseContent = GetResponseContent();
await httpContext.Response.WriteAsync(responseContent);

HttpContext.Response.TransmitFile

La diffusion d'un fichier est abordée dans Fonctionnalités de demande dans ASP.NET Core.

HttpContext.Response.Headers

L'envoi d'en-têtes de réponse est compliqué par le fait que si vous les définissez après que quoi que ce soit ait été écrit dans le corps de la réponse, ils ne seront pas envoyés.

La solution consiste à définir une méthode de rappel qui sera appelée juste avant le début de l'écriture dans la réponse. Il est préférable de le faire au début de la méthode Invoke dans votre middleware. C'est cette méthode de rappel qui définit vos en-têtes de réponse.

Le code suivant définit une méthode de rappel appelée SetHeaders :

public async Task Invoke(HttpContext httpContext)
{
    // ...
    httpContext.Response.OnStarting(SetHeaders, state: httpContext);

La méthode de rappel SetHeaders ressemblerait à ceci :

// using Microsoft.AspNet.Http.Headers;
// using Microsoft.Net.Http.Headers;

private Task SetHeaders(object context)
{
    var httpContext = (HttpContext)context;

    // Set header with single value
    httpContext.Response.Headers["ResponseHeaderName"] = "headerValue";

    // Set header with multiple values
    string[] responseHeaderValues = new string[] { "headerValue1", "headerValue1" };
    httpContext.Response.Headers["ResponseHeaderName"] = responseHeaderValues;

    // Translating ASP.NET 4's HttpContext.Response.RedirectLocation  
    httpContext.Response.Headers[HeaderNames.Location] = "http://www.example.com";
    // Or
    httpContext.Response.Redirect("http://www.example.com");

    // GetTypedHeaders extension method provides strongly typed access to many headers
    var responseHeaders = httpContext.Response.GetTypedHeaders();

    // Translating ASP.NET 4's HttpContext.Response.CacheControl 
    responseHeaders.CacheControl = new CacheControlHeaderValue
    {
        MaxAge = new System.TimeSpan(365, 0, 0, 0)
        // Many more properties available 
    };

    // If you use .NET Framework 4.6+, Task.CompletedTask will be a bit faster
    return Task.FromResult(0);
}

HttpContext.Response.Cookies

Cookies voyagent vers le navigateur dans une réponse en-tête Set-Cookie. Par conséquent, l'envoi de cookies nécessite le même rappel que celui utilisé pour envoyer les en-têtes de réponse :

public async Task Invoke(HttpContext httpContext)
{
    // ...
    httpContext.Response.OnStarting(SetCookies, state: httpContext);
    httpContext.Response.OnStarting(SetHeaders, state: httpContext);

La méthode de rappel SetCookies ressemblerait à ceci :

private Task SetCookies(object context)
{
    var httpContext = (HttpContext)context;

    IResponseCookies responseCookies = httpContext.Response.Cookies;

    responseCookies.Append("cookie1name", "cookie1value");
    responseCookies.Append("cookie2name", "cookie2value",
        new CookieOptions { Expires = System.DateTime.Now.AddDays(5), HttpOnly = true });

    // If you use .NET Framework 4.6+, Task.CompletedTask will be a bit faster
    return Task.FromResult(0); 
}

Ressources supplémentaires