在以前的MVC引用程序中,控制器是一個功能齊全的框架,但它偏重,因此在.Net6.0官方引入了MinimalAPIs,即最小API,與MVC相比,它足夠的簡潔,適合小型服務來使用,下面就讓我們看看如何使用MinimalAPI來開發一個web應用程序
入門
下面我們來看一下官方提供的MinimalAPI
是如何使用的
前提條件:安裝.NET 6.0?(https://dotnet.microsoft.com/zh-cn/download/dotnet/6.0)
1.新建ASP.NET Core 空項目Assignment.MinimalApiDemo
dotnet new web -o Assignment.MinimalApiDemo
cd Assignment.MinimalApiDemo
2.增加一個Get
請求,修改Program
app.MapGet("/test", () => "Test Success!");
根據需求,自行增加Get
?(MapGet)、Post
?(MapPost)、Put
?(MapPut)、Delete
?(MapDelete)方法即可,完整代碼如下:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/test", () => "Test Success!");
MASA Framework版MinimalAPI
隨著我們的服務變得越來越多,這些服務全部被堆積在Program
中,這樣豈不是變成流水賬式的代碼?那怎么做才能使得我們的代碼更加美觀呢?
下面我們就來看一下MASA Framework提供的MinimalAPIs
?(https://github.com/masastack/MASA.Framework/blob/main/src/Contrib/Service/Masa.Contrib.Service.MinimalAPIs/README.zh-CN.md)是如何來使用的
1.選中項目Assignment.MinimalApiDemo
,并安裝Masa.Contrib.Service.MinimalAPIs
dotnet add package Masa.Contrib.Service.MinimalAPIs --version 0.6.0-preview.13
2.注冊MASA Framework版的MinimalAPI
,修改Program
var app = builder.AddServices();
3.新增加一個用戶的服務,新增UserService
類
public class UserService : ServiceBase
{public IResult Add(RegisterUserRequest request){//模擬添加用戶return Results.Ok();}
}
如何新建類目到這里已經結束了,可能會有小伙伴十分的疑惑,MASA Framework提供的方案讓我有點摸不著頭腦,但項目運行后就會發現在Swagger上多了一個服務
細心的小伙伴發現了,這個服務好像是我們新增的添加用戶服務
,但鏈接地址為什么是api/v1/Users
?呢?讓我們接著往下看。
進階
通過快速入門我們了解到如何使用MinimalAPI
,但我們也清楚流水賬式編程的危害,我們不希望讓項目中充斥著流水賬式的代碼,我們希望它是整潔的,并且是有跡可循的,這時候MASA提供的MinimalAPI方案進入了我們的視野,它上手難度極低,對我們來說它是很棒的,但如果我們不清楚它是如何設計的話,我們敢放心大膽的使用它嗎?雖然它有些枯燥,但我們必須要掌握它是如何設計的,它都支持了什么樣的功能
約定
當服務未禁用自動映射路由時,框架會自動掃描繼承ServiceBase
的非抽象子類并注冊到服務集合中(IServiceCollection),并為滿足以下要求的方法自動注冊路由
當前類的方法的訪問級別為
public
(不包含父類方法)方法上未增加特性
IgnoreRouteAttribute
路由規則
路由規則優先級:
自定義路由 > 約定生成路由
如何自定義路由?
通過RoutePattern
特性我們可以為方法自定義路由
[RoutePattern("user/add")]
public IResult Add([FromBody]RegisterUserRequest request)
{//模擬添加用戶return Results.Ok();
}
約定的生成路由規則為:
Pattern(路由) = BaseUri + RouteMethodName
BaseUri
: 根地址,默認: null當
BaseUri
為空
或者null
時,則?BaseUri = Prefix/Version/ServiceName
RouteMethodName
: 除非自定義RouteMethodName
,否則RouteMethodName = GetMethodName(方法名)
GetMethodName:
TrimStart:
Get/Post/Create/Put/Update/Delete/Remove
?等TrimEnd:
Async
PS:/api/v1/User/Add
,將會變成/api/v1/User
當方法的參數存在id并且id支持從Route中獲取時,將會變成/api/v1/User/{id}
,如果id為可空或者存在默認值時,將會變成/api/v1/User/{id?}
配置
配置分為全局配置、局部配置(僅在當前服務生效),其中優先級為:局部配置 > 全局配置,默認局部配置的參數為null,我們約定局部參數未配置時,以全局配置為準
全局配置
DisableAutoMapRoute: 是否禁用自動映射路由,如果為true (禁用),則框架不會自動映射路由,默認:false
Prefix: 前綴,默認: api
Version: 版本,默認: v1
AutoAppendId: 是否追加Id,默認: true
PluralizeServiceName: 服務名稱是否啟用復數,默認: true
GetPrefixes: 用于識別當前方法類型為
Get
請求,默認:?new List<string> { "Get", "Select" }
PostPrefixes: 用于識別當前方法類型為
Post
請求,默認:?new List<string> { "Post", "Add", "Upsert", "Create" }
PutPrefixes: 用于識別當前方法類型為
Put
請求,默認:?new List<string> { "Put", "Update", "Modify" }
DeletePrefixes: 用于識別當前方法類型為
Delete
請求,默認:?new List<string> { "Delete", "Remove" }
DisableTrimMethodPrefix: 禁用移除方法前綴(
Get/Post/Create/Put/Update/Delete/Remove
?等), 默認: falseMapHttpMethodsForUnmatched: 匹配請求方式失敗使用,默認: 支持Post、Get、Delete、Put
Assemblies: 用于掃描服務所在的程序集,默認:?
AppDomain.CurrentDomain.GetAssemblies()
RouteHandlerBuilder: 基于
RouteHandlerBuilder
的委托,可用于權限認證、Cors等
局部配置
BaseUri: 根地址,默認: null
ServiceName: 自定義服務名,默認: null
RouteHandlerBuilder:基于
RouteHandlerBuilder
的委托,可用于權限認證、Cors等RouteOptions: 局部路由配置
DisableAutoMapRoute
Prefix
Version
AutoAppendId
PluralizeServiceName
GetPrefixes
PostPrefixes
PutPrefixes
DeletePrefixes
DisableTrimMethodPrefix
MapHttpMethodsForUnmatched
其中ServiceName為null時,
ServiceName = 類名.TrimEnd("Service")
?//不區分大小寫
特性
RoutePattern
用于自定義路由,支持參數
Pattern: 自定義路由或自定義方法名
當StartWithBaseUri:true,Pattern為自定義方法名
當StartWithBaseUri:false,Pattern為自定義路由
StartWithBaseUri: 是否基于BaseUri進行追加,默認: false
HttpMethod:請求類型,默認: null(根據方法名前綴自動識別),如果希望指定請求類型而非自動識別,則可手動指定:
Get
、Post
、Put
、Delete
IgnoreRoute
用于忽略方法自動映射,例如;存在某個方法已經手動指定映射路由,不希望框架重復進行映射可使用IgnoreRoute
, 例如:
public class User2Service : ServiceBase
{public User2Service(){App.Map("/api/v2/user/add", Add);}[IgnoreRoute]public void Add([FromBody] RegisterUserRequest request, IData data){data.Add(request.Name, request.Age);}
}
場景
通過上面的學習我們已經了解到了MASA
提供了哪些配置,那下面就讓我們實戰來演練一下,通過模擬不同的場景使用不同的配置,以確保我們正確掌握這些知識
場景一:
Q: 我不是一個新手,從0.6.0版本以前的版本就開始使用MASA
提供的MinimalAPI
了,對新版的MinimalAPI
很喜歡,但我暫時不希望更改手動注冊的方式,我希望升級之后不會對我現有的項目造成影響,我不希望將升級導致原來的服務無法訪問
A: 你希望繼續使用最新版的MinimalAPI
,但不希望對原來的項目造成影響,在當前服務中,希望能一如既往的使用手動注冊,而不是自動注冊,那你可以配置全局禁用自動注冊,例如:
var app = builder.AddServices(options =>
{options.DisableAutoMapRoute = true;
});
當然如果你希望在某個特定的服務中開啟自動映射,則可以在服務中配置:
public class UserService: ServiceBase
{public UserService(){RouteOptions.DisableAutoMapRoute = false;}public void Add([FromBody] RegisterUserRequest request, IData data){data.Add(request.Name, request.Age);}
}
場景二:
Q: 我是一個新手,我覺得我的項目不需要使用前綴以及版本,我希望自動映射的路由可以幫助我刪掉它們
A: 你需要的是全局配置,通過全局配置禁用前綴以及版本即可,例如:
var app = builder.AddServices(options =>
{options.Prefix = string.Empty;options.Version = string.Empty;
});
場景三:
Q: 我是一個新手,雖然我很想嚴格遵守Resetful標準來寫服務,但遺憾的是我無法掌控全局,總是有人不按照標準對方法進行命名,我希望可以人為控制特定的方法的路由
A: 目前有兩種方法可供選擇,它們分別是:
第一種:自定義路由并忽略自動映射
public class UserService : ServiceBase
{public UserService(){App.Map("/user/add", Add);}[IgnoreRoute]public void Add([FromBody] RegisterUserRequest request, IData data){data.Add(request.Name, request.Age);}
}
第二種: 完整自定義路由
public class UserService : ServiceBase
{[RoutePattern("/api/v2/user/add")]public void CreateUser([FromBody] RegisterUserRequest request, IData data){data.Add(request.Name, request.Age);}
}
第三種: 僅修改請求方式
public class UserService : ServiceBase
{[RoutePattern(HttpMethod = "Post")]public void CreateUser([FromBody] RegisterUserRequest request, IData data){data.Add(request.Name, request.Age);}
}
如果你希望手動指定方法的請求類型,則可以使用
[RoutePattern("/api/v2/user/add", HttpMethod = "Post")]
場景四:
Q: 我希望為項目中所有的接口都必須授權才能訪問,但我不希望在每個方法上增加Authorize
特性
A: 你的項目是需要為全局服務來設置,則可通過全局配置的RouteHandlerBuilder
參數來完成,例如:
var app = builder.AddServices(options =>
{options.RouteHandlerBuilder = routeHandlerBuilder => routeHandlerBuilder.RequireAuthorization();
});
如果你希望對某個服務增加特殊的授權策略,則可以:
public class UserService : ServiceBase
{public UserService(){RouteHandlerBuilder = routeHandlerBuilder => routeHandlerBuilder.RequireAuthorization("test");}public void CreateUser([FromBody] RegisterUserRequest request, IData data){data.Add(request.Name, request.Age);}
}
但是你必須知道的是,如果在服務內配置了
RouteHandlerBuilder
,那么全局配置的RouteHandlerBuilder
將對當前服務失效,局部配置存在時,全局配置將不起作用
場景五:
Q: 我希望某個服務不需要經過授權即可訪問,那我該怎么做?
A: 只需要在方法上加AllowAnonymous
特性即可, 它是MinimalAPI支持的,除了AllowAnonymous
、EnableCors
、Authorize
等都是支持的, 但HttpGet
、HttpPost
、HttpPut
、HttpDelete
特性是不支持的
public class UserService : ServiceBase
{[AllowAnonymous]public void CreateUser([FromBody] RegisterUserRequest request, IData data){data.Add(request.Name, request.Age);}
}
常見問題
Q1:為何使用DbContext時總是提示DbContext
已經被釋放?
A:UserService僅在項目啟動時會被初始化一次,之后不再初始化,因此Service的構造函數參數僅支持Singleton
或Transient
。如果您的服務的生命周期為Scoped
,建議在對應的方法中增加參數,例如:
public void Add([FromBody] RegisterUserRequest request, IData data)
{data.Add(request.Name, request.Age);
}
Q2:模型校驗不起作用?
A:目前版本的MinimalAPI
并不支持模型綁定與驗證,后續版本(https://github.com/dotnet/aspnetcore/issues/39504)會增加支持
Q3:Builder.AddServices()
又為什么必須要放到最后?
A:我們知道通過builder.Build()
可以得到WebApplication
,但在.Net6.0中新增加了限制,這個限制就是在Build
后無法再次更新IServiceCollection
,否則會提示Cannot modify ServiceCollection after application is built
Q4:為什么MinimalAPIs
的生命周期是單例?
A:目前AddServices
方法中做了兩件事,第一件事就是獲取到所有的服務,并注冊到服務集合中,第二件事就是觸發服務并將對應服務的地址以及方法映射到到App
,App.Map
類似App.Use
,也是一個擴展方法,類似MVC的路由,其生命周期是單例,我們僅僅是將繼承ServiceBase
的服務映射到App
中,并沒有魔改MinimalAPI
,因此并不存在性能問題,但同樣其生命周期也無法改變
總結
MinimalAPI
與MVC
我應該如何選擇?
小型服務使用MinimalAPI
,因為它是很輕量級的,但如果是大型服務或者功能特別復雜的,還是推薦使用MVC
,MinimalAPI
的上手成本很低,但它不是銀彈,選擇適合自己的才是最好的
MinimalAPI還有一些特殊的地方,例如Get
請求無法使用類對象來接收參數,如果希望使用類對象來接受,
則需要使用自定義綁定? (https://docs.microsoft.com/zh-cn/aspnet/core/fundamentals/minimal-apis?view=aspnetcore-6.0#custom-binding)
除此之外還有其他不一樣的地方
完整文檔可查看(https://docs.microsoft.com/zh-cn/aspnet/core/fundamentals/minimal-apis?view=aspnetcore-6.0)
本章源碼
Assignment14
https://github.com/zhenlei520/MasaFramework.Practice
開源地址
MASA.Framework:https://github.com/masastack/MASA.Framework
如果你對我們的 MASA Framework 感興趣,無論是代碼貢獻、使用、提 Issue,歡迎聯系我們
《MASA?Framework實戰課程》已開課
點擊“閱讀原文”查看課程安排