System.Web 适配器

dotnet/systemweb-adapters 存储库中适配器的主要用例是帮助那些在想要迁移到 ASP.NET Core 时依赖类库中 System.Web 类型的开发人员。

适配器的一个重要功能是它们 允许同时从 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:此包用于支持库,提供可能依赖的 System.Web API,例如 HttpContext 以及其他相关 API。 此包面向 .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 应用程序提供服务,以便配置 System.Web API 的行为,并选择性地参与任何增量迁移的行为。 这通常不应从库引用,而是从应用程序本身引用。
  • Microsoft.AspNetCore.SystemWebAdapters.Abstractions:此包是一个支持包,它为 ASP.NET Core 和 ASP.NET Framework 应用程序(例如会话状态序列化)使用的服务提供抽象。

转换为 System.Web.HttpContext

若要在 HttpContext 的两种表示形式之间进行转换,可以执行以下作:

HttpContextHttpContext:

  • 隐式转换
  • HttpContext.AsSystemWeb()

HttpContextHttpContext

  • 隐式转换
  • HttpContext.AsAspNetCore()

这两种方法都将在请求期间使用缓存 HttpContext 表示形式。 这允许根据需要对HttpContext进行有针对性地重写。

示例:

ASP.NET 框架

请考虑一个控制器执行以下动作:

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

然后,它在一个单独的程序集中具有逻辑,四处传递该 HttpContext,直到最后,某个内部方法对它执行一些逻辑,例如:

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. 此设置禁用特定测试集合的并行执行,确保该集合中的测试一个接一个运行,而无需并发。