.NET平臺系列25:從 ASP.NET 遷移到 ASP.NET Core 的技術指南

張傳寧發表於2021-06-15
先決條件

.NET Core SDK 2.2 或更高版本

目標框架

  ASP.NET Core專案為開發人員提供了面向 .NET Core 和/或 .NET Framework 的靈活性。 若要確定最合適的目標框架,請參閱《從.NET Framework遷移到.NET Core/.NET5的技術指南》。

面向 .NET Framework 時,專案需要引用單個 NuGet 包。

得益於有 ASP.NET Core 元包,面向 .NET Core 時可以避免進行大量的顯式包引用。 在專案中安裝 Microsoft.AspNetCore.App 元包:

<ItemGroup>
   <PackageReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>

使用此元包時,應用不會部署元包中引用的任何包。 .NET Core 執行時儲存中包含這些資產,並已預編譯,旨在提升效能。 如需瞭解更多詳情,請參閱用於 ASP.NET Core 的 Microsoft.AspNetCore.App 元包

專案結構差異

ASP.NET Core 中簡化了 .csproj 檔案格式。 下面是一些顯著的更改:

  • 無需顯式新增,即可將檔案視作專案的一部分。 服務於大型團隊時,這可減少出現 XML 合併衝突的風險。

  • 沒有對其他專案的基於 GUID 的引用,這可以提高檔案的可讀性。

  • 無需在 Visual Studio 中解除安裝檔案即可對它進行編輯:

Global.asax 檔案替換

ASP.NET Core 引入了啟動應用的新機制。 ASP.NET 應用程式的入口點是 Global.asax 檔案。 路由配置及篩選器和區域註冊等任務在 Global.asax 檔案中進行處理。

public class MvcApplication : System.Web.HttpApplication
{
    protected void Application_Start()
    {
        AreaRegistration.RegisterAllAreas();
        FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
        RouteConfig.RegisterRoutes(RouteTable.Routes);
        BundleConfig.RegisterBundles(BundleTable.Bundles);
    }
}

此方法會將應用程式和應用程式要部署到的伺服器耦合在一起,並且它們的耦合方式會干擾實現。 為了將它們分離,引入了 OWIN 來提供一種更為簡便的同時使用多個框架的方法。 OWIN 提供了一個管道,可以只新增所需的模組。 託管環境使用 Startup 函式配置服務和應用的請求管道。 Startup 在應用程式中註冊一組中介軟體。 對於每個請求,應用程式都使用現有處理程式集的連結列表的頭指標呼叫各個中介軟體元件。 每個中介軟體元件可以向請求處理管道新增一個或多個處理程式。 為此,需要返回對成為列表新頭的處理程式的引用。 每個處理程式負責記住並呼叫列表中的下一個處理程式。 使用 ASP.NET Core 時,應用程式的入口點是 Startup,不再具有 Global.asax 的依賴關係。 結合使用 OWIN 和 .NET Framework 時,使用的管道應如下所示:using Owinusing System.Web.Http;

namespace WebApi
{   
//注意:預設情況下,所有請求都通過這個OWIN管道。或者,您可以通過新增appSetting來關閉此功能owin:AutomaticAppStartup with 值“false”。 //關閉此選項後,通過在RouteTable.routes上使用MapOwinPath或MapOwinRoute擴充套件在global.asax檔案中新增路由,您仍然可以讓OWIN應用監聽特定路由 public class Startup { // 在啟動時呼叫一次以配置應用程式。 public void Configuration(IAppBuilder builder) { HttpConfiguration config = new HttpConfiguration(); config.Routes.MapHttpRoute("Default", "{controller}/{customerID}", new { controller = "Customer", customerID = RouteParameter.Optional }); config.Formatters.XmlFormatter.UseXmlSerializer = true; config.Formatters.Remove(config.Formatters.JsonFormatter); // config.Formatters.JsonFormatter.UseDataContractJsonSerializer = true; builder.UseWebApi(config); } } }

這會配置預設路由,預設為 XmlSerialization 而不是 Json。 根據需要向此管道新增其他中介軟體(載入服務、配置設定、靜態檔案等)。

ASP.NET Core 使用相似的方法,但是不依賴 OWIN 處理條目。 而是通過 Program.cs Main 方法(類似於控制檯應用程式)來完成,並且 Startup 會通過該處進行載入。

using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;

namespace WebApplication2
{
    public class Program
    {
        public static void Main(string[] args)
        {
            CreateWebHostBuilder(args).Build().Run();
        }

        public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
            WebHost.CreateDefaultBuilder(args)
                .UseStartup<Startup>();
    }
}

Startup 必須包含 Configure 方法。 在 Configure 中,向管道新增必要的中介軟體。 在下面的示例(來自預設網站模板)中,擴充套件方法為管道配置以下支援:

  • 錯誤頁
  • HTTP 嚴格傳輸安全
  • 從 HTTP 重定向到 HTTPS
  • ASP.NET Core MVC
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseHsts();
    }

    app.UseHttpsRedirection();
    app.UseMvc();
}

現在主機和應用程式已分離,這樣將來就可以靈活地遷移到其他平臺。

若要獲取 ASP.NET Core Startup 和中介軟體的更深入的參考資訊,請參閱 ASP.NET Core 中的 Startup

儲存配置

  ASP.NET支援儲存設定。 這些設定可用於支援應用程式已部署到的環境(以此用途為例)。 常見做法是將所有的自定義鍵值對儲存在 Web.config 檔案的 <appSettings> 部分中:

<appSettings>
  <add key="UserName" value="User" />
  <add key="Password" value="Password" />
</appSettings>

應用程式使用 System.Configuration 名稱空間中的 ConfigurationManager.AppSettings 集合讀取這些設定:

string userName = System.Web.Configuration.ConfigurationManager.AppSettings["UserName"];
string password = System.Web.Configuration.ConfigurationManager.AppSettings["Password"];

ASP.NET Core 可以將應用程式的配置資料儲存在任何檔案中,並可在啟動中介軟體的過程中載入它們。 專案模板中使用的預設檔案是 appsettings.json:

{
  "Logging": {
    "IncludeScopes": false,
    "LogLevel": {
      "Default": "Debug",
      "System": "Information",
      "Microsoft": "Information"
    }
  },
  "AppConfiguration": {
    "UserName": "UserName",
    "Password": "Password"
  }
}

將此檔案載入到應用程式內的 IConfiguration 的例項的過程在 Startup.cs 中完成:

public Startup(IConfiguration configuration)
{
    Configuration = configuration;
}

public IConfiguration Configuration { get; }

應用讀取 Configuration 來獲得這些設定:

string userName = Configuration.GetSection("AppConfiguration")["UserName"];
string password = Configuration.GetSection("AppConfiguration")["Password"];

此方法有擴充套件項,它們可使此過程更加可靠,例如使用依存關係注入 (DI) 來載入使用這些值的服務。 DI 方法提供了一組強型別的配置物件。

// 假設AppConfiguration是表示AppConfiguration節點的強型別版本的類
services.Configure<AppConfiguration>(Configuration.GetSection("AppConfiguration"));

若要獲取 ASP.NET Core 配置的更深入的參考資訊,請參閱 ASP.NET Core 中的配置

本機依存關係注入

  生成大型可縮放應用程式時,一個重要的目標是將元件和服務鬆散耦合。 依賴項注入不僅是可實現此目標的常用技術,還是 ASP.NET Core 的本機元件。

ASP.NET應用中,開發人員依賴第三方庫實現依存關係注入。 其中的一個庫是 Microsoft 模式和做法提供的 Unity

實現打包 UnityContainer 的 IDependencyResolver 是使用 Unity 設定依存關係注入的一個示例:

 1 using Microsoft.Practices.Unity;
 2 using System;
 3 using System.Collections.Generic;
 4 using System.Web.Http.Dependencies;
 5 
 6 public class UnityResolver : IDependencyResolver
 7 {
 8     protected IUnityContainer container;
 9 
10     public UnityResolver(IUnityContainer container)
11     {
12         if (container == null)
13         {
14             throw new ArgumentNullException("container");
15         }
16         this.container = container;
17     }
18 
19     public object GetService(Type serviceType)
20     {
21         try
22         {
23             return container.Resolve(serviceType);
24         }
25         catch (ResolutionFailedException)
26         {
27             return null;
28         }
29     }
30 
31     public IEnumerable<object> GetServices(Type serviceType)
32     {
33         try
34         {
35             return container.ResolveAll(serviceType);
36         }
37         catch (ResolutionFailedException)
38         {
39             return new List<object>();
40         }
41     }
42 
43     public IDependencyScope BeginScope()
44     {
45         var child = container.CreateChildContainer();
46         return new UnityResolver(child);
47     }
48 
49     public void Dispose()
50     {
51         Dispose(true);
52     }
53 
54     protected virtual void Dispose(bool disposing)
55     {
56         container.Dispose();
57     }
58 }

建立 UnityContainer 的例項,註冊服務,然後將 HttpConfiguration 的依賴關係解析程式設定為容器的 UnityResolver 新例項:

public static void Register(HttpConfiguration config)
{
    var container = new UnityContainer();
    container.RegisterType<IProductRepository, ProductRepository>(new HierarchicalLifetimeManager());
    config.DependencyResolver = new UnityResolver(container);

    // Other Web API configuration not shown.
}

在必要時注入 IProductRepository

public class ProductsController : ApiController
{
    private IProductRepository _repository;

    public ProductsController(IProductRepository repository)  
    {
        _repository = repository;
    }

    // Other controller methods not shown.
}

由於依存關係注入是 ASP.NET Core的組成部分,因此可以在 Startup.cs 的 ConfigureServices 方法中新增你的服務:

public void ConfigureServices(IServiceCollection services)
{
    // 註冊應用服務
    services.AddTransient<IProductRepository, ProductRepository>();
}

可在任意位置注入儲存庫,Unity 亦是如此。有關依賴關係注入的詳細資訊,請參閱依賴關係注入

提供靜態檔案

  Web 開發的一個重要環節是提供客戶端靜態資源的功能。 HTML、CSS、Javascript 和影像是最常見的靜態檔案示例。 這些檔案需要儲存在應用(或 CDN)的釋出位置中,並且需要引用它們,以便請求可以載入這些檔案。 在 ASP.NET Core 中,此過程發生了變化。

在 ASP.NET 中,靜態檔案儲存在各種目錄中,並在檢視中進行引用。在 ASP.NET Core 中,靜態檔案儲存在“Web 根”(<內容根>/wwwroot)中,除非另有配置。 通過從 Startup.Configure 呼叫 UseStaticFiles 擴充套件方法將這些檔案載入到請求管道中:

public void Configure(IApplicationBuilder app)
{
    app.UseStaticFiles();
}

如果面向 .NET Framework,請安裝 NuGet 包 Microsoft.AspNetCore.StaticFiles。

例如,可以通過瀏覽器從類似 http://<app>/images/<imageFileName> 的位置訪問 wwwroot/images 資料夾中的影像資產。

若要獲取在 ASP.NET Core 中提供靜態檔案的更深入的參考資訊,請參閱靜態檔案

多值 cookie

  ASP.NET Core 不支援多值 cookie。 為每個值建立一個 cookie。

ASP.NET Core 中不壓縮身份驗證 cookie

  出於安全原因,ASP.NET Core 中不壓縮身份驗證 cookie。 使用身份驗證 cookie 時,開發人員應將宣告資訊數量減少到所需的量。

部分應用遷移

  部分應用遷移的一種方法是建立 IIS 子應用程式,只將特定的路由從 ASP.NET 4.x 遷移到 ASP.NET Core,同時保留應用的 URL 結構。 例如,applicationHost.config 檔案中應用的 URL 結構:

<sites>
    <site name="Default Web Site" id="1" serverAutoStart="true">
        <application path="/">
            <virtualDirectory path="/" physicalPath="D:\sites\MainSite\" />
        </application>
        <application path="/api" applicationPool="DefaultAppPool">
            <virtualDirectory path="/" physicalPath="D:\sites\netcoreapi" />
        </application>
        <bindings>
            <binding protocol="http" bindingInformation="*:80:" />
            <binding protocol="https" bindingInformation="*:443:" sslFlags="0" />
        </bindings>
    </site>
    ...
</sites>

目錄結構:

.
├── MainSite
│   ├── ...
│   └── Web.config
└── NetCoreApi
    ├── ...
    └── web.config
[BIND] 和輸入格式化程式

  ASP.NET 早期版本使用 [Bind] 屬性防止“過多釋出”攻擊。 在 ASP.NET Core 中,輸入格式化程式的工作方式有所不同。 與輸入格式化程式一起用於分析 JSON 或 XML 時,[Bind] 屬性不再專用於防止過多釋出。 資料來源是使用 x-www-form-urlencoded 內容型別釋出的表單資料時,這些屬性會影響模型繫結。

對於將 JSON 資訊釋出到控制器並使用 JSON 輸入格式化程式分析資料的應用程式,我們建議將 [Bind] 屬性替換為與 [Bind] 屬性定義的屬性相匹配的檢視模型。

其他資源

其他專案遷移具體操作步驟,請參考以下部落格:


參考文獻:

  • https://docs.microsoft.com/zh-cn/aspnet/core/migration/proper-to-2x/?view=aspnetcore-5.0

 

相關文章