在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
前言随着.Net6的发布,微软也改进了对之前ASP.NET Core构建方式,使用了新的Minimal API模式。之前默认的方式是需要在Startup中注册IOC和中间件相关,但是在Minimal API模式下你只需要简单的写几行代码就可以构建一个ASP.NET Core的Web应用,真可谓非常的简单,加之配合c#的global using和Program的顶级声明方式,使得Minimal API变得更为简洁,不得不说.NET团队在,NET上近几年真是下了不少功夫,接下来我们就来大致介绍下这种极简的使用模式。 使用方式既然说它很简单了,到底是怎么个简单法呢。相信下载过Visual Studio 2022的同学们已经用它新建过ASP.NET Core 6的项目了,默认的方式就是Minimal API模式,这样让整个Web程序的结构看起来更简单了,加上微软对Lambda的改进使其可以对Lambda参数进行Attribute标记,有的场景甚至可以放弃去定义Controller类了。 几行代码构建Web程序使用Minimal API最简单的方式就是能通过三行代码就可以构建一个WebApi的程序,代码如下 var app = WebApplication.Create(args); app.MapGet("/", () => "Hello World"); app.Run(); 是的你没有看错,仅仅这样运行起来就可以,默认监听的 更改监听地址如果你想更改它监听的服务端口可以使用如下的方式进行更改 var app = WebApplication.Create(args); app.MapGet("/", () => "Hello World"); app.Run("http://localhost:6666"); 如果想同时监听多个端口的话,可以使用如下的方式 var app = WebApplication.Create(args); app.Urls.Add("http://localhost:6666"); app.Urls.Add("http://localhost:8888"); app.MapGet("/", () => "Hello World"); app.Run(); 或者是直接通过环境变量的方式设置监听信息,设置环境变量 ASPNETCORE_URLS=http://localhost:6666 如果设置多个监听的URL地址的话可以在多个地址之间使用分号 ASPNETCORE_URLS=http://localhost:6666;https://localhost:8888 如果想监听本机所有Ip地址则可以使用如下方式 var app = WebApplication.Create(args); app.Urls.Add("http://*:6666"); app.Urls.Add("http://+:8888"); app.Urls.Add("http://0.0.0.0:9999"); app.MapGet("/", () => "Hello World"); app.Run(); 同样的也可以使用添加环境变量的方式添加监听地址 ASPNETCORE_URLS=http://*:6666;https://+:8888;http://0.0.0.0:9999 日志操作日志操作也是比较常用的操作,在Minimal API中微软干脆把它提出来,直接简化了操作,如下所示 var builder = WebApplication.CreateBuilder(args); builder.Logging.AddJsonConsole(); var app = builder.Build(); app.Logger.LogInformation("读取到的配置信息:{content}", builder.Configuration.GetSection("consul").Get<ConsulOption>()); app.Run(); 基础环境配置无论我们在之前的.Net Core开发或者现在的.Net6开发都有基础环境的配置,它包括 var builder = WebApplication.CreateBuilder(new WebApplicationOptions { ApplicationName = typeof(Program).Assembly.FullName, ContentRootPath = Directory.GetCurrentDirectory(), EnvironmentName = Environments.Staging }); Console.WriteLine($"应用程序名称: {builder.Environment.ApplicationName}"); Console.WriteLine($"环境变量: {builder.Environment.EnvironmentName}"); Console.WriteLine($"ContentRoot目录: {builder.Environment.ContentRootPath}"); var app = builder.Build(); 或者是通过环境变量的方式去配置,最终实现的效果都是一样的
主机相关设置我们在之前的.Net Core开发模式中,程序的启动基本都是通过构建主机的方式,比如之前的Web主机或者后来的泛型主机,在Minimal API中同样可以进行这些操作,比如我们模拟一下之前泛型主机配置Web程序的方式 var builder = WebApplication.CreateBuilder(args); builder.Host.ConfigureDefaults(args).ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup<Startup>(); }); var app = builder.Build(); 如果只是配置Web主机的话Minimal API还提供了另一种更直接的方式,如下所示 var builder = WebApplication.CreateBuilder(args); builder.WebHost.UseStartup<Startup>(); builder.WebHost.UseWebRoot("webroot"); var app = builder.Build(); 默认容器替换很多时候我们在使用IOC的时候会使用其他三方的IOC框架,比如大家耳熟能详的Autofac,我们之前也介绍过其本质方式就是使用 var builder = WebApplication.CreateBuilder(args); builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory()); //之前在Startup中配置ConfigureContainer可以使用如下方式 builder.Host.ConfigureContainer<ContainerBuilder>(builder => builder.RegisterModule(new MyApplicationModule())); var app = builder.Build(); 中间件相关相信大家都已经仔细看过了 var builder = WebApplication.CreateBuilder(args); builder.Services.AddControllers(); builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); var app = builder.Build(); //判断环境变量 if (app.Environment.IsDevelopment()) { //异常处理中间件 app.UseDeveloperExceptionPage(); app.UseSwagger(); app.UseSwaggerUI(); } //启用静态文件 app.UseStaticFiles(); app.UseAuthorization(); app.MapControllers(); app.Run(); 常用的中间件配置还是和之前是一样的,因为本质都是
请求处理我们可以使用 app.MapGet("/", () => "Hello GET"); app.MapPost("/", () => "Hello POST"); app.MapPut("/", () => "Hello PUT"); app.MapDelete("/", () => "Hello DELETE"); 如果想让一个路由地址可以处理多种Http方法的请求可以使用MapMethods方法,如下所示 app.MapMethods("/multiple", new[] { "GET", "POST","PUT","DELETE" }, (HttpRequest req) => $"Current Http Method Is {req.Method}" ); 通过上面的示例我们不仅看到了处理不同Http请求的方式,还可以看到Minimal Api可以根据委托的类型自行推断如何处理请求,比如上面的示例,我们没有写Response Write相关的代码,但是输出的却是委托里的内容,因为我们上面示例中的委托都满足 static string LocalFunction() => "This is local function"; app.MapGet("/local-fun", LocalFunction); 还可以是类的实例方法 HelloHandler helloHandler = new HelloHandler(); app.MapGet("/instance-method", helloHandler.Hello); class HelloHandler { public string Hello() { return "Hello World"; } } 亦或者是类的静态方法 app.MapGet("/static-method", HelloHandler.SayHello); class HelloHandler { public static string SayHello(string name) { return $"Hello {name}"; } } 其实本质都是一样的,那就是将他们转换为可执行的委托,无论什么样的形式,能满足委托的条件即可。 路由约束Minimal Api还支持在对路由规则的约束,这个和我们之前使用UseEndpoints的方式类似,比如我约束路由参数只能为整型,如果不满足的话会返回404 app.MapGet("/users/{userId:int}", (int userId) => $"user id is {userId}"); app.MapGet("/user/{name:length(20)}", (string name) => $"user name is {name}"); 经常使用的路由约束还有其他几个,也不是很多大概有如下几种,简单的列一下表格
模型绑定在我们之前使用ASP.NET Core Controller方式开发的话,模型绑定是肯定会用到的,它的作用就是简化我们解析Http请求信息也是MVC框架的核心功能,它可以将请求信息直接映射成c#的简单类型或者POCO上面。在Minimal Api的
绑定示例接下来我们首先看一下绑定路由参数 app.MapGet("/sayhello/{name}", (string name) => $"Hello {name}"); 还可以使用路由和querystring的混用方式 app.MapGet("/sayhello/{name}", (string name,int? age) => $"my name is {name},age {age}"); 这里需要注意的是,我的age参数加了可以为空的标识,如果不加的话则必须要在url的请求参数中传递age参数,否则将报错,这个和我们之前的操作还是有区别的。 具体的类也可以进行模型绑定,比如咱们这里定义了名为Goods的POCO进行演示 app.MapPost("/goods",(Goods goods)=>$"商品{goods.GName}添加成功"); class Goods { public int GId { get; set; } public string GName { get; set; } public decimal Price { get; set; } } 需要注意的是HTTP方法GET、HEAD、OPTIONS、DELETE将不会从body进行模型绑定,如果需要在Get请求中获取Body信息,可以直接从HttpRequest中读取它。 如果我们需要使用通过IServiceCollection注册的具体实例,可以以通过模型绑定的方式进行操作(很多人喜欢叫它方法注入,但是严格来说却是是通过定义模型绑定的相关操作实现的),而且还简化了具体操作,我们就不需要在具体的参数上进行 var builder = WebApplication.CreateBuilder(args); builder.Services.AddScoped<Person>(provider => new() { Id = 1, Name = "yi念之间", Sex = "Man" }); var app = builder.Build(); app.MapGet("/", (Person person) => $"Hello {person.Name}!"); app.Run(); 如果是混合使用的话,也可以不用指定具体的BindSource进行标记了,前提是这些值的名称在不同的绑定来源中是唯一的,这种感觉让我想到了刚开始学习MVC4.0的时候模型绑定的随意性,比如下面的例子 app.MapGet("/sayhello/{name}", (string name,int? age,Person person) => $"my name is {name},age {age}, sex {person.Sex}"); 上面示例的模型绑定参数来源可以是
不仅仅如此,它还支持更复杂的方式,这使得模型绑定更为灵活,比如以下示例 app.MapPost("/goods",(Goods goods, Person person) =>$"{person.Name}添加商品{goods.GName}成功"); 它的模型绑定的值来源可以是
当然如果你想让模型绑定的来源更清晰,或者就想指定具体参数的绑定来源那也是可以的,反正就是各种灵活,比如上面的示例改造一下,这样就可以显示声明 app.MapPost("/goods",([FromBody]Goods goods, [FromServices]Person person) =>$"{person.Name}添加商品{goods.GName}成功"); 很多时候我们可能通过定义类和方法的方式来声明Map相关方法的执行委托,这个时候呢依然可以进行灵活的模型绑定,而且可能你也发现了,直接通过lambda表达式的方式虽然支持可空类型,但是它不支持缺省参数,也就是咱们说的方法默认参数的形式,比如 app.MapPost("/goods", GoodsHandler.AddGoods); class GoodsHandler { public static string AddGoods(Goods goods, Person person, int age = 20) => $"{person.Name}添加商品{goods.GName}成功"; } 当然你也可以对AddGoods方法的参数进行显示的模型绑定处理,真的是十分的灵活 public static string AddGoods([FromBody] 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采用了一种新的方式来自定义模型绑定,这种方式是一种基于约定的方式,无需提前注册,也无需集成什么类或者实现什么接口,只需要在自定义的类中存在
接下来我们分别演示一下这两种方式的使用方法,首先是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方法确实存在一定的限制,不过操作起来比较简单,这个时候我们模拟请求
请求完成会得到如下结果
然后我们改造一下上面的例子使用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); } } 同样请求 输出结果 相信通过上面的其他示例演示,我们大概看到了一些在Minimal Api中的结果输出,总结起来其实可以分为三种情况
接下来简单演示几个例子来简单看一下具体是如何操作的,首先最简单的就是输出文本类型 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)); 还有一种是微软帮我们封装好的一种形式,即返回的是 //成功结果 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(); }); 上面我们也提到了 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, }; 亦或者 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, }; 当然我们也可以自定义 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>")); 这里需要注意的是,我们自定义的扩展方法一定是基于 总结本文我们主要是介绍了ASP.NET Core 6 Minimal API的常用的使用方式,相信大家对此也有了一定的了解,在.NET6中也是默认的项目方式,整体来说却是非常的简单、简洁、强大、灵活,不得不说Minimal API却是在很多场景都非常适用的。当然我也在其它地方看到过关于它的评价,褒贬不一吧,笔者认为,没有任何一种技术是银弹,存在即合理。如果你的项目够规范够合理,那么使用Minimal API绝对够用,如果不想用或者用不了也没关系,能实现你想要结果就好了,其实也没啥好评价的。 到此这篇关于简单聊下.NET6 Minimal API的使用方式的文章就介绍到这了,更多相关.NET6 Minimal API内容请搜索极客世界以前的文章或继续浏览下面的相关文章希望大家以后多多支持极客世界! |
请发表评论