Заметка
Доступ к этой странице требует авторизации. Вы можете попробовать войти в систему или изменить каталог.
Доступ к этой странице требует авторизации. Вы можете попробовать сменить директорию.
В этой статье показано, как перенести существующие модули HTTP ASP.NET из system.webserver в промежуточное программное обеспечение ASP.NET Core.
Модули, которые были изменены
Прежде чем перейти к промежуточному программному обеспечению ASP.NET Core, давайте сначала вспомним, как работают модули HTTP.
Модули:
Классы, реализующие IHttpModule
Вызывается для каждого запроса
Возможность короткого замыкания (остановить дальнейшую обработку запроса)
Способность добавлять в ответ HTTP или создавать собственные ответы
Настроено в Web.config
Порядок обработки входящих запросов модулей определяется следующим образом:
Серия событий, инициируемых ASP.NET, таких как BeginRequest и AuthenticateRequest. Полный список см. в разделе System.Web.HttpApplication. Каждый модуль может создать обработчик для одного или нескольких событий.
Для того же события порядок их настройки в Web.config.
В дополнение к модулям можно добавить обработчики для событий жизненного цикла в Global.asax.cs файл. Эти обработчики выполняются после обработчиков в настроенных модулях.
От модулей до ПО промежуточного слоя
Промежуточное ПО проще, чем HTTP-модули:
Модули,
Global.asax.csWeb.config (за исключением конфигурации IIS) и жизненный цикл приложения исчезлиРоли модулей выполняются промежуточным слоям.
Промежуточное ПО конфигурируется с помощью кода, а не в Web.config
- Ветвление конвейера позволяет отправлять запросы в определенное ПО промежуточного слоя, основанное не только на URL-адресе, но и на заголовках запросов, строках запроса и т. д.
- Ветвление конвейера позволяет отправлять запросы в определенное ПО промежуточного слоя, основанное не только на URL-адресе, но и на заголовках запросов, строках запроса и т. д.
Промежуточное программное обеспечение очень похоже на модули:
Вызывается по умолчанию для каждого запроса
Возможность прерывания процесса обработки запроса, не передавая запрос следующему промежуточному обработчику
Возможность создания собственного HTTP-ответа
Промежуточное программное обеспечение и модули обрабатываются в другом порядке:
Порядок промежуточного слоя основан на порядке вставки в канал обработки запросов, а порядок модулей в основном же основан на событиях System.Web.HttpApplication.
Порядок промежуточного слоя для ответов является обратным по отношению к порядку для запросов, а порядок модулей совпадает с порядком модулей для запросов и ответов.
См. статью "Создание конвейера ПО промежуточного слоя" с помощью IApplicationBuilder
Обратите внимание, как на приведенном выше изображении посредник аутентификации перехватил запрос.
Перенос кода модуля в программное обеспечение промежуточного уровня
Существующий модуль HTTP будет выглядеть примерно так:
// 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.
}
}
}
Как показано на странице Middleware, ASP.NET Core middleware — это класс, который раскрывает Invoke метод, принимающий HttpContext и возвращающий Task. Новый ПО промежуточного слоя будет выглядеть следующим образом:
// 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>();
}
}
}
Предыдущий шаблон ПО промежуточного слоя был взят из раздела по написанию ПО промежуточного слоя.
Вспомогательный класс MyMiddlewareExtensions упрощает настройку промежуточного слоя в классе Startup. Метод UseMyMiddleware добавляет класс middleware в конвейер обработки запросов. Службы, необходимые промежуточному программному обеспечению, внедряются в его конструктор.
Модуль может завершить запрос, например, если пользователь не авторизован:
// 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;
}
}
Промежуточное ПО обрабатывает это, не вызывая Invoke следующее промежуточное ПО в конвейере. Помните, что это не полностью завершает запрос, так как предыдущие промежуточные программы все еще будут вызываться, когда ответ возвращается назад через конвейер.
// 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.
}
При переносе функциональных возможностей модуля в новое ПО промежуточного слоя вы можете обнаружить, что код не компилируется, так как HttpContext класс значительно изменился в ASP.NET Core. Ознакомьтесь с разделом "Миграция из ASP.NET Framework HttpContext в ASP.NET Core" , чтобы узнать, как перейти на новый ASP.NET Core HttpContext.
Перенос вставки модуля в конвейер запросов
Модули HTTP обычно добавляются в конвейер запросов с помощью 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>
Преобразуйте это, добавив новое ПО промежуточного слоя в конвейер запросов в классе 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?}");
});
}
Точное место в конвейере, в котором вы вставляете новое ПО промежуточного слоя, зависит от события, которое он обрабатывает как модуль (BeginRequest, EndRequestи т. д.) и его порядок в списке модулей в Web.config.
Как было сказано ранее, в ASP.NET Core отсутствует жизненный цикл приложения, и порядок обработки ответов промежуточным программным обеспечением отличается от порядка, который используется модулями. Это может сделать решение по заказу более сложным.
Если сортировка становится проблемой, модуль можно разделить на несколько компонентов программного обеспечения промежуточного слоя, которые можно упорядочить независимо.
Загрузка параметров промежуточного программного обеспечения с помощью паттерна настроек
Некоторые модули имеют параметры конфигурации, которые хранятся в Web.config. Однако в ASP.NET Core новая модель конфигурации используется вместо Web.config.
Новая система конфигурации предоставляет следующие варианты решения:
Непосредственно вставьте параметры в ПО промежуточного слоя, как показано в следующем разделе.
Используйте шаблон параметров:
Создайте класс для хранения параметров промежуточного слоя, в качестве примера:
public class MyMiddlewareOptions { public string Param1 { get; set; } public string Param2 { get; set; } }Хранение значений параметров
Система конфигурации позволяет хранить значения параметров в любом месте. Однако большинство сайтов используют
appsettings.json, поэтому мы применим этот подход:{ "MyMiddlewareOptionsSection": { "Param1": "Param1Value", "Param2": "Param2Value" } }MyMiddlewareOptionsSection здесь — имя раздела. Он не должен совпадать с именем класса параметров.
Связывание значений параметров с классом параметров
Шаблон параметров использует платформу внедрения зависимостей ASP.NET Core для связывания типа параметров (например
MyMiddlewareOptions, сMyMiddlewareOptionsобъектом с фактическими параметрами).StartupОбновите класс:Если вы используете
appsettings.json, добавьте его в построитель конфигураций в конструкторе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(); }Настройте службу параметров:
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(); }Привяжите ваши опции к вашему классу опций:
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(); }
Вставьте параметры в конструктор промежуточного программного обеспечения. Это аналогично внедрению параметров в контроллер.
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 } }Метод расширения UseMiddleware, который добавляет ваше промежуточное программное обеспечение, обеспечивает внедрение зависимостей.
Это не ограничивается объектами
IOptions. Любой другой объект, который требуется для вашего промежуточного слоя, можно внедрить таким образом.
Загрузка опций промежуточного программного обеспечения с помощью прямого внедрения
Шаблон параметров имеет преимущество, которое создает свободное связывание между значениями параметров и их потребителями. После того как вы связали класс параметров с фактическими значениями параметров, любой другой класс может получить доступ к параметрам через платформу внедрения зависимостей. Нет необходимости передавать значения параметров.
Это перестает работать, если вы хотите использовать одно и то же промежуточное программное обеспечение дважды с разными параметрами. Например, промежуточное программное обеспечение для авторизации, используемое в разных филиалах, поддерживающее различные роли. Невозможно связать два разных объекта параметров с одним классом параметров.
Решение состоит в том, чтобы получить объекты параметров с фактическими значениями параметров в своем Startup классе и передать их непосредственно каждому экземпляру посредством промежуточного ПО.
Добавьте второй ключ в
appsettings.jsonЧтобы добавить второй набор параметров в
appsettings.jsonфайл, используйте новый ключ для уникальной идентификации:{ "MyMiddlewareOptionsSection2": { "Param1": "Param1Value2", "Param2": "Param2Value2" }, "MyMiddlewareOptionsSection": { "Param1": "Param1Value", "Param2": "Param2Value" } }Извлеките значения опций и передайте их в промежуточное ПО. Метод расширения
Use...(который добавляет 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?}"); }); }Разрешите промежуточному слою принимать параметр опций. Укажите перегрузку
Use...метода расширения (который принимает параметр параметров и передает егоUseMiddlewareв ). При вызовеUseMiddlewareс параметрами, он передает параметры конструктору промежуточного программного обеспечения при создании экземпляра данного объекта.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)); } }Обратите внимание, как это оборачивает объект options в объект
OptionsWrapper. Это реализуетIOptions, как ожидалось конструктором промежуточного слоя.
Постепенная миграция IHttpModule
Иногда преобразование модулей в промежуточное ПО не всегда возможно. Для поддержки сценариев миграции, в которых необходимы модули и не могут быть перемещены в ПО промежуточного слоя, адаптеры System.Web поддерживают добавление их в ASP.NET Core.
Пример IHttpModule
Чтобы поддерживать модули, экземпляр HttpApplication должен быть доступен. Если нестандартный HttpApplication не используется, по умолчанию будет использован стандартный для добавления модулей. События, объявленные в пользовательском приложении (включая Application_Start) будут зарегистрированы и выполняются соответствующим образом.
using System.Web;
using Microsoft.AspNetCore.OutputCaching;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddSystemWebAdapters()
.AddHttpApplication<MyApp>(options =>
{
// Size of pool for HttpApplication instances. Should be what the expected concurrent requests will be
options.PoolSize = 10;
// Register a module (optionally) by name
options.RegisterModule<MyModule>("MyModule");
});
// Only available in .NET 7+
builder.Services.AddOutputCache(options =>
{
options.AddHttpApplicationBasePolicy(_ => new[] { "browser" });
});
builder.Services.AddAuthentication();
builder.Services.AddAuthorization();
var app = builder.Build();
app.UseAuthentication();
app.UseAuthenticationEvents();
app.UseAuthorization();
app.UseAuthorizationEvents();
app.UseSystemWebAdapters();
app.UseOutputCache();
app.MapGet("/", () => "Hello World!")
.CacheOutput();
app.Run();
class MyApp : HttpApplication
{
protected void Application_Start()
{
}
public override string? GetVaryByCustomString(System.Web.HttpContext context, string custom)
{
// Any custom vary-by string needed
return base.GetVaryByCustomString(context, custom);
}
}
class MyModule : IHttpModule
{
public void Init(HttpApplication application)
{
application.BeginRequest += (s, e) =>
{
// Handle events at the beginning of a request
};
application.AuthorizeRequest += (s, e) =>
{
// Handle events that need to be authorized
};
}
public void Dispose()
{
}
}
Миграция Global.asax
Эту инфраструктуру можно использовать для переноса использования Global.asax при необходимости. Источник из Global.asax — это настраиваемый HttpApplication, и файл можно включить в приложение ASP.NET Core. Так как он называется Global, для его регистрации можно использовать следующий код:
builder.Services.AddSystemWebAdapters()
.AddHttpApplication<Global>();
До тех пор, пока логика в нем доступна в ASP.NET Core, этот подход можно использовать для постепенной миграции зависимостей от Global.asax на ASP.NET Core.
События проверки подлинности и авторизации
Чтобы события проверки подлинности и авторизации выполнялись в нужное время, следует использовать следующий шаблон:
app.UseAuthentication();
app.UseAuthenticationEvents();
app.UseAuthorization();
app.UseAuthorizationEvents();
Если это не сделать, события всё равно будут происходить. Тем не менее, это произойдет во время вызова .UseSystemWebAdapters().
Пул модулей HTTP
Так как модули и приложения в ASP.NET Framework были назначены запросу, для каждого запроса требуется новый экземпляр. Тем не менее, поскольку их создание может быть дорогостоящим, они объединяются с использованием ObjectPool<T>. Чтобы настроить фактическое время существования HttpApplication экземпляров, можно использовать настраиваемый пул:
builder.Services.TryAddSingleton<ObjectPool<HttpApplication>>(sp =>
{
// Recommended to use the in-built policy as that will ensure everything is initialized correctly and is not intended to be replaced
var policy = sp.GetRequiredService<IPooledObjectPolicy<HttpApplication>>();
// Can use any provider needed
var provider = new DefaultObjectPoolProvider();
// Use the provider to create a custom pool that will then be used for the application.
return provider.Create(policy);
});