关于.NET6 Minimal API的使用方式详解

2022-04-16 00:23:35
目录
前言使用方式几行代码构建Web程序更改监听地址y] Goods goods, [FromServices] Person person, [FromQuery]int age = 20) => $"{person.Name}添加商品{goods.GName}成功";

在使用Map相关方法的时候,由于是在Program入口程序或者其他POCO中直接编写相关逻辑的,因此需要用到HttpContext、HttpRequest、HttpResponse相关实例的时候没办法进行直接操作,这个时候也需要通过模型绑定的方式获取对应实例

app.MapGet("/getcontext",(HttpContext context,HttpRequest request,HttpResponse response) => response.WriteAsync($"IP:{context.Connection.RemoteIpAddress},Request Method:{request.Method}"));

自定义绑定

Minimal Api采用了一种新的方式来自定义模型绑定,这种方式是一种基于约定的方式,无需提前注册,也无需集成什么类或者实现什么接口,只需要在自定义的类中存在TryParseBindAsync方法即可,这两个方法的区别是

TryParse方法是对路由参数、url参数、header相关的信息进行转换绑定 BindAsync可以对任何请求的信息进行转换绑定,功能比TryParse要强大

接下来我们分别演示一下这两种方式的使用方法,首先是TryParse方法

app.MapGet("/address/getarray",(Address address) => address.Addresses);public class Address{    public List<string>? Addresses { get; set; }    public static bool TryParse(string? addressStr, IFormatProvider? provider, out Address? address)    {        var addresses = addressStr?.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);        if (addresses != null && addresses.Any())        {            address = new Address { Addresses = addresses.ToList() };            return true;        }        address = new Address();        return false;    }}

这样就可以完成简单的转换绑定操作,从写法上我们可以看到,TryParse方法确实存在一定的限制,不过操作起来比较简单,这个时候我们模拟请求

http://localhost:5036/address/getarray?address=山东,山西,河南,河北

请求完成会得到如下结果

["山东","山西","河南", "河北"]

然后我们改造一下上面的例子使用BindAsync的方式进行结果转换,看一下它们操作的不同

app.MapGet("/address/getarray",(Address address) => address.Addresses);public class Address{    public List<string>? Addresses { get; set; }    public static ValueTask<Address?> BindAsync(HttpContext context, ParameterInfo parameter)    {        string addressStr = context.Request.Query["address"];        var addresses = addressStr?.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);        Address address = new();        if (addresses != null && addresses.Any())        {            address.Addresses = addresses.ToList();            return ValueTask.FromResult<Address?>(address);        }        return ValueTask.FromResult<Address?>(address);    }}

同样请求http://localhost:5036/address/getarray?address=山东,山西,河南,河北 地址会得到和上面相同的结果,到底如何选择同学们可以按需使用,得到的效果都是一样的。如果类中同时存在TryParseBindAsync方法,那么只会执行BindAsync方法。

输出结果

相信通过上面的其他示例演示,我们大概看到了一些在Minimal Api中的结果输出,总结起来其实可以分为三种情况

IResult 结果输出,可以包含任何值得输出,包含异步任务 Task<IResult>ValueTask<IResult>string 文本类型输出,包含异步任务 Task<string>ValueTask<string>T 对象类型输出,比如自定义的实体、匿名对象等,包含异步任务 Task<T>ValueTask<T>

接下来简单演示几个例子来简单看一下具体是如何操作的,首先最简单的就是输出文本类型

app.MapGet("/hello", () => "Hello World");

然后输出一个对象类型,对象类型可以包含对象或集合甚至匿名对象,或者是咱们上面演示过的HttpResponse对象,这里的对象可以理解为面向对象的那个对象,满足Response输出要求即可

app.MapGet("/simple", () => new { Message = "Hello World" });//或者是app.MapGet("/array",()=>new string[] { "Hello", "World" });//亦或者是EF的返回结果app.Map("/student",(SchoolContext dbContext,int classId)=>dbContext.Student.Where(i=>i.ClassId==classId));

还有一种是微软帮我们封装好的一种形式,即返回的是IResult类型的结果,微软也是很贴心的为我们统一封装了一个静态的Results类,方便我们使用,简单演示一下这种操作

//成功结果app.MapGet("/success",()=> Results.Ok("Success"));//失败结果app.MapGet("/fail", () => Results.BadRequest("fail"));//404结果app.MapGet("/404", () => Results.NotFound());//根据逻辑判断返回app.Map("/student", (SchoolContext dbContext, int classId) => {    var classStudents = dbContext.Student.Where(i => i.ClassId == classId);    return classStudents.Any() ? Results.Ok(classStudents) : Results.NotFound();});

上面我们也提到了Results类其实是微软帮我们多封装了一层,它里面的所有静态方法都是返回IResult的接口实例,这个接口有许多实现的类,满足不同的输出结果,比如Results.File("foo.text")方法其本质就是返回一个FileContentResult类型的实例

public static IResult File(byte[] fileContents,string? contentType = null,string? fileDownloadName = null,bool enableRangeProcessing = false,DateTimeOffset? lastModified = null,EntityTagHeaderValue? entityTag = null)=> new FileContentResult(fileContents, contentType){    FileDownloadName = fileDownloadName,    EnableRangeProcessing = enableRangeProcessing,    LastModified = lastModified,    EntityTag = entityTag,};

亦或者Results.Json(new { Message="Hello World" })本质就是返回一个JsonResult类型的实例

public static IResult Json(object? data, JsonSerializerOptions? options = null, string? contentType = null, int? statusCode = null)            => new JsonResult            {                Value = data,                JsonSerializerOptions = options,                ContentType = contentType,                StatusCode = statusCode,            };

当然我们也可以自定义IResult的实例,比如我们要输出一段html代码。微软很贴心的为我们提供了专门扩展Results的扩展类IResultExtensions基于这个类我们才能完成IResult的扩展

static class ResultsExtensions{    //基于IResultExtensions写扩展方法    public static IResult Html(this IResultExtensions resultExtensions, string html)    {        ArgumentNullException.ThrowIfNull(resultExtensions, nameof(resultExtensions));        //自定义的HtmlResult是IResult的实现类        return new HtmlResult(html);    }}class HtmlResult:IResult{    //用于接收html字符串    private readonly string _html;    public HtmlResult(string html)    {        _html = html;    }    /// <summary>    /// 在该方法写自己的输出逻辑即可    /// </summary>    /// <returns></returns>    public Task ExecuteAsync(HttpContext httpContext)    {        httpContext.Response.ContentType = MediaTypeNames.Text.Html;        httpContext.Response.ContentLength = Encoding.UTF8.GetByteCount(_html);        return httpContext.Response.WriteAsync(_html);    }}

定义完成这些我们就可以直接在Results类中使用我们定义的扩展方法了,使用方式如下

app.MapGet("/hello/{name}", (string name) => Results.Extensions.Html(@$"<html>    <head><title>Index</title></head>    <body>        <h1>Hello {name}</h1>    </body></html>"));

PcRYwsdjsr里需要注意的是,我们自定义的扩展方法一定是基于IResultExtensions扩展的,然后再使用的时候注意是使用的Results.Extensions这个属性,因为这个属性是IResultExtensions类型的,然后就是我们自己扩展的Results.Extensions.Html方法。

总结

本文我们主要是介绍了ASP.NET Core 6 Minimal API的常用的使用方式,相信大家对此也有了一定的了解,在.NET6中也是默认的项目方式,整体来说却是非常的简单、简洁、强大、灵活,不得不说Minimal API却是在很多场景都非常适用的。当然我也在其它地方看到过关于它的评价,褒贬不一吧,笔者认为,没有任何一种技术是银弹,存在即合理。如果你的项目够规范够合理,那么使用Minimal API绝对够用,如果不想用或者用不了也没关系,能实现你想要结果就好了,其实也没啥好评价的。