Адаптеры System.Web

Основной вариант использования адаптеров в репозитории dotnet/systemweb-adapters — помочь разработчикам, которые зависят от типов в своих библиотеках классов и хотят перейти на ASP.NET Core.

Важной особенностью адаптеров является то, что они позволяют использовать библиотеку как в проектах ASP.NET Framework, так и в ASP.NET Core. Обновление нескольких приложений ASP.NET Framework до ASP.NET Core часто включает промежуточные состояния, в которых не все приложения были полностью обновлены. Используя System.Web адаптеры, библиотеку можно использовать как из вызывающих ASP.NET Core абонентов, так и из ASP.NET Framework абонентов, которые не были обновлены.

Рассмотрим пример использования адаптеров, переходящих с .NET Framework на ASP.NET Core.

Пакеты

  • Microsoft.AspNetCore.SystemWebAdapters: этот пакет используется в вспомогательных библиотеках и предоставляет API System.Web, от которых вы могли бы зависеть, например HttpContext и от других. Этот пакет предназначен для .NET Standard 2.0, .NET Framework 4.5+ и .NET 5+.
  • Microsoft.AspNetCore.SystemWebAdapters.FrameworkServices. Этот пакет предназначен только для .NET Framework и предназначен для предоставления служб приложениям ASP.NET Framework, которые могут потребоваться для добавочной миграции. Обычно не предполагается, что на это будут ссылаться из библиотек, а из самих приложений.
  • Microsoft.AspNetCore.SystemWebAdapters.CoreServices. Этот пакет предназначен только для .NET 6+ и предназначен для предоставления служб для приложений ASP.NET Core для настройки поведения API System.Web, а также для включения любых дополнительных особенностей в процессе поэтапной миграции. Обычно не предполагается, что на это будут ссылаться из библиотек, а из самих приложений.
  • Microsoft.AspNetCore.SystemWebAdapters.Abstractions. Этот пакет является вспомогательным пакетом, предоставляющим абстракции для служб, используемых как приложением ASP.NET Core, так и ASP.NET Framework, например сериализация состояния сеанса.

Преобразование в System.Web.HttpContext

Для преобразования между двумя представлениями HttpContext можно выполнить следующее:

Для HttpContext до HttpContext.

  • Неявное приведение
  • HttpContext.AsSystemWeb()

Для HttpContext от HttpContext

  • Неявное приведение
  • HttpContext.AsAspNetCore()

Оба этих метода будут использовать кэшированное HttpContext представление в течение длительности запроса. Это позволяет выполнять целевые перезаписи HttpContext по мере необходимости.

Пример

платформа ASP.NET

Рассмотрим контроллер, который выполняет такие действия, как:

public class SomeController : Controller
{
  public ActionResult Index()
  {
    SomeOtherClass.SomeMethod(HttpContext.Current);
  }
}

что затем приводит к тому, что логика в отдельной сборке передается между компонентами до тех пор, пока, наконец, какой-то внутренний метод не выполнит на этом некоторую логику, такую как:

public class Class2
{
  public bool PerformSomeCheck(HttpContext context)
  {
    return context.Request.Headers["SomeHeader"] == "ExpectedValue";
  }
}

ASP.NET Core

Чтобы запустить приведенную выше логику в ASP.NET Core, разработчику потребуется добавить Microsoft.AspNetCore.SystemWebAdapters пакет, который позволит проектам работать на обеих платформах.

Библиотеки должны быть обновлены для понимания адаптеров, но это будет так же просто, как добавление пакета и перекомпиляции. Если это единственная зависимость системы System.Web.dll, то библиотеки смогут нацелиться на .NET Standard 2.0 , чтобы упростить процесс сборки при миграции.

Теперь контроллер в ASP.NET Core будет выглядеть следующим образом:

public class SomeController : Controller
{
  [Route("/")]
  public IActionResult Index()
  {
    SomeOtherClass.SomeMethod(HttpContext);
  }
}

Обратите внимание, что так как существует свойство HttpContext, они могут это передать, но в целом это выглядит так же. С помощью неявных преобразований HttpContext можно преобразовать в адаптер, который затем можно передать через уровни, используя код таким же образом.

Модульное тестирование

При модульном тестировании кода, использующего адаптеры System.Web, следует учитывать некоторые особые аспекты.

В большинстве случаев нет необходимости настраивать дополнительные компоненты для выполнения тестов. Но если тестируемый компонент используется HttpRuntime, может потребоваться запустить SystemWebAdapters службу, как показано в следующем примере:

namespace TestProject1;

/// <summary>
/// This demonstrates an xUnit feature that ensures all tests
/// in classes marked with this collection are run sequentially.
/// </summary>
[CollectionDefinition(nameof(SystemWebAdaptersHostedTests),
    DisableParallelization = true)]
public class SystemWebAdaptersHostedTests
{
}
[Collection(nameof(SystemWebAdaptersHostedTests))]
public class RuntimeTests
{
    /// <summary>
    /// This method starts up a host in the background that
    /// makes it possible to initialize <see cref="HttpRuntime"/>
    /// and <see cref="HostingEnvironment"/> with values needed 
    /// for testing with the <paramref name="configure"/> option.
    /// </summary>
    /// <param name="configure">
    /// Configuration for the hosting and runtime options.
    /// </param>
    public static async Task<IDisposable> EnableRuntimeAsync(
        Action<SystemWebAdaptersOptions>? configure = null,
        CancellationToken token = default)
        => await new HostBuilder()
           .ConfigureWebHost(webBuilder =>
           {
               webBuilder
                   .UseTestServer()
                   .ConfigureServices(services =>
                   {
                       services.AddSystemWebAdapters();
                       if (configure is not null)
                       {
                           services.AddOptions
                               <SystemWebAdaptersOptions>()
                               .Configure(configure);
                       }
                   })
                   .Configure(app =>
                   {
                       // No need to configure pipeline for tests
                   });
           })
           .StartAsync(token);
    [Fact]
    public async Task RuntimeEnabled()
    {
        using (await EnableRuntimeAsync(options =>
            options.AppDomainAppPath = "path"))
        {
            Assert.True(HostingEnvironment.IsHosted);
            Assert.Equal("path", HttpRuntime.AppDomainAppPath);
        }
        Assert.False(HostingEnvironment.IsHosted);
    }
}

Тесты должны выполняться в последовательности, а не параллельно. В предыдущем примере показано, как этого добиться, установив параметр XUnit DisableParallelization на значение true. Этот параметр отключает параллельное выполнение для определенной коллекции тестов, обеспечивая выполнение тестов в этой коллекции после другой без параллелизма.