1. 程式人生 > >IHostingEnvironment VS IHostEnvironment - .NET Core 3.0中的廢棄型別

IHostingEnvironment VS IHostEnvironment - .NET Core 3.0中的廢棄型別

原文: https://andrewlock.net/ihostingenvironment-vs-ihost-environment-obsolete-types-in-net-core-3/
作者: Andrew Lock
譯者: Lamond Lu

本篇是如何升級到ASP.NET Core 3.0系列文章的第二篇。

  • Part 1 - 將.NET Standard 2.0類庫轉換為.NET Core 3.0類庫
  • Part 2 - IHostingEnvironment VS IHostEnvironment - .NET Core 3.0中的廢棄型別(本篇)
  • Part 3 - 避免在ASP.NET Core 3.0啟動時注入服務
  • Part 4 - 將終端中介軟體轉換為ASP.NET Core 3.0中的節點路由
  • Part 5 - 將整合測試的轉換為NET Core 3.0

在本篇部落格中,我將描述與之前版本相比,ASP.NET Core 3.0中已經被標記為廢棄的型別。我將解釋一下為什麼這些型別被廢棄了,它們的替換型別是什麼,以及你應該什麼時候使用它們。


ASP.NET Core與通用主機(Generic Host)合併

在ASP.NET Core 2.1中引入了新的通用主機(Generic Host), 它是藉助Microsoft.Extension.*程式集來進行程式配置,依賴注入,以及日誌記錄來構建非HTTP應用的一種方式。 雖然這是一個相當不錯的點子,但是引入主機抽象在基礎上與ASP.NET Core使用的HTTP主機不相容。這導致了多種名稱空間的衝突與不相容,所以在ASP.NET Core 2.x版本中,我一直儘量不使用通用主機。

在ASP.NET Core 3.0中,開發人員作出了巨大的努力,將Web主機與通用主機相容起來。ASP.NET Core的Web主機現在可以作為IHostedService執行在通用主機中,重複抽象的問題(ASP.NET Core中使用一套抽象,通用主機使用另一套抽象)得到了根本解決。

當然,這還不是全部。當你從ASP.NET Core 2.x升級到3.0, ASP.NET Core 3.0並不強迫你立即使用新的通用主機。如果你願意,你可以繼續使用舊的WebHostBuilder,而不使用新的HostBuilder。雖然在ASP.NET Core 3.0的官方文件中一直暗示這是必須的,但是在當前的階段,這是一個可選配置,如果你需要,可以繼續使用Web主機,而不使用通用主機。

PS: 不過我還是建議你將可能將HostBuilder作為你未來的升級計劃。我但是在未來的某個時間點WebHostBuilder將被移除,即使現在它還沒有被標記為[Obsolete]

作為重構的通用主機的一部分,一些在之前版本中重複的型別被標記為廢棄了,一些新的型別被引入了。在這些型別中,最好的例子就是IHostingEnvironment

IHostingEnvironment VS IHostEnvironment VS IWebHostEnviornment

IHostingEnvironment是.NET Core 2.x中最讓人討厭的一個介面,因為它存在於兩個名稱空間中, Microsoft.AspNetCore.HostingMicrosoft.Extensions.Hosting.這兩個介面有少許不同,且不相容。

namespace Microsoft.AspNetCore.Hosting
{
    public interface IHostingEnvironment
    {
        string EnvironmentName { get; set; }
        string ApplicationName { get; set; }
        string WebRootPath { get; set; }
        IFileProvider WebRootFileProvider { get; set; }
        string ContentRootPath { get; set; }
        IFileProvider ContentRootFileProvider { get; set; }
    }
}

namespace Microsoft.Extensions.Hosting
{
    public interface IHostingEnvironment
    {
        string EnvironmentName { get; set; }
        string ApplicationName { get; set; }
        string ContentRootPath { get; set; }
        IFileProvider ContentRootFileProvider { get; set; }
    }
}

之所以有兩個同名介面是有歷史原因的。AspNetCore版本的介面已經存在了很長時間了,在ASP.NET Core 2.1版本中,通用主機引入了Extensions版本。Extensions版本沒有提供用於服務靜態檔案的wwwroot目錄的概念(因為它承載的是非HTTP服務)。所以你可能已經注意到Extensions缺少了WebRootFileProviderWebRootPath兩個屬性。

出於向後相容的原因,這裡需要一個單獨的抽象。但是,這種做法真正令人討厭的後果之一是無法編寫用於通用主機和ASP.NET Core的擴充套件方法。

在ASP.NET Core 3.0中,上述的兩個介面都已經被標記為廢棄了。你依然可以使用它們,但是在編譯的時候,你會得到一些警告。相對的,兩個新的介面被引入進來: IHostEnvironmentIWebHostEnvironment。雖然他們出現在不同的名稱空間中,但是現在它們有了不同的名字,而且使用了繼承關係。

namespace Microsoft.Extensions.Hosting
{
    public interface IHostEnvironment
    {
        string EnvironmentName { get; set; }
        string ApplicationName { get; set; }
        string ContentRootPath { get; set; }
        IFileProvider ContentRootFileProvider { get; set; }
    }
}

namespace Microsoft.AspNetCore.Hosting
{
    public interface IWebHostEnvironment : IHostEnvironment
    {
        string WebRootPath { get; set; }
        IFileProvider WebRootFileProvider { get; set; }
    }
}

這個層次關係更容易理解了,避免了重複,並且意味著接收通用主機版本宿主環境抽象(IHostEnvironment)的方法現在也可以接收web版本(IWebHostEnvironment)的抽象了。在幕後,IHostEnvironmentIWebHostEnvironment的實現是相同的 - 除了舊介面,他們還實現了新介面。

例如,ASP.NET Core的實現類如下:

namespace Microsoft.AspNetCore.Hosting
{
    internal class HostingEnvironment : IHostingEnvironment, 
            Extensions.Hosting.IHostingEnvironment, 
            IWebHostEnvironment
    {
        public string EnvironmentName { get; set; } 
                = Extensions.Hosting.Environments.Production;
        public string ApplicationName { get; set; }
        public string WebRootPath { get; set; }
        public IFileProvider WebRootFileProvider { get; set; }
        public string ContentRootPath { get; set; }
        public IFileProvider ContentRootFileProvider { get; set; }
    }
}

那麼你到底應該使用哪個介面呢?最簡單的答案是"儘可能使用IHostEnvironment介面"。

但是詳細來說,情況有很多。。。

如果你正在編寫的ASP.NET Core 3.0的應用

儘可能是使用IHostEnviornment介面,但你需要訪問WebRootPathWebRootFileProvider兩個屬性的時候,請使用IWebHostEnvironment介面。

如果你正在編寫一個在通用主機和.NET Core 3.0專案中使用的類庫

使用IHostEnvironment介面。你的類庫依然可以在ASP.NET Core 3.0應用中可用。

如果你正在編寫一個在ASP.NET Core 3.0應用中使用的類庫

和之前一樣,儘量使用IHostEnvironment介面,因為你的類庫可能不僅使用在ASP.NET Core應用中,還有可能使用在其他通用主機應用中。然而,如果你需要訪問IWebHostEnvironment介面中的額外屬性,那麼你可能不得不更新你的類庫,讓它面向netcoreapp3.0,而不是netstandard2.0, 並且新增<FreameworkReference>元素配置。

如果你正在編寫一個在ASP.NET Core 2.x和3.0中使用的類庫

這種場景比較難處理,基本上你有兩種可選的方案:

  • 你可以繼續使用Microsoft.AspNetCore版本的IHostingEnvironment。它在2.x和3.0應用中都可以正常工作,你只需要在後續版本中停止使用即可。
  • 使用#ifdef條件編譯指令,針對ASP.NET Core 3.0使用IHostEnvironment介面,針對ASP.NET Core 2.x使用IHostingEnviornment介面。

IApplicationLifetime VS IHostApplicationLifetime

IHostingEnvironment介面相似,IApplicationLifetime介面也有名稱空間的衝突問題。和之前的例子相同,這兩個介面分別存在於Microsoft.Extensions.HostingMicrosoft.AspNetCore.Hosting中。但是在這個例子中,這兩個介面是完全一致的。

// 與Microsoft.AspNetCore.Hosting中的定義完全一致
namespace Microsoft.Extensions.Hosting
{
    public interface IApplicationLifetime
    {
        CancellationToken ApplicationStarted { get; }
        CancellationToken ApplicationStopped { get; }
        CancellationToken ApplicationStopping { get; }
        void StopApplication();
    }
}

如你所料,這種重複是向後相容的徵兆。在.NET Core 3.0中新的介面IHostApplicationLifetime被引入,該介面僅在Microsoft.Extensions.Hosting名稱空間中定義,但是在通用主機和ASP.NET Core應用中都可以使用。

namespace Microsoft.Extensions.Hosting
{
    public interface IHostApplicationLifetime
    {
        CancellationToken ApplicationStarted { get; }
        CancellationToken ApplicationStopping { get; }
        CancellationToken ApplicationStopped { get; }
        void StopApplication();
    }
}

同樣的,這個介面和之前版本是完全一致的。ApplicationLifetime型別在通用主機專案的啟動和關閉中扮演了非常重要的角色。非常有趣的是,在Microsoft.AspNetCore.Hosting中沒有一個真正等價的型別,Extensions版本的介面處理了兩種不同的實現。AspNetCore名稱空間中唯一的實現是一個簡單的封裝類,型別將實現委託給了一個作為通用主機部分被新增的ApplicationLifetime物件中。

namespace Microsoft.AspNetCore.Hosting
{
    internal class GenericWebHostApplicationLifetime : IApplicationLifetime
    {
        private readonly IHostApplicationLifetime _applicationLifetime;
        public GenericWebHostApplicationLifetime(
            IHostApplicationLifetime applicationLifetime)
        {
            _applicationLifetime = applicationLifetime;
        }

        public CancellationToken ApplicationStarted => 
            _applicationLifetime.ApplicationStarted;
        public CancellationToken ApplicationStopping =>
            _applicationLifetime.ApplicationStopping;
        public CancellationToken ApplicationStopped => 
            _applicationLifetime.ApplicationStopped;
        public void StopApplication() =>
            _applicationLifetime.StopApplication();
    }
}

幸運的是,選擇使用哪一個介面,比選擇託管環境(Hosting Environment)要簡單的多。

如果你正在編寫一個.NET Core 3.0或者ASP.NET Core 3.0應用或者類庫

使用IHostApplicationLifetime介面。你只需要引用Microsoft.Extensions.Hosting.Abstractions, 即可以在所有應用中使用。

如果你在編寫一個被ASP.NET Core 2.x和3.0應用共同使用的類庫

現在,你可能又會陷入困境:

  • 你可以繼續使用Microsoft.Extensions版本的IApplicationLifetime。它在2.x和3.0應用中都可以正常使用,但是在未來的版本中,你將不得不停止使用它
  • 使用#ifdef條件編譯指令,針對ASP.NET Core 3.0使用IHostApplicationLifetime介面,針對ASP.NET Core 2.x使用IApplicationLifetime介面。

幸運的是,IApplicationLifetime介面通常使用的比IHostingEnvironment介面少的多,所以你可能不會在此遇到過多的困難。

IWebHost VS IHost

這裡有一件事情可能讓你驚訝,IWebHost介面沒有被更新,它沒有繼承ASP.NET Core 3.0中的IHost。相似的,IWebHostBuilder也沒有繼承自IHostBuilder。它們依然是完全獨立的介面, 一個只工作在ASP.NET Core中,一個只工作在通用主機中。

幸運的是,這也沒有關係。現在ASP.NET Core 3.0已經被重構使用通用主機的抽象介面, 你可以編寫使用通用主機IHostBuilder抽象的方法,並在ASP.NET Core和通用主機應用中共享它們。如果你需要進行ASP.NET Core的特定操作,你可以依然使用IWebHostBuilder介面。

例如,你可以編寫如下的擴充套件方法,一個使用IHostBuilder, 一個使用IWebHostBuilder:

public static class ExampleExtensions
{
    public static IHostBuilder DoSomethingGeneric(this IHostBuilder builder)
    {
        // 新增通用主機配置
        return builder;
    }

    public static IWebHostBuilder DoSomethingWeb(this IWebHostBuilder builder)
    {
        // 新增Web託管配置
        return builder;
    }
}

其中一個方法在通用主機上進行某些配置(列入,使用依賴注入註冊某些服務),在另外一個方法中對IWebHostBuilder進行某種配置,例如你可能會為Kestrel伺服器設定一些預設值。

如果你在建立了一個全新的ASP.NET Core 3.0應用,你的Program.cs檔案看起來應該是如下程式碼:

public class Program
{
    public static void Main(string[] args) => CreateHostBuilder(args).Build().Run();

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureWebHostDefaults(webBuilder =>
            {
                webBuilder
                    .UseStartup<Startup>();
            });
}

你可以新增針對兩個擴充套件方法的呼叫。一個在通用IHostBuilder上呼叫,另一個在ConfigWebHostDefaults()方法中,針對IWebHostBuilder呼叫

public class Program
{
    public static void Main(string[] args) => CreateHostBuilder(args).Build().Run();

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .DoSomethingGeneric() // IHostBuilder擴充套件方法
            .ConfigureWebHostDefaults(webBuilder =>
            {
                webBuilder
                    .DoSomethingWeb() // IWebHostBuilder擴充套件方法
                    .UseStartup<Startup>();
            });
}

在ASP.NET Core 3.0中,你可以對兩種構建器型別進行呼叫,這意味著,你現在可以僅依賴通用主機的抽象,就可以在ASP.NET Core應用中複用它們。然後,你可以將ASP.NET Core的特性行為放在頂層,而不必像2.x中一樣重複方法。

總結

在本文中,我們討論了ASP.NET Core 3.0中一些被標記為廢棄的型別,它們被移動到哪裡去了,以及這麼做的原因。如果你正在將一個應用升級到ASP.NET Core 3.0, 你並不需要馬上替換它們,因為他們現在的行為依然相同,但是在將來的版本中會被替換掉,因此如果可以的話,最好對其進行更新。在某些場景中,它還使你的應用之間共享程式碼更加容易,因此值得研究一下