原文: 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
VSIHostEnvironment
- .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, 你並不需要馬上替換它們,因為他們現在的行為依然相同,但是在將來的版本中會被替換掉,因此如果可以的話,最好對其進行更新。在某些場景中,它還使你的應用之間共享代碼更加容易,因此值得研究一下。