介紹
?? ?.NET 6.0 已經發布,ASP.NET Core 6.0 也已發布。其中有不少變化讓很多人感到困惑。例如,“誰動了我的奶酪”,它在哪里Startup.cs?在這篇文章中,我將深入研究這個問題,看看它移動到了哪里以及其他變化。
?? ?ASP.NET Core 的中間件并沒有發生根本性的變化,但部分項目結構以及注冊依賴項的位置發生了變化。為了更好地理解它,最好從 .NET Core 3.1 項目模板開始,然后手動升級它,看看它與新模板相比如何。
升級舊式控制臺項目
?? ?首先,讓我們創建一個新的控制臺項目。我將其命名為OldToNew。我選擇了 .NET Core 3.1 目標,并將其升級到 .NET 6.0 以查看差異。如果您已經使用 .NET 一段時間,您會在文件中認出這個項目結構Program.cs。?
using System;
namespace OldToNew
{
? ? internal class Program
? ? {
? ? ? ? static void Main(string[] args)
? ? ? ? {
? ? ? ? ? ? Console.WriteLine("Hello World!");
? ? ? ? }
? ? }
}
????????在 .NET 6.0 中,這些變化旨在簡化和消除應用程序中的冗余。他們引入的首批功能之一是所謂的Filescoped Namespaces。傳統命名空間如下所示:
namespace OldToNew
{
? ? // code goes here.
}
????????如果您已經使用過 .NET 一段時間,那么您可能從未在文件中放置過多個命名空間。您可以刪除花括號,只需添加分號,將整個文件標記為使用一個命名空間。
namespace OldToNew;
// code goes here.
?
using System;
namespace OldToNew;
internal class Program
{
? ? static void Main(string[] args)
? ? {
? ? ? ? Console.WriteLine("Hello World!");
? ? }
}
?
????????Visual Studio 將會向我抱怨,因為這是一個 .NET Core 3.1 項目,所以在我們走得太遠之前,我們需要編輯 .NET Core 3.1 .csproj 文件并將其轉換為 .NET 6.0 應用程序。
<!-- .NET Core 3.1 -->
<Project Sdk="Microsoft.NET.Sdk">
? <PropertyGroup>
? ? <OutputType>Exe</OutputType>
? ? <TargetFramework>netcoreapp3.1</TargetFramework>
? </PropertyGroup>
</Project>
<!-- .NET 6.0 -->
<Project Sdk="Microsoft.NET.Sdk">
? <PropertyGroup>
? ? <OutputType>Exe</OutputType>
? ? <TargetFramework>net6.0</TargetFramework>
? </PropertyGroup>
</Project>
????????一旦我們做出這一改變,Visual Studio 就會對文件范圍的命名空間感到滿意。
????????我們要做的下一個更改是刪除該using System;行。為此,我們需要再次編輯項目文件并啟用Implicit Usings。
<!-- .NET 6.0 -->
<Project Sdk="Microsoft.NET.Sdk">
? <PropertyGroup>
? ? <OutputType>Exe</OutputType>
? ? <TargetFramework>net6.0</TargetFramework>
? ? <ImplicitUsings>enable</ImplicitUsings>
? </PropertyGroup>
</Project>
????????如果我們啟用隱式 using 語句,我們通常使用的大多數常用 using 語句將默認包含在 SDK 中,您不再需要將它們包含在文件中。我們可以刪除該using System;
行,因為編譯器會自動為我們添加 using 語句。
namespace OldToNew;
internal class Program
{
? ? static void Main(string[] args)
? ? {
? ? ? ? Console.WriteLine("Hello World!");
? ? }
}?
????????他們引入的下一個功能是所謂的“頂級語句”。 其目的是刪除每個控制臺應用程序或 ASP.NET Core 應用程序中存在的“垃圾”。
????????使用頂級語句,我們可以刪除static void Main(string[] args)方法和花括號以及命名空間和class Program聲明。
Console.WriteLine("Hello World!");
????????一旦刪除所有這些,您就會看到我們剩下的唯一代碼就是我們的Console.WriteLine()
方法!
將控制臺應用更改為 Web 應用 (ASP.NET Core)
?? ?目前,這只是一個簡單的控制臺應用程序,但我想將其轉換為 ASP.NET Core 應用程序。在執行此操作之前,讓我們先查看解決方案資源管理器中的依賴項和框架節點。
????????您可以在上方看到,Frameworks這Microsoft.NETCore.App是包含創建控制臺應用程序所需的所有包的 SDK。讓我們將 .csproj 文件中的 SDK 類型更改為 .Web 類型項目,看看會發生什么Microsoft.NET.Sdk:Microsoft.NET.Sdk.Web。
<!-- .NET 6.0 -->
<Project Sdk="Microsoft.NET.Sdk.Web">
? <PropertyGroup>
? ? <OutputType>Exe</OutputType>
? ? <TargetFramework>net6.0</TargetFramework>
? </PropertyGroup>
</Project>
注意現在依賴下發生的情況:
????????該框架現在包括Microsoft.AspNetCore.App將引入創建 ASP.NET Core 應用程序所需的所有包。它還將修改全局 using 語句以包含 ASP.NET 特定的 using 語句。
????????現在讓我們刪除該Console.WriteLine("Hello World!");行并添加 ASP.NET Core 特定的代碼。
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.Run();?
????????我們現在正在構建一個 Web 應用程序。WebApplication對象從哪里來?它屬于Microsoft.AspNetCore.Builder命名空間。
????????我們可以運行這個應用程序,但是由于沒有端點,所以會有點無聊。讓我們添加一個端點來完成這個任務:
app.MapGet("/", () => {
? ? return "Hello World!";
}); ??
????????我們剛剛在 ASP.NET Core 6.0 中創建了一個最小 API Web 應用程序。讓我們運行它并看看會發生什么。?
????????當我們啟動默認路徑時,它會返回,hello world
因此我們有一個功能齊全的 Web 應用程序。最少的 API 是快速構建 Web API 項目的好方法。
Startup.cs 怎么樣?
?? ?ASP.NET Core 6.0 中的中間件流程與完整 Web API MVC 項目的流程類似,并且共享許多相同的實現。在以前的 ASP.NET Core 項目中,您將獲得一個Startup.cs包含兩個方法的類ConfigureServices()和Configure()。
?? ?在 ConfigureServices()中,您注冊了依賴項注入服務。在 Configure() 中,您概述了中間件管道順序和結構。
?? ?在我們的新項目中,這是什么樣子的?您將依賴注入放在哪里?答案是在第一行和第二行之間,注冊中間件在第二行和第三行之間,如下所示。
var builder = WebApplication.CreateBuilder(args);
// REGISTER SERVICES HERE
var app = builder.Build();
// REGISTER MIDDLEWARE HERE
app.Run();
例如,如果我想添加身份驗證,我會像這樣注冊:?
var builder = WebApplication.CreateBuilder(args);
// REGISTER SERVICES HERE
builder.Services.AddAuthentication(...) ...
builder.Services.AddAuthorization();
var app = builder.Build();
// REGISTER MIDDLEWARE HERE
app.UseAuthentication();
app.UseAuthorization();
app.MapGet("/", () => {
? ? return "Hello World!";
}); ?
app.Run();
????????中間件管道中的順序很重要,因此我在中間件MapGet()上方添加了UseAuthentication()和UseAuthorization()。但是,為了使其生效,您需要使用屬性注釋要保護的端點。這將需要使用語句[Authorize],必須引用:Microsoft.AspNetCore.Authorization?。
using Microsoft.AspNetCore.Authorization;
...
app.MapGet("/",[Authorize]() => {
? ? return "Hello World!";
});
Program.cs
到目前為止,我們的文件的完整代碼如下所示:?
var builder = WebApplication.CreateBuilder(args);
// REGISTER SERVICES HERE
builder.Services.AddAuthentication();
builder.Services.AddAuthorization();
var app = builder.Build();
// REGISTER MIDDLEWARE HERE
app.UseAuthentication();
app.UseAuthorization();
app.MapGet("/",[Authorize] () => {
? ? return "Hello World!";
}); ?
app.Run();?
????????如果我們現在運行它,我們將會得到一個異常,因為我們從未指定身份驗證方案。
????????讓我們修復它。我們可以選擇多種不同類型的身份驗證方案,但在 WebAPI 中,我們通常使用諸如 bearer token 或 JWT token 之類的東西來保護應用程序。
????????讓我們繼續將 JWT 承載者添加到最小 API。我們需要Microsoft.AspNetCore.Authentication.JwtBearer通過 NuGet 導入包,然后添加以下代碼:
using Microsoft.AspNetCore.Authentication.JwtBearer;
...
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer();
所以完整的塊應該是這樣的:
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
var builder = WebApplication.CreateBuilder(args);
// REGISTER SERVICES HERE
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer();
builder.Services.AddAuthorization();
var app = builder.Build();
// REGISTER MIDDLEWARE HERE
app.UseAuthentication();
app.UseAuthorization();
app.MapGet("/", [Authorize] () => {
? ? return "Hello World!";
});
app.Run();
????????JWT Bearer 已添加到應用程序中,讓我們運行它并查看會發生什么。現在我們得到了我們正在尋找的狀態,401 unauthorized因為我們沒有為其提供任何類型的令牌。還需要其他步驟來設置獲取令牌的方法并確保我們只允許接受有效的令牌,但這超出了本文的范圍。本文的重點是演示在哪里注冊依賴項和中間件以及如何使用它們。
????????我們應該做的最后一項修改是將 [Authorize] 屬性更改為使用“流暢的語法”,如下所示:
//FROM THIS:
app.MapGet("/", [Authorize] () => {
? ? return "Hello World!";
});
//TO THIS:
app.MapGet("/", () => {
? return "Hello World!";
}).RequireAuthorization();
它們都做同樣的事情,但是在最小 API 情況下,“流暢的語法”感覺更自然。?
恢復丟失的 Startup.cs 文件
?? ?接下來,讓我們看看能否恢復我們的老朋友Startup.cs文件。如果將所有內容都放在一個文件中,最小 API 結構可能會變得混亂和臃腫。
?? ?一定有辦法讓我們更好地組織這些。我將首先向Startup.cs項目添加一個名為的新類。
public class Startup
{
? ? public Startup(IConfiguration configuration)
? ? {
? ? ? ? Configuration = configuration;
? ? }
? ? public IConfiguration Configuration { get; }
? ? public void ConfigureServices(IServiceCollection services)
? ? {
? ? }
? ? ?public void Configure(WebApplication app, IWebHostEnvironment env)
? ? {
? ? }
}?
????????這看起來應該與過去的完整 Web API 項目模板很相似。我只是將其粘貼到我的新Startup.cs
文件中并刪除了命名空間。該Startup.cs
文件需要IConfiguration
傳入一個對象。我們可以從我們的 中提供該對象嗎Program.cs
??
// ?Program.cs file
var builder = WebApplication.CreateBuilder(args);
var startup = new Startup(builder.Configuration);?
????????是的。我們可以這樣做。builder創建的對象包含一個Configuration屬性,我們可以使用該屬性將其傳遞給Startup構造函數。
????????接下來,讓我們嘗試為該ConfigureServices()方法提供一個IServiceCollection。
// ?Program.cs file
var builder = WebApplication.CreateBuilder(args);
var startup = new Startup(builder.Configuration);
startup.ConfigureServices(builder.Services);?
????????同樣,builder
包含一個Services
我們可以用來傳遞給ConfigureServices()
方法的屬性。現在我們可以從中刪除所有依賴注入代碼Program.cs
并將其移動到ConfigureServices
中的方法Startup.cs
。?
// Startup.cs file
public void ConfigureServices(IServiceCollection services)
{
? ? services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer();
? ? services.AddAuthorization();
}
接下來,讓我們嘗試Configure()
用WebApplication
類提供方法。?
// ?Program.cs file
var builder = WebApplication.CreateBuilder(args);
var startup = new Startup(builder.Configuration);
startup.ConfigureServices(builder.Services);
var app = builder.Build();
startup.Configure(app, builder.HostingEnvironment);?
????????有趣的是,雖然我們傳遞的app是 類型WebApplication,但如果你檢查它,該Configure()方法需要的是IApplicationBuilder,但它似乎沒問題。為什么?如果你深入研究該WebApplication對象,你會看到它實現了IApplicationBuilder接口。
????????我們將中間件管道代碼剪切出來Program.cs,并將其粘貼到Configure()方法中Startup.cs。
// Startup.cs file
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
? app.UseAuthentication();
? app.UseAuthorization();
? app.MapGet("/", () =>
? {
? ? ? return "hello world";
? }).RequireAuthorization();
? app.Run();
}
????????不幸的是,這實際上行不通,因為接口IApplicationBuilder
沒有MapGet()
方法或Run()
方法。那些存在于WebApplication
類中。如果我們將Configure()
方法更改為接受WebApplication
對象,它應該可以工作。?
// Startup.cs file
public void Configure(WebApplication app, IWebHostEnvironment env)
{
? app.UseAuthentication();
? app.UseAuthorization();
? app.MapGet("/", () =>
? {
? ? ? return "hello world";
? }).RequireAuthorization();
? app.Run();
}
讓我們看一下完整的文件“Program.cs”和“Startup.cs”,看看它們現在是什么樣子。?
// Program.cs file
var builder = WebApplication.CreateBuilder(args);
var startup = new Startup(builder.Configuration);
startup.ConfigureServices(builder.Services);
var app = builder.Build();
startup.Configure(app, builder.Environment);
// Startup.cs file
using Microsoft.AspNetCore.Authentication.JwtBearer;
public class Startup
{
? ? public Startup(IConfiguration configuration)
? ? {
? ? ? ? Configuration = configuration;
? ? }
? ? public IConfiguration Configuration { get; }
? ? // This method gets called by the runtime. Use this method to add services to the container.
? ? public void ConfigureServices(IServiceCollection services)
? ? {
? ? ? ? services.AddAuthentication(options =>
? ? ? ? {
? ? ? ? ? ? options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
? ? ? ? }).AddJwtBearer();
? ? ? ? services.AddAuthorization();
? ? }
? ? // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
? ? public void Configure(WebApplication app, IWebHostEnvironment env)
? ? {
? ? ? ? app.UseAuthentication();
? ? ? ? app.UseAuthorization();
? ? ? ? app.MapGet("/", () =>
? ? ? ? {
? ? ? ? ? ? return "hello world";
? ? ? ? }).RequireAuthorization();
? ? ? ? app.Run();
? ? }
}
????????如果我們運行它,我們會得到401 Unauthorized
狀態,這意味著它正在運行。除了將我們的配置內容移到 中之外Startup.cs
,我們沒有做太多改變,這讓它看起來更像舊的 .Net Core 風格。?
我們可以做得更好嗎?
?? ?使用Startup.cs確實有助于改善組織,但我認為我們可以做得更好。我一直討厭文件中Startup.cs單詞ConfigureServices()和Configure()彼此太接近,而且你還要傳遞一個IConfiguration對象。這可能會讓人搞不清楚什么放在哪里。
?? ?我們為什么不嘗試改進這一點呢?我不喜歡這個名字ConfigureServices()。如果我們將它重命名為RegisterDependentServices()并將其放在自己的文件中會怎么樣?這將使我們更容易理解發生了什么。
// RegisterDependentServices.cs file
using Microsoft.AspNetCore.Authentication.JwtBearer;
public static class RegisterDependentServices
{
? ? public static WebApplicationBuilder RegisterServices(this WebApplicationBuilder builder)
? ? {
? ? ? ? // Register your dependencies
? ? ? ? builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
? ? ? ? ? ? .AddJwtBearer();
? ? ? ? builder.Services.AddAuthorization();
? ? ? ? return builder;
? ? }
}
????????我決定創建該類static并使用擴展方法將其添加到該類中。我們稍后WebApplicationBuilder會看到這如何改進文件。Program.cs
????????接下來,讓我們創建一個新文件SetupMiddlewarePipeline.cs。此文件將包含中間件管道。
public static class SetupMiddlewarePipeline
{
? ? public static WebApplication SetupMiddleware(this WebApplication app)
? ? {
? ? ? ? // Configure the pipeline !! ORDER MATTERS !!
? ? ? ? app.UseAuthorization();
? ? ? ? app.UseAuthentication();
? ? ? ? app.MapGet("/", () =>
? ? ? ? {
? ? ? ? ? ? return "hello world";
? ? ? ? }).RequireAuthorization();
? ? ? ? return app;
? ? }
}
現在,這會如何改變我們的Program.cs
文件??
// Program.cs file
WebApplication app = WebApplication.CreateBuilder(args)
? ? .RegisterServices()
? ? .Build();
app.SetupMiddleware()
? ? .Run();
這樣就生成了一個非常干凈的Program.cs
文件。事實上,如果您愿意,您可以將其全部放在一行中。
// Program.cs file
WebApplication.CreateBuilder(args)
? ? .RegisterServices()
? ? .Build()
? ? .SetupMiddleware()
? ? .Run();
我并不討厭它。事實上,我覺得我喜歡它。
那么 IConfiguration 怎么樣?
?? ?之前,Startup()構造函數需要IConfiguration注入其中。但是,由于WebApplication和WebApplicationBuilder都具有.Configuration屬性,因此我們不再需要顯式注入它。
// RegisterDependentServices.cs file
public static class RegisterDependentServices
{
? public static WebApplicationBuilder RegisterServices(this WebApplicationBuilder builder)
? {
? ? // ******* Access the configuration *******
? ? var config = builder.Configuration;
? ? // Register your dependencies
? ? builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
? ? ? ? .AddJwtBearer();
? ? builder.Services.AddAuthorization();
? ? return builder;
? }
}
// setupMiddlewarePipeline.cs file
public static class SetupMiddlewarePipeline
{
? public static WebApplication SetupMiddleware(this WebApplication app)
? {
? ? // ******** Access the configuration ********
? ? var config = app.Configuration;
? ? // Configure the pipeline !! ORDER MATTERS !!
? ? app.UseAuthorization();
? ? app.UseAuthentication();
? ? app.MapGet("/", () =>
? ? {
? ? ? return "hello world";
? ? }).RequireAuthorization();
? ? return app;
? }
}
如果我們需要訪問應用程序的配置,它是可用的,而且我們的狀況很好。?
如果您喜歡此文章,請收藏、點贊、評論,謝謝,祝您快樂每一天。?