从MVC到使⽤ASP.NETCore6.0的MinimalApi
从 MVC 到使⽤ ASP.NET Core 6.0 的Minimal API
2007 年,随着 ASP.NET MVC 引⼊了其他语⾔中变得司空见惯的,并为其提供原⽣⽀持,.NET Web 应⽤程序开发有了极速的发展。2012 年,也许是由于 ReSTful API 的⽇益流⾏,借鉴了 ASP.NET MVC 的许多概念⼜引⼊了 ASP.NET Web API,这是对 WCF 的重⼤改进,使开发⼈员能够以更少的仪式构建 HTTP API,。
后来,在 ASP.NET Core 中,⽤于构建⽹站和 API 的单⼀框架,这些框架被统⼀到了 ASP.NET Core MVC 中。
在 ASP.NET Core MVC 应⽤程序中,控制器负责接受输⼊、执⾏或编排操作并返回响应。它是⼀个功能齐全的框架,通过过滤器、内置模型绑定和验证、约定和基于声明的⾏为等提供可扩展的管道。对于许多⼈来说,它是构建现代 HTTP 应⽤程序的多合⼀解决⽅案。
在某些情况下,您可能只需要 MVC 框架的特定功能或具有使 MVC 不受欢迎的性能限制。随着更多 HTTP 功能作为 ASP.NET Core 中间件(例如⾝份验证、授权、路由等)出现,⽆需 MVC 即可构建轻量级 HTTP 应⽤程序变得更加容易,但通常需要⼀些功能,否则您必须⾃⼰构建,例如作为模型绑定和 HTTP 响应⽣成。
ASP.NET Core 6.0 旨在通过 Minimal API 弥合这⼀差距,以更少的仪式提供 ASP.NET MVC 的许多功能。这篇⽂章提供了有关如何将传统MVC 概念转换为这种构建轻量级 HTTP API 和服务的新⽅法的分步指南。
在这些⽰例中,我使⽤的是 .NET 6.0 预览 7,为了提供公平和最新的并排⽐较,我还使⽤了最新的webapi模板,因为 MVC 还受益于 C# 10的⼀些新特性,使事情变得更加“最⼩化”。
Startup
MVC
dotnet new webapi
新的 ASP.NET 模板取消了Startup类并利⽤了 C# 10 的顶级语句功能,因此我们有⼀个Program.cs包含所有引导代码的⽂件:
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (builder.Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
调⽤builder.Services.AddControllers()负责注册 MVC 框架依赖项并发现我们的控制器。然后我们调⽤app.MapControllers()注册我们的控制器路由和MVC 中间件。
MinimalAPI
dotnet new web
ASP.NET Empty 模板对规范的“Hello world”⽰例使⽤ Minimal API:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.MapGet("/", () => "Hello World!");
app.Run();
该MapGet⽅法是 Minimal API 扩展的⼀部分。除此之外,它与 MVC 并没有太⼤区别(考虑到 HTTPS 重定向和授权中间件只是从 Empty 模
板中省略⽽不是隐式启⽤)。
定义路由和处理程序
MVC
在 MVC 中,我们有定义路由的⽅法,⼀种是通过约定,⼀种是使⽤属性。
基于约定的路由更常⽤于⽹站⽽不是 API,并包含在mvc模板中。⽽不是app.MapControllers我们使⽤:
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
所述pattern指定路线的不同区段,并且允许指定的默认值。参数可以利⽤ ASP.NET 的来限制接受的
值。
对于 API,建议使⽤。
通过属性路由,您可以使⽤指定 HTTP 动词和路径的属性来装饰控制器和动作:
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
private static readonly string[] Summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
[HttpGet]
public IEnumerable<WeatherForecast> Get()
{
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
})
.ToArray();
}
}
在启动时,路由将⾃动注册。上⾯的⽰例来⾃默认webapi模板,演⽰了路由令牌替换。该[Route("[controller]")]属性将使⽤/weatherforecast所有路由的前缀(或资源)(控制器类名减去“Controller”后缀),⽆参数[HttpGet]属性将在资源的根处注册操作,因此HTTP GET /weatherforecast将命中此操作。
如果我想扩展 API 以允许按位置检索预测,我可以添加以下操作:
[HttpGet("locations/{location}")]
public IEnumerable<WeatherForecast> GetByLocation(string location)
{
}
请求时,/weatherforecast/locations/london该值london将绑定到相应的操作参数。
与它们的 Minimal API 对应物相⽐,MVC 控制器看起来⾮常臃肿。但是,值得注意的是,控制器也可
以是 POCO(Plain Old CLR Objects)。为了获得与上⾯的“Hello World”最⼩ API ⽰例相同的结果,我们只需要:
public class RootController
{
[HttpGet("/")]
public string Hello() => "Hello World";
}
从这⾥你可以看到尤其是当你考虑到你仍然需要⼀定程度的模块化时,即使使⽤Minimal API, MVC 也可以是“Minimal”,。
MinimalAPI
要使⽤ Minimal API 定义路由和处理程序,请使⽤Map(Get|Post|Put|Delete)⽅法。有趣的是没有MapPatch⽅法,但您可以使⽤MapMethods.
要使⽤ Minimal API 实现相同的天⽓预报⽰例:
var summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
app.MapGet("/weatherforecast", () =>
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = summaries[Random.Shared.Next(summaries.Length)]
})
.ToArray();
});
app.Run();
与 MVC ⽰例类似,我们可以将其扩展为按位置查询:
app.MapGet("/weatherforecast/locations/{location}", (string location) =>
{
});
请注意,在 MVC 和 Minimal API ⽰例中,我们受益于返回类型到序列化 HTTP 200 (OK) 响应的隐式转换。稍后我们将介绍两个框架的更明确的 HTTP 对象模型。
模型绑定
模型绑定是从 HTTP 请求中检索值并将它们转换为 .NET 类型的过程。由于我们在上⾯介绍了绑定路由值,本节将主要关注在请求正⽂中或通过查询字符串参数接收 JSON 数据。
MVC
在 MVC 中,您可以将 JSON 从请求正⽂绑定到 .NET 类型,⽅法是将其作为参数传递给您的操作⽅法并使⽤[FromBody]属性对其进⾏修饰:[HttpPost("/payments")]
public IActionResult Post([FromBody]PaymentRequest request)
{
}
或者,通过使⽤[ApiController]属性装饰您的控制器,将应⽤⼀个约定来绑定主体中的任何复杂类型。
在某些情况下,您可能希望从查询参数绑定复杂类型。我喜欢为具有多个过滤选项的搜索端点执⾏此操作。您可以使⽤以下[FromQuery]属性实现此⽬的:
[HttpGet("/echo")]
public IActionResult Search([FromQuery]SearchRequest request)
{
}
否则,简单类型将从路由或查询字符串值绑定:
[HttpGet("/portfolios/{id}")]
public IActionResult Search(int id, int? page = 1, int? pageSize = 10)
{
}
/portfolios/10?page=2&pagesize=20将满⾜上述操作参数的请求。
上⾯的⽰例还通过将可选参数标记为可为空并可选地提供默认值来演⽰可选参数的使⽤。
这对于复杂类型的⼯作⽅式略有不同。即使将类型设为可空,如果未发送正⽂,您将收到 HTTP 415(⽆效媒体类型)或 400(错误请求)响应,具体取决于是否Content-Type设置了标头。
以前,这种⾏为只能通过全局进⾏MvcOptions.AllowEmptyInputInBodyModelBinding全局配置,但从 ASP.NET Core 5 开始,它现在可以按请求进⾏配置:
[HttpPost("/payments")]
public IActionResult Post([FromBody(EmptyBodyBehavior = EmptyBodyBehavior.Allow)]PaymentRequest? request)
{
}
MinimalAPI
Minimal API 中的模型绑定⾮常相似;您使⽤您希望从请求中绑定的类型配置您的处理程序委托。复杂类型将从请求正⽂中⾃动绑定,⽽简单类型将从路由或查询字符串参数中绑定。使⽤ Minimal API 实现的相同⽰例如下:
app.MapPost("/payments", (PaymentRequest paymentRequest) =>
});
app.MapGet("/portfolios/{id}", (int id, int? page, int? pageSize) =>
{
});
为了指定默认值,您需要传递⼀个⽅法作为委托,因为 C# 尚不⽀持内联 lambda 函数的默认值:
app.MapGet("/search/{id}", Search);
app.Run();
minimalIResult Search(int id, int? page = 1, int? pageSize = 10)
{
}
该[FromQuery]属性不⽀持绑定复杂类型。有可⽤于⾃定义模型绑定的扩展点,我将在后⾯的⽂章中介绍。
要⽀持可选的请求参数,您可以应⽤与[FromBody]MVC相同的属性,指定EmptyBodyBehavior:
app.MapPost("/payments", ([FromBody(EmptyBodyBehavior = EmptyBodyBehavior.Allow)]PaymentRequest? paymentRequest]) =>
{
});
HTTP 响应
MVC 和 Minimal API 都会⾃动将您的返回类型序列化到响应正⽂并返回 HTTP 200 (OK) 响应,例如:
// MVC
[HttpPost("/echo")]
public EchoRequest Echo(EchoRequest echo) => echo;
// Minimal API
app.MapPost("/echo", (EchoRequest echo) => echo);
您还可以返回void或Task返回⼀个空的 HTTP 200 (OK) 响应:
// MVC
[HttpPost("/echo")]
public void Echo(EchoRequest echo) => {};
// Minimal API
app.MapPost("/echo", (EchoRequest echo) => {});
除了隐式转换之外,MVC 和 Minimal API 都有⼀个丰富的 HTTP 响应对象模型,涵盖了最常见的 HTTP 响应。
MVC
在 MVC 中,您可以返回IActionResult并使⽤许多内置实现,例如AcceptedResult. 如果您是从ControllerBase那⾥派⽣控制器的,那么⼤多数响应类型都可以使⽤辅助⽅法:
[HttpDelete("/projects/{id}")]
public IActionResult Delete(int id)
{
return Accepted();
}
MinimalAPI
使⽤ Minimal API,我们可以返回IResult. 在Results静态类可以很容易地产⽣了⼀些内置的响应类型:
app.MapDelete("/projects/{id}", (int id) =>
{
return Results.Accepted();
});
依赖注⼊
MVC
要将依赖项注⼊ MVC 控制器,我们通常使⽤构造函数注⼊,其中所需的类型(或更常见的是它们的底层接⼝)作为构造函数参数提供:
public class CacheController : ControllerBase
private readonly ICache _cache;
public CacheController(ICache cache)
{
_cache = cache;
}
[HttpDelete("/cache/{id}")]
public async Task<IActionResult> Delete(string id)
{
await _cache.Delete(id);
return Accepted();
}
}
依赖项在启动时注册(现在默认在 Program.cs 中):
builder.Services.AddScoped<ICache, MemoryCache>();
使⽤范围⽣命周期注册的服务将在 MVC 应⽤程序中按 HTTP 请求创建。
MinimalAPI
使⽤ Minimal API,我们仍然可以从依赖注⼊中受益,但不是使⽤构造函数注⼊,⽽是在处理程序委托中将依赖作为参数传递:
app.MapDelete("/cache/{id}", async (string id, ICache cache) =>
{
await cache.Delete(id);
return Results.Accepted();
});
这种⽅法更纯粹,可以使测试更容易。不利的⼀⾯是,⼀旦您获得多个依赖项,您的处理程序定义就会变得⾮常嘈杂。
最后,虽然依赖在内本地声明的依赖项可能很诱⼈Program.cs,但这不仅会使测试变得困难,⽽且还会导致范围问题。我建议尽可能利⽤ DI 容器,即使是单例依赖。
HTTp上下⽂
您的 API 可能需要访问有关 HTTP 请求的其他信息,例如当前⽤户的标头或详细信息。MVC 和 Minimal API 都构建在您熟悉的相同ASP.NET Core HTTP 抽象之上。
MVC
在MVC中,获得您的控制器时,从ControllerBase您可以访问HttpContext,HttpRequest,HttpResponse和当前⽤户(ClaimsPrincipal从基类属性):[HttpGet]
public IEnumerable<WeatherForecast> Get()
{
if (Request.Headers.TryGetValue("some header", out var headerValue))
{
}
bool isSpecialUser = User.Identity.IsAuthenticated
&& User.HasClaim("special");
如果您的控制器是⼀个简单的 POCO 并且不是派⽣⾃ControllerBase您,则需要使⽤构造函数注⼊来注⼊IHttpContextAccessor您的控制器或直接访问请求、响应和⽤户,请为这些类型执⾏⼀些 DI 连接。如果 POCO 控制器可以利⽤类似于下⾯描述的 Minimal API 的⽅法注⼊,那就太好了。
MinimalAPI
使⽤ Minimal API,您可以通过将作为参数传递给处理程序委托来访问相同的上下⽂信息:
HttpContext
HttpRequest
HttpResponse
ClaimsPrincipal
CancellationToken(请求中⽌)
app.MapGet("/hello", (ClaimsPrincipal user) => {
return "Hello " + user.FindFirstValue("sub");
});
Url映射
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论