原文: 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 -
IHostingEnvironmentVSIHostEnvironment- .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.Hosting和Microsoft.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缺少了WebRootFileProvider和WebRootPath兩個屬性。
出於向后兼容的原因,這里需要一個單獨的抽象。但是,這種做法真正令人討厭的后果之一是無法編寫用於通用主機和ASP.NET Core的擴展方法。
在ASP.NET Core 3.0中,上述的兩個接口都已經被標記為廢棄了。你依然可以使用它們,但是在編譯的時候,你會得到一些警告。相對的,兩個新的接口被引入進來: IHostEnvironment和IWebHostEnvironment。雖然他們出現在不同的命名空間中,但是現在它們有了不同的名字,而且使用了繼承關系。
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)的抽象了。在幕后,IHostEnvironment和IWebHostEnvironment的實現是相同的 - 除了舊接口,他們還實現了新接口。
例如,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接口,但你需要訪問WebRootPath和WebRootFileProvider兩個屬性的時候,請使用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.Hosting和Microsoft.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, 你並不需要馬上替換它們,因為他們現在的行為依然相同,但是在將來的版本中會被替換掉,因此如果可以的話,最好對其進行更新。在某些場景中,它還使你的應用之間共享代碼更加容易,因此值得研究一下。
