前言

随着.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的程序,代码如下

  1. var app = WebApplication.Create(args);
  2. app.MapGet("/", () => "Hello World");
  3. app.Run();

是的你没有看错,仅仅这样运行起来就可以,默认监听的 http://localhost:5000https://localhost:5001,所以直接在浏览器输入http://localhost:5000地址就可以看到浏览器输出Hello World字样。

更改监听地址

如果你想更改它监听的服务端口可以使用如下的方式进行更改

  1. var app = WebApplication.Create(args);
  2. app.MapGet("/", () => "Hello World");
  3. app.Run("http://localhost:6666");

如果想同时监听多个端口的话,可以使用如下的方式

  1. var app = WebApplication.Create(args);
  2. app.Urls.Add("http://localhost:6666");
  3. app.Urls.Add("http://localhost:8888");
  4. app.MapGet("/", () => "Hello World");
  5. app.Run();

或者是直接通过环境变量的方式设置监听信息,设置环境变量ASPNETCORE_URLS的值为完整的监听URL地址,这样的话就可以直接省略了在程序中配置相关信息了

  1. ASPNETCORE_URLS=http://localhost:6666

如果设置多个监听的URL地址的话可以在多个地址之间使用分号;隔开多个值

  1. ASPNETCORE_URLS=http://localhost:6666;https://localhost:8888

如果想监听本机所有Ip地址则可以使用如下方式

  1. var app = WebApplication.Create(args);
  2. app.Urls.Add("http://*:6666");
  3. app.Urls.Add("http://+:8888");
  4. app.Urls.Add("http://0.0.0.0:9999");
  5. app.MapGet("/", () => "Hello World");
  6. app.Run();

同样的也可以使用添加环境变量的方式添加监听地址

  1. ASPNETCORE_URLS=http://*:6666;https://+:8888;http://0.0.0.0:9999

日志操作

日志操作也是比较常用的操作,在Minimal API中微软干脆把它提出来,直接简化了操作,如下所示

  1. var builder = WebApplication.CreateBuilder(args);
  2. builder.Logging.AddJsonConsole();
  3. var app = builder.Build();
  4. app.Logger.LogInformation("读取到的配置信息:{content}", builder.Configuration.GetSection("consul").Get<ConsulOption>());
  5. app.Run();

基础环境配置

无论我们在之前的.Net Core开发或者现在的.Net6开发都有基础环境的配置,它包括 ApplicationNameContentRootPath EnvironmentName相关,不过在Minimal API中,可以通过统一的方式去配置

  1. var builder = WebApplication.CreateBuilder(new WebApplicationOptions
  2. {
  3. ApplicationName = typeof(Program).Assembly.FullName,
  4. ContentRootPath = Directory.GetCurrentDirectory(),
  5. EnvironmentName = Environments.Staging
  6. });
  7. Console.WriteLine($"应用程序名称: {builder.Environment.ApplicationName}");
  8. Console.WriteLine($"环境变量: {builder.Environment.EnvironmentName}");
  9. Console.WriteLine($"ContentRoot目录: {builder.Environment.ContentRootPath}");
  10. var app = builder.Build();

或者是通过环境变量的方式去配置,最终实现的效果都是一样的

  • ASPNETCORE_ENVIRONMENT
  • ASPNETCORE_CONTENTROOT
  • ASPNETCORE_APPLICATIONNAME

主机相关设置

我们在之前的.Net Core开发模式中,程序的启动基本都是通过构建主机的方式,比如之前的Web主机或者后来的泛型主机,在Minimal API中同样可以进行这些操作,比如我们模拟一下之前泛型主机配置Web程序的方式

  1. var builder = WebApplication.CreateBuilder(args);
  2. builder.Host.ConfigureDefaults(args).ConfigureWebHostDefaults(webBuilder =>
  3. {
  4. webBuilder.UseStartup<Startup>();
  5. });
  6. var app = builder.Build();

如果只是配置Web主机的话Minimal API还提供了另一种更直接的方式,如下所示

  1. var builder = WebApplication.CreateBuilder(args);
  2. builder.WebHost.UseStartup<Startup>();
  3. builder.WebHost.UseWebRoot("webroot");
  4. var app = builder.Build();

默认容器替换

很多时候我们在使用IOC的时候会使用其他三方的IOC框架,比如大家耳熟能详的Autofac,我们之前也介绍过其本质方式就是使用UseServiceProviderFactory中替换容器的注册和服务的提供,在Minimal API中可以使用如下的方式去操作

  1. var builder = WebApplication.CreateBuilder(args);
  2. builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory());
  3. //之前在Startup中配置ConfigureContainer可以使用如下方式
  4. builder.Host.ConfigureContainer<ContainerBuilder>(builder => builder.RegisterModule(new MyApplicationModule()));
  5. var app = builder.Build();

中间件相关

相信大家都已经仔细看过了WebApplication.CreateBuilder(args).Build()通过这种方式构建出来的是一个WebApplication类的实例,而WebApplication正是实现了 IApplicationBuilder接口。所以其本质还是和我们之前使用Startup中的Configure方法的方式是一致的,比如我们配置一个Swagger程序为例

  1. var builder = WebApplication.CreateBuilder(args);
  2. builder.Services.AddControllers();
  3. builder.Services.AddEndpointsApiExplorer();
  4. builder.Services.AddSwaggerGen();
  5. var app = builder.Build();
  6. //判断环境变量
  7. if (app.Environment.IsDevelopment())
  8. {
  9. //异常处理中间件
  10. app.UseDeveloperExceptionPage();
  11. app.UseSwagger();
  12. app.UseSwaggerUI();
  13. }
  14. //启用静态文件
  15. app.UseStaticFiles();
  16. app.UseAuthorization();
  17. app.MapControllers();
  18. app.Run();

常用的中间件配置还是和之前是一样的,因为本质都是IApplicationBuilder的扩展方法,我们这里简单列举一下

中间件名称 描述 API
Authentication 认证中间件 app.UseAuthentication()
Authorization 授权中间件. app.UseAuthorization()
CORS 跨域中间件. app.UseCors()
Exception Handler 全局异常处理中间件. app.UseExceptionHandler()
Forwarded Headers 代理头信息转发中间件. app.UseForwardedHeaders()
HTTPS Redirection Https重定向中间件. app.UseHttpsRedirection()
HTTP Strict Transport Security (HSTS) 特殊响应头的安全增强中间件. app.UseHsts()
Request Logging HTTP请求和响应日志中间件. app.UseHttpLogging()
Response Caching 输出缓存中间件. app.UseResponseCaching()
Response Compression 响应压缩中间件. app.UseResponseCompression()
Session Session中间件 app.UseSession()
Static Files 静态文件中间件. app.UseStaticFiles(), app.UseFileServer()
WebSockets WebSocket支持中间件. app.UseWebSockets()

请求处理

我们可以使用WebApplication中的Map{HTTPMethod}相关的扩展方法来处理不同方式的Http请求,比如以下示例中处理Get、Post、Put、Delete相关的请求

  1. app.MapGet("/", () => "Hello GET");
  2. app.MapPost("/", () => "Hello POST");
  3. app.MapPut("/", () => "Hello PUT");
  4. app.MapDelete("/", () => "Hello DELETE");

如果想让一个路由地址可以处理多种Http方法的请求可以使用MapMethods方法,如下所示

  1. app.MapMethods("/multiple", new[] { "GET", "POST","PUT","DELETE" }, (HttpRequest req) => $"Current Http Method Is {req.Method}" );

通过上面的示例我们不仅看到了处理不同Http请求的方式,还可以看到Minimal Api可以根据委托的类型自行推断如何处理请求,比如上面的示例,我们没有写Response Write相关的代码,但是输出的却是委托里的内容,因为我们上面示例中的委托都满足Func<string>的形式,所以Minimal Api自动处理并输出返回的信息,其实只要满足委托类型的它都可以处理,接下来咱们来简单一下,首先是本地函数的形式

  1. static string LocalFunction() => "This is local function";
  2. app.MapGet("/local-fun", LocalFunction);

还可以是类的实例方法

  1. HelloHandler helloHandler = new HelloHandler();
  2. app.MapGet("/instance-method", helloHandler.Hello);
  3. class HelloHandler
  4. {
  5. public string Hello()
  6. {
  7. return "Hello World";
  8. }
  9. }

亦或者是类的静态方法

  1. app.MapGet("/static-method", HelloHandler.SayHello);
  2. class HelloHandler
  3. {
  4. public static string SayHello(string name)
  5. {
  6. return $"Hello {name}";
  7. }
  8. }

其实本质都是一样的,那就是将他们转换为可执行的委托,无论什么样的形式,能满足委托的条件即可。

路由约束

Minimal Api还支持在对路由规则的约束,这个和我们之前使用UseEndpoints的方式类似,比如我约束路由参数只能为整型,如果不满足的话会返回404

  1. app.MapGet("/users/{userId:int}", (int userId) => $"user id is {userId}");
  2. app.MapGet("/user/{name:length(20)}", (string name) => $"user name is {name}");

经常使用的路由约束还有其他几个,也不是很多大概有如下几种,简单的列一下表格

限制 示例 匹配示例 说明
int {id:int} 123456789, -123456789 匹配任何整数
bool {active:bool} true, false 匹配 truefalse. 忽略大小写
datetime {dob:datetime} 2016-12-31, 2016-12-31 7:32pm 匹配满足DateTime类型的值
decimal {price:decimal} 49.99, -1,000.01 匹配满足 decimal类型的值
double {height:double} 1.234, -1,001.01e8 匹配满足 double 类型的值
float {height:float} 1.234, -1,001.01e8 匹配满足 float 类型的值
guid {id:guid} CD2C1638-1638-72D5-1638-DEADBEEF1638 匹配满足Guid类型的值
long {ticks:long} 123456789, -123456789 匹配满足 long 类型的值
minlength(value) {username:minlength(4)} KOBE 字符串长度必须是4个字符
maxlength(value) {filename:maxlength(8)} CURRY 字符串长度不能超过8个字符
length(length) {filename:length(12)} somefile.txt 字符串的字符长度必须是12个字符
length(min,max) {filename:length(8,16)} somefile.txt 字符串的字符长度必须介于8和l6之间
min(value) {age:min(18)} 20 整数值必须大于18
max(value) {age:max(120)} 119 整数值必须小于120
range(min,max) {age:range(18,120)} 100 整数值必须介于18和120之间
alpha {name:alpha} Rick 字符串必须由一个或多个a-z的字母字符组成,且不区分大小写。
regex(expression) {ssn:regex(^\\d{{3}}-\\d{{2}}-\\d{{4}}$)} 123-45-6789 字符串必须与指定的正则表达式匹配。
required {name:required} JAMES 请求信息必须包含该参数

模型绑定

在我们之前使用ASP.NET Core Controller方式开发的话,模型绑定是肯定会用到的,它的作用就是简化我们解析Http请求信息也是MVC框架的核心功能,它可以将请求信息直接映射成c#的简单类型或者POCO上面。在Minimal Api的Map{HTTPMethod}相关方法中同样可以进行丰富的模型绑定操作,目前可以支持的绑定源有如下几种

  • Route(路由参数)
  • QueryString
  • Header
  • Body(比如JSON)
  • Services(即通过IServiceCollection注册的类型)
  • 自定义绑定
绑定示例

接下来我们首先看一下绑定路由参数

  1. app.MapGet("/sayhello/{name}", (string name) => $"Hello {name}");

还可以使用路由和querystring的混用方式

  1. app.MapGet("/sayhello/{name}", (string name,int? age) => $"my name is {name},age {age}");

这里需要注意的是,我的age参数加了可以为空的标识,如果不加的话则必须要在url的请求参数中传递age参数,否则将报错,这个和我们之前的操作还是有区别的。

具体的类也可以进行模型绑定,比如咱们这里定义了名为Goods的POCO进行演示

  1. app.MapPost("/goods",(Goods goods)=>$"商品{goods.GName}添加成功");
  2. class Goods
  3. {
  4. public int GId { get; set; }
  5. public string GName { get; set; }
  6. public decimal Price { get; set; }
  7. }

需要注意的是HTTP方法GET、HEAD、OPTIONS、DELETE将不会从body进行模型绑定,如果需要在Get请求中获取Body信息,可以直接从HttpRequest中读取它。

如果我们需要使用通过IServiceCollection注册的具体实例,可以以通过模型绑定的方式进行操作(很多人喜欢叫它方法注入,但是严格来说却是是通过定义模型绑定的相关操作实现的),而且还简化了具体操作,我们就不需要在具体的参数上进行FromServicesAttribute标记了

  1. var builder = WebApplication.CreateBuilder(args);
  2. builder.Services.AddScoped<Person>(provider => new() { Id = 1, Name = "yi念之间", Sex = "Man" });
  3. var app = builder.Build();
  4. app.MapGet("/", (Person person) => $"Hello {person.Name}!");
  5. app.Run();

如果是混合使用的话,也可以不用指定具体的BindSource进行标记了,前提是这些值的名称在不同的绑定来源中是唯一的,这种感觉让我想到了刚开始学习MVC4.0的时候模型绑定的随意性,比如下面的例子

  1. app.MapGet("/sayhello/{name}", (string name,int? age,Person person) => $"my name is {name},age {age}, sex {person.Sex}");

上面示例的模型绑定参数来源可以是

参数 绑定来源
name 路由参数
age querystring
person 依赖注入

不仅仅如此,它还支持更复杂的方式,这使得模型绑定更为灵活,比如以下示例

  1. app.MapPost("/goods",(Goods goods, Person person) =>$"{person.Name}添加商品{goods.GName}成功");

它的模型绑定的值来源可以是

参数 绑定来源
goods body里的json
person 依赖注入

当然如果你想让模型绑定的来源更清晰,或者就想指定具体参数的绑定来源那也是可以的,反正就是各种灵活,比如上面的示例改造一下,这样就可以显示声明

  1. app.MapPost("/goods",([FromBody]Goods goods, [FromServices]Person person) =>$"{person.Name}添加商品{goods.GName}成功");

很多时候我们可能通过定义类和方法的方式来声明Map相关方法的执行委托,这个时候呢依然可以进行灵活的模型绑定,而且可能你也发现了,直接通过lambda表达式的方式虽然支持可空类型,但是它不支持缺省参数,也就是咱们说的方法默认参数的形式,比如

  1. app.MapPost("/goods", GoodsHandler.AddGoods);
  2. class GoodsHandler
  3. {
  4. public static string AddGoods(Goods goods, Person person, int age = 20) => $"{person.Name}添加商品{goods.GName}成功";
  5. }

当然你也可以对AddGoods方法的参数进行显示的模型绑定处理,真的是十分的灵活

  1. public static string AddGoods([FromBody] Goods goods, [FromServices] Person person, [FromQuery]int age = 20) => $"{person.Name}添加商品{goods.GName}成功";

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

  1. 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方法

  1. app.MapGet("/address/getarray",(Address address) => address.Addresses);
  2. public class Address
  3. {
  4. public List<string>? Addresses { get; set; }
  5. public static bool TryParse(string? addressStr, IFormatProvider? provider, out Address? address)
  6. {
  7. var addresses = addressStr?.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
  8. if (addresses != null && addresses.Any())
  9. {
  10. address = new Address { Addresses = addresses.ToList() };
  11. return true;
  12. }
  13. address = new Address();
  14. return false;
  15. }
  16. }

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

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

请求完成会得到如下结果

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

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

  1. app.MapGet("/address/getarray",(Address address) => address.Addresses);
  2. public class Address
  3. {
  4. public List<string>? Addresses { get; set; }
  5. public static ValueTask<Address?> BindAsync(HttpContext context, ParameterInfo parameter)
  6. {
  7. string addressStr = context.Request.Query["address"];
  8. var addresses = addressStr?.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
  9. Address address = new();
  10. if (addresses != null && addresses.Any())
  11. {
  12. address.Addresses = addresses.ToList();
  13. return ValueTask.FromResult<Address?>(address);
  14. }
  15. return ValueTask.FromResult<Address?>(address);
  16. }
  17. }

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

输出结果

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

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

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

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

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

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

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

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

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

  1. public static IResult File(byte[] fileContents,string? contentType = null,
  2. string? fileDownloadName = null,
  3. bool enableRangeProcessing = false,
  4. DateTimeOffset? lastModified = null,
  5. EntityTagHeaderValue? entityTag = null)
  6. => new FileContentResult(fileContents, contentType)
  7. {
  8. FileDownloadName = fileDownloadName,
  9. EnableRangeProcessing = enableRangeProcessing,
  10. LastModified = lastModified,
  11. EntityTag = entityTag,
  12. };

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

  1. public static IResult Json(object? data, JsonSerializerOptions? options = null, string? contentType = null, int? statusCode = null)
  2. => new JsonResult
  3. {
  4. Value = data,
  5. JsonSerializerOptions = options,
  6. ContentType = contentType,
  7. StatusCode = statusCode,
  8. };

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

  1. static class ResultsExtensions
  2. {
  3. //基于IResultExtensions写扩展方法
  4. public static IResult Html(this IResultExtensions resultExtensions, string html)
  5. {
  6. ArgumentNullException.ThrowIfNull(resultExtensions, nameof(resultExtensions));
  7. //自定义的HtmlResult是IResult的实现类
  8. return new HtmlResult(html);
  9. }
  10. }
  11. class HtmlResult:IResult
  12. {
  13. //用于接收html字符串
  14. private readonly string _html;
  15. public HtmlResult(string html)
  16. {
  17. _html = html;
  18. }
  19. /// <summary>
  20. /// 在该方法写自己的输出逻辑即可
  21. /// </summary>
  22. /// <returns></returns>
  23. public Task ExecuteAsync(HttpContext httpContext)
  24. {
  25. httpContext.Response.ContentType = MediaTypeNames.Text.Html;
  26. httpContext.Response.ContentLength = Encoding.UTF8.GetByteCount(_html);
  27. return httpContext.Response.WriteAsync(_html);
  28. }
  29. }

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

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

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

总结

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

欢迎扫码关注我的公众号

简单聊下.NET6 Minimal API的使用方式的更多相关文章

  1. 简单聊下IO复用

    没图,不分析API Java中IO API的发展:Socket -> SocketChannel -> AsynchronousSocketChannelServerSocket -> ...

  2. 简单聊下Unicode和UTF-8

    今晚听同事分享提到这个,简单总结下. ## Unicode字符集 Unicode的出现是因为ASCII等其他编码码不够用了,比如ASCII是英语为母语的人发明的,只要一个字节8位就能够表示26个英文字 ...

  3. 【.NET6】gRPC服务端和客户端开发案例,以及minimal API服务、gRPC服务和传统webapi服务的访问效率大对决

    前言:随着.Net6的发布,Minimal API成了当下受人追捧的角儿.而这之前,程序之间通信效率的王者也许可以算得上是gRPC了.那么以下咱们先通过开发一个gRPC服务的教程,然后顺势而为,再接着 ...

  4. 【.NET 6】使用.NET 6开发minimal api以及依赖注入的实现、VS2022热重载和自动反编译功能的演示

    前言: .net 6 LTS版本发布已经有若干天了.此处做一个关于使用.net 6 开发精简版webapi(minimal api)的入门教程,以及VS2022 上面的两个强大的新技能(热重载.代码自 ...

  5. .Net Minimal Api 介绍

    Minimal API是.Net 6中新增的模板,借助C# 10的一些特性以最少的代码运行一个Web服务.本文脱离VS通过VS Code,完成一个简单的Minimal Api项目的开发. 创建项目 随 ...

  6. 拿nodejs快速搭建简单Oauth认证和restful API server攻略

    拿nodejs快速搭建简单Oauth认证和restful API server攻略:http://blog.csdn.net/zhaoweitco/article/details/21708955 最 ...

  7. 汇编Ring 3下实现 HOOK API

    [文章标题]汇编ring3下实现HOOK API [文章作者]nohacks(非安全,hacker0058) [作者主页]hacker0058.ys168.com [文章出处]看雪论坛(bbs.ped ...

  8. 简单了解下Dubbo

    1. Dubbo是什么? Dubbo是一个分布式服务框架,致力于提供高性能和透明化的RPC远程服务调用方案,以及SOA服务治理方案.简单的说,dubbo就是个服务框架,如果没有分布式的需求,其实是不需 ...

  9. Gin实战:Gin+Mysql简单的Restful风格的API(二)

    上一篇介绍了Gin+Mysql简单的Restful风格的API,但代码放在一个文件中,还不属于restful风格,接下来将进行进一步的封装. 目录结构 ☁ gin_restful2 tree . ├─ ...

随机推荐

  1. Arthas 进阶教程

    Arthas 进阶教程 启动math-game 下载demo-arthas-spring-boot.jar,再用java -jar命令启动: wget https://github.com/hengy ...

  2. 如果你还不知道Apache Zookeeper?你凭什么拿大厂Offer!!

    很多同学或多或少都用到了Zookeeper,并知道它能实现两个功能 配置中心,实现表分片规则的统一配置管理 注册中心,实现sharding-proxy节点的服务地址注册 那么Zookeeper到底是什 ...

  3. 什么,你还使用 webpack?别人都在用 vite 搭建项目了

    一.vite 到底是干嘛的? vite 实际上就是一个面向现代浏览器,基于 ES module 实现了一个更轻快的项目构建打包工具. vite 是法语中轻快的意思. vite 的特点: 1.轻快的冷服 ...

  4. 谈谈BEM规范(含代码)

    css规范之BEM规范 前言 引用一句经典名言在编程的世界里其中一件最难的事情就是命名,不管是设计到编程语言还是标记语言都会有命名的需求.今天聊的就是关于css的命名规范的发展过程以及演变. 命名的发 ...

  5. Beta阶段性总结

    1.题士开发总结 2.反思 2.1 Issue管理 从0522敲定各个功能的API后,团队成员及时沟通,积极开发,但由于开发过程没能有效体现在issue上(如未能及时在issue上形成记录,功能开发完 ...

  6. the Agiles Scrum Meeting 6

    会议时间:2020.4.14 20:00 1.每个人的工作 今天已完成的工作 增量组:开发广播正文展开收起功能 issues:增量组:广播正文展开收起功能实现 完善组:修复冲刺部分的bug issue ...

  7. 人人都写过的5个Bug!

    大家好,我是良许. 计算机专业的小伙伴,在学校期间一定学过 C 语言.它是众多高级语言的鼻祖,深入学习这门语言会对计算机原理.操作系统.内存管理等等底层相关的知识会有更深入的了解,所以我在直播的时候, ...

  8. [CSP-S2021] 回文

    链接: P7915 题意: 给出一个长度为 \(2n\) 的序列 \(a\),其中 \(1\sim n\) 每个数出现了 2 次.有 L,R 两种操作分别是将 \(a\) 的开头或末尾元素加入到初始为 ...

  9. OpenWrt编译报错:Package airfly_receiver is missing dependencies for the following libraries

    今天在编译一个OpenWrt测试用例的时候出现报错 Package airfly_receiver is missing dependencies for the following librarie ...

  10. Codeforces Round #738 (Div. 2) D2题解

    D2. Mocha and Diana (Hard Version) 至于D1,由于范围是1000,我们直接枚举所有的边,看看能不能加上去就行,复杂度是\(O(n^2logn)\).至于\(n\)到了 ...