【導讀】
我們知道在。NET Framework中可以嵌入執行Web APi,那麼在。NET Core(。NET 6+稱之為。NET)中如何內嵌執行Web Api呢,在實際專案中這種場景非常常見,那麼我們本節以。NET 6。0作為演示示例一起來瞅瞅。
內嵌執行.NET Core Web APi
接下來我們透過控制檯作為主程式來啟動Web APi,首先我們建立名為EmbedWebApi的控制檯程式,然後建立Embed。WebApi類庫執行Web APi,我們在此Web APi中建立如下介面,並實現相關方法來執行Web APi
public
class
InitTest
:
IInitTest
{
public
void
Init
(
)
{
var
builder = WebApplication。CreateBuilder();
builder。Services。AddControllers();
var
app = builder。Build();
app。UseRouting();
app。UseEndpoints(endpoints =>
{
endpoints。MapDefaultControllerRoute();
});
app。Run();
}
}
public
interface
IInitTest
{
void
Init
(
)
;
}
透過寫介面並在對應方法中執行Web APi主要是達到在控制中呼叫該介面進行模擬實現,這裡需要注意一點的是,因為我們建立的Web APi是類庫,要想使用Web裡面的Api等等,直接在專案檔案中新增如下一行以表明我們要引用框架,這樣一來框架裡面所包含的APi等等版本都一致統一,
而不是透過NuGet一一下載,這是錯誤的做法
“Microsoft。AspNetCore。App” /> 接下來我們在該類庫中按照規範建立Controllers資料夾,並建立測試控制器,如下 using Microsoft。AspNetCore。Mvc; namespace Embed。WebApi。Controllers { [ ApiController ] [ Route( “api/[controller]/[action]” ) ] public class TestController : ControllerBase { [ HttpGet ] public IActionResult Test ( ) { return Ok( “Hello World” ); } } } 最後我們在控制檯程式中註冊上述介面並呼叫初始化方法,如下: internal class Program { static void Main ( string [] args ) { var services = new ServiceCollection(); services。AddTransient var serviceProvider = services。BuildServiceProvider(); var initTest = serviceProvider。GetRequiredService initTest。Init(); Console。Read(); } } 蕪湖,我們透過Postman模擬呼叫測試介面,結果驚呆了,404了~~~ 當我們將類庫中的控制器移動到控制檯中,此時請求測試介面併成功返回對世界的問候,這是什麼原因呢? 不難猜測可知,WebAPi控制器的啟用以作為入口的主程式集進行查詢啟用。 雖然這樣看似解決了問題,假設呼叫嵌入執行的主程式是底層已經封裝好的基礎設施,那麼豈不是遭到了程式碼入侵,所以我們就想在執行的Web APi類庫裡面去啟用,此時我們想到將類庫作為Web APi應用程式一部分應用手動載入並激活,在初始化方法裡面修改為如下即可請求測試介面成功 public class InitTest : IInitTest { private static readonly string AssemblyName = typeof (InitTest)。Assembly。GetName()。Name; public void Init () { var builder = WebApplication。CreateBuilder(); builder。Services。AddControllers() 。AddApplicationPart(Assembly。Load( new AssemblyName(AssemblyName))); var app = builder。Build(); app。UseRouting(); app。UseEndpoints(endpoints => { endpoints。MapDefaultControllerRoute(); }); app。Run(); } } 上述直接在執行Web APi類庫中新增控制器啟用,這種場景完全限定於底層主入口已封裝好,所以只能採用這種方式,若是主入口我們自己可控制,當然還有另外一種方式,來,我們瞧瞧擷取的關鍵性原始碼 /// /// Populates the given using the list of /// s configured on the /// 。 /// /// The type of the feature。 /// The feature instance to populate。 public void PopulateFeature { if (feature == ) { throw new ArgumentException( nameof (feature)); } foreach ( var provider in FeatureProviders。OfType { provider。PopulateFeature(ApplicationParts, feature); } } internal void PopulateDefaultParts ( string entryAssemblyName ) { var assemblies = GetApplicationPartAssemblies(entryAssemblyName); var seenAssemblies = new HashSet foreach ( var assembly in assemblies) { if (!seenAssemblies。Add(assembly)) { // “assemblies” may contain duplicate values, but we want unique ApplicationPart instances。 // Note that we prefer using a HashSet over Distinct since the latter isn‘t // guaranteed to preserve the original ordering。 continue ; } var partFactory = ApplicationPartFactory。GetApplicationPartFactory(assembly); foreach ( var applicationPart in partFactory。GetApplicationParts(assembly)) { ApplicationParts。Add(applicationPart); } } } private static IEnumerable GetApplicationPartAssemblies ( string entryAssemblyName ) { var entryAssembly = Assembly。Load( new AssemblyName(entryAssemblyName)); // Use ApplicationPartAttribute to get the closure of direct or transitive dependencies // that reference MVC。 var assembliesFromAttributes = entryAssembly。GetCustomAttributes 。Select(name => Assembly。Load(name。AssemblyName)) 。OrderBy(assembly => assembly。FullName, StringComparer。Ordinal) 。SelectMany(GetAssemblyClosure); // The SDK will not include the entry assembly as an application part。 We’ll explicitly list it // and have it appear before all other assemblies \ ApplicationParts。 return GetAssemblyClosure(entryAssembly) 。Concat(assembliesFromAttributes); } private static IEnumerable GetAssemblyClosure ( Assembly assembly ) { yield return assembly; var relatedAssemblies = RelatedAssemblyAttribute。GetRelatedAssemblies(assembly, throwOnError: false ) 。OrderBy(assembly => assembly。FullName, StringComparer。Ordinal); foreach ( var relatedAssembly in relatedAssemblies) { yield return relatedAssembly; } } 從上述原始碼可知,透過主入口程式集還會載入引用的程式集去查詢並激活相關特性(比如控制器),當然前提是實現ApplicationPartAttribute特性,此特性必須在主入口程式集裡定義,定義在程式集上 所以我們只需一行程式碼即可搞定,我們在控制檯主入口名稱空間頂部新增特性,引入Web APi類庫程式集作為應用程式的一部分,如下: [ assembly: ApplicationPart( “Embed。WebApi” ) ] 那麼接下來問題又來了,要是需要執行多個Web APi我們又當如何呢? 按照上述方式一一新增未嘗不可,我們也可以透過MSBuild任務來進行構建將相關特性自動新增到主入口程式集描述資訊裡面 去,例如: “Microsoft。AspNetCore。Mvc。ApplicationParts。ApplicationPartAttribute” > <_Parameter1>Embed。WebApi 有的童鞋就問了,這不寫死了麼,那還不如透過新增特性的方式去處理,請注意這裡只是使用示例 實際情況下,我們可將多個Web APi放在同一解決方案下,然後在此解決方案下建立可構建任務的。targets檔案,並在主專案檔案裡引入,將程式集名稱作為變數引入,剩下事情自行統一處理,若不清楚怎麼搞,就在程式碼中使用特性方式也未嘗不可,例如如下: “Microsoft。AspNetCore。Mvc。ApplicationParts。ApplicationPartAttribute” > <_Parameter1>$(AssemblyName) 本節我們重點討論如何內嵌執行。NET Core Web APi類庫,同時介紹了兩種啟用比如控制器特性方案, 希望對您有所幫助,謝謝,我們下節再會