首页 > 编程语言 >ASP.NET Core MiniAPI中 EndPoint相关

ASP.NET Core MiniAPI中 EndPoint相关

时间:2023-12-30 17:58:05浏览次数:35  
标签:MiniAPI Core ASP return string app Results fruit id

1.状态码返回之演化之路

1.1最基本的就是用Results或者TypedResults返回带有状态码的响应(可选Json响应体)

      app.MapGet("/fruit/{id}", (string id) =>
      {
          if (_fruit.TryGetValue(id, out Fruit fruit))
          {
              return Results.Ok(fruit);
          }
          else
          {
              return Results.NotFound();
          }
      });

            app.MapPost("/fruit/{id}", (string id, Fruit fruit) =>
            {
                if (_fruit.TryAdd(id, fruit))
                {
                    return TypedResults.Created($"/fruit/{id}", fruit);
                }
                else
                {
                    return Results.BadRequest(new { id = "A fruit with id already exists" });
                }
            });





可选响应体

app.MapGet("/fruit/{id}", (string id) =>
{
    if (_fruit.TryGetValue(id, out Fruit fruit))
    {
        return Results.Ok(fruit);
    }
    else
    {
        return Results.NotFound(new {id="暂无发现"});
    }
});

1.2 用Problem Details返回有用的错误。

1.1的方案有个小瑕疵,就是对于错误响应没有一个统一的格式,因此可以用**Problem Details"来有个统一的格式描述错误。
Problem Details有两个API:Results.Problem(TypedResults.Problem)Results.ValidationProblem(TypedResults.ValidationProblem)ProblemValidationProblem的区别就是前者是默认是500错误码,后者默认是400错误码,前者也可以填入参数statusCode来指明自定义错误码,如400,后者还需要填入Dictionary<string,string[]>类型的参数。

 app.MapGet("/fruit/{id}", (string id) =>
 {
     if (_fruit.TryGetValue(id, out Fruit fruit))
     {
         return Results.Ok(fruit);//或者TypedResults.Ok(fruit);
     }
     else
     {
         return Results.Problem("暂无",statusCode:404);
     }
 });

app.MapPost("/fruit/{id}", (string id, Fruit fruit) =>
{
    if (_fruit.TryAdd(id, fruit))
    {
        return TypedResults.Created($"/fruit/{id}", fruit);
    }
    else
    {
        return Results.ValidationProblem(new Dictionary<string, string[]>
        {
            ["id"] = new[] { "A fruit with this id already exists" },
        });
    }
});



可见,确实统一了格式!

1.3将所有错误转为Problem Details

在1.2中,我们仅仅是在我们可控的endpoint中使用ProblemValidationProblem,转为统一的Problem Details,问题是并不是所有的错误都仅发生在endpoint中,也可能发生在中间件中,也有可能是未知的异常。现在我们要将所有错误统一输出为Problem Detials。
错误分为异常和错误状态码两种:

1.3.1 将异常转为Problem Details

只需要builder.Services.AddProblemDetails()方法来注册服务,并app.UseExceptionHandler()即可.
以下是没有使用该方法及其反应:

app.MapGet("/error", void () => throw new NotImplementedException());

以下是使用该方法及其反应

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddProblemDetails();//注册服务
var app = builder.Build();
app.UseExceptionHandler();//使用异常处理中间件
app.MapGet("/error", void () => throw new NotImplementedException());

1.3.2 将错误状态码转为Problem Details

还是必须先注册builder.Services.AddProblemDetails(),然后使用app.UseStatusCodePages()
这样的话,如果任何进入该中间件的带有错误码的且无响应体的响应将会自动被加入Problem Details响应体.

builder.Services.AddProblemDetails();//注册服务
app.UseStatusCodePages();
app.MapGet("/fruit/{id}", (string id) =>
{
    if (_fruit.TryGetValue(id, out Fruit fruit))
    {
        return Results.Ok(fruit);
    }
    else
    {
        return Results.NotFound();
    }
});

2返回其他数据类型

  • Results.File()
  • Results.Byte()
  • Results.Stream()

3 endpoint filters

endpoint filters工作流程与中间件非常相似,都是流水线式请求进入,响应出来;都可以对请求进行短路;都可以进行logging,exception handle;
但也有非常明显的不同:中间件是对所有请求起作用的,endpoint filters,顾名思义,只对特定的请求起作用;endpoint filters可以访问到下一层级穿过来的result,而中间件不能。

class ValidationHelper
{
    internal static async ValueTask<object?> ValidateId(EndpointFilterInvocationContext context,EndpointFilterDelegate next)
    {
        var id = context.GetArgument<string>(0);
        if(String.IsNullOrEmpty(id) || !id.StartsWith("f"))
        {
            return Results.ValidationProblem(new Dictionary<string, string[]>
            {
                ["id"] = new[] { "Invalid format.Id must start with f" },
            });
        }
        return await next(context);
    }
}
app.MapGet("/fruit/{id}", (string id) => _fruit.TryGetValue(id, out var fruit) ?
TypedResults.Ok(fruit) : Results.Problem(statusCode: 404))
    .AddEndpointFilter(ValidationHelper.ValidateId)
    .AddEndpointFilter(async (context, next) =>
    {
        app.Logger.LogInformation("====Executing filter...");
        object? result = await next(context);
        app.Logger.LogInformation($"===Handler result:{result}");
        return result;
    });

直接短路:

f开头,不存在:

正常访问到:

将该Filter应用到post上

app.MapPost("/fruit/{id}", (string id, Fruit fruit) =>
{
    if (_fruit.TryAdd(id, fruit))
    {
        return TypedResults.Created($"/fruit/{id}", fruit);
    }
    else
    {
        return Results.BadRequest(new { id = "A fruit with id already exists" });
    }
}).AddEndpointFilter(ValidationHelper.ValidateId);

成功拦截!

3.1 Filter Factory

上面的Filter有个小瑕疵,就是严格依赖参数顺序,但是对于MapPost来说,参数顺序却是随意的,
比如app.MapPost("/fruit/{id}",(stirng id,Fruit fruit)=>{...}),和app.MapPost("/fruit/{id}",(Fruit fruit,string id)=>{...})都是一样的,
但对于Filter,就是致命的错误。
那么该怎么办呢?Filter Factory的产生就是为了解决这个困境。

class ValidationHelper
{
    internal static async ValueTask<object?> ValidateId(EndpointFilterInvocationContext context,EndpointFilterDelegate next)
    {
        var id = context.GetArgument<string>(0);
        if(String.IsNullOrEmpty(id) || !id.StartsWith("f"))
        {
            return Results.ValidationProblem(new Dictionary<string, string[]>
            {
                ["id"] = new[] { "Invalid format.Id must start with f" },
            });
        }
        return await next(context);
    }
    internal static EndpointFilterDelegate ValidateIdFactory(EndpointFilterFactoryContext context,EndpointFilterDelegate next)
    {
        ParameterInfo[] parameters = context.MethodInfo.GetParameters();
        int? idPosition = null;
        for(int i = 0; i < parameters.Length; i++)
        {
            if (parameters[i].Name=="id" && parameters[i].ParameterType == typeof(string))
            {
                idPosition = i;
                break;
            }
        }
        if (!idPosition.HasValue)
        {
            return next;
        }
        return async (invocationContext) =>
        {
            var id = invocationContext.GetArgument<string>(idPosition.Value);
            if(string.IsNullOrEmpty(id) || !id.StartsWith("f"))
            {
                return Results.ValidationProblem(new Dictionary<string, string[]>
                {
                    ["id"] = new[] {"Id must start with f"}, 
                });
            }
            return await next(invocationContext);
        };
    }
}

app.MapPost("/fruit/{id}", ( Fruit fruit, string id) =>
{
    if (_fruit.TryAdd(id, fruit))
    {
        return TypedResults.Created($"/fruit/{id}", fruit);
    }
    else
    {
        return Results.BadRequest(new { id = "A fruit with id already exists" });
    }
}).AddEndpointFilterFactory(ValidationHelper.ValidateIdFactory);

3.2 IEndpointFilter接口

每次手敲ValidationHelper中的方法,很恼火,那么用这个接口,可以充分利用vs的智能补全。

 class IdValidattionFilter : IEndpointFilter
 {
    //下面的async需要自己加上,不知道是不是vs智能补全的bug
     public async ValueTask<object?> InvokeAsync(EndpointFilterInvocationContext context, EndpointFilterDelegate next)
     {
         var id = context.GetArgument<string>(0);
         if (String.IsNullOrEmpty(id) || !id.StartsWith("f"))
         {
             return Results.ValidationProblem(new Dictionary<string, string[]>
             {
                 ["id"] = new[] { "Invalid format.Id must start with f" },
             });
         }
         return await next(context);
     }
 }

app.MapPost("/fruit/{id}", ( Fruit fruit, string id) =>
{
    if (_fruit.TryAdd(id, fruit))
    {
        return TypedResults.Created($"/fruit/{id}", fruit);
    }
    else
    {
        return Results.BadRequest(new { id = "A fruit with id already exists" });
    }
}).AddEndpointFilter< IdValidattionFilter>();//不传实例,只传类型就行。

4.用路由组route group 来组织API

MapGet("/fruit/{id}",(string id)=>{...});
MapPost("/fruit/{id}",(string id)=>{...});
MapPut("/fruit/{id}",(string id)=>{...});
MapDelete("/fruit/{id}",(string id)=>{...});

对于以上4个endpoint,都需要验证id,我们可以用filter来验证,但是还要一个一个的添加,也还是非常繁琐,那么route group就是为了应对这种情况。

 var builder = WebApplication.CreateBuilder(args);
 builder.Services.AddProblemDetails();//注册服务
 var app = builder.Build();
 app.UseExceptionHandler();//使用异常处理中间件
 app.UseStatusCodePages();
 var _fruit=new ConcurrentDictionary<string , Fruit>()
 {
   
 };

RouteGroupBuilder fruitApi = app.MapGroup("/fruit");//MapGrtoup可以嵌套,即MapGroup(xxx).MapGroup(xxxx).MapGroup()...
fruitApi.MapGet("/",()=>_fruit);
RouteGroupBuilder fruiApiWithValidation = fruitApi.AddEndpointFilterFactory(ValidationHelper.ValidateIdFactory);
fruiApiWithValidation.MapGet("/{id}", (string id) => _fruit.TryGetValue(id, out var fruit) ?
TypedResults.Ok(fruit) : Results.Problem(statusCode: 404))
    //.AddEndpointFilter(ValidationHelper.ValidateId)
    .AddEndpointFilter(async (context, next) =>
    {
        app.Logger.LogInformation("====Executing filter...");
        object? result = await next(context);
        app.Logger.LogInformation($"===Handler result:{result}");
        return result;
    });

 fruiApiWithValidation.MapPost("/{id}", ( Fruit fruit, string id) =>
 {
     if (_fruit.TryAdd(id, fruit))
     {
         return TypedResults.Created($"/fruit/{id}", fruit);
     }
     else
     {
         return Results.BadRequest(new { id = "A fruit with id already exists" });
     }
 });

 fruiApiWithValidation.MapPut("/{id}", (string id, Fruit fruit) =>
 {
     _fruit[id] = fruit;
     return Results.NoContent();
 });
 fruiApiWithValidation.MapDelete("/{id}", (string id) =>
 {
     _fruit.TryRemove(id, out _);
     return Results.NoContent();
 });

 app.MapGet("/teapot", (HttpResponse res) =>
 {
     res.StatusCode = 428;
     res.ContentType = MediaTypeNames.Text.Plain;
     return res.WriteAsync("tea pot!");
 });
 app.MapGet("/error", void () => throw new NotImplementedException());
 app.Run();

标签:MiniAPI,Core,ASP,return,string,app,Results,fruit,id
From: https://www.cnblogs.com/johnyang/p/17936593.html

相关文章

  • .Net Core WebAPI 缓存
    Asp.NetCoreWebAPI缓存 一、缓存缓存指在中间层中存储数据的行为,该行为可使后续数据检索更快。从概念上讲,缓存是一种性能优化策略和设计考虑因素。缓存可以显著提高应用性能,方法是提高不常更改(或检索成本高)的数据的就绪性。二、RFC9111在最新的缓存控制规范文件RFC91......
  • ASP.NET Core 6(.NET 6) Program.cs中使用读取appsettings.json配置文件
    ​ 在ASP.NETCore6(.NET6)中,可以使用Json格式的appsettings.json配置文件来配置应用程序,用于存储应用程序的配置信息,方便我们灵活的配置应用程序。本文主要介绍Program.cs中,使用读取appsettings.json配置文件的方法,以及相关的示例代码。1、通过配置实体类的方式1)配置实体......
  • .net core 单元测试项目搭建
    背景和目的为了提高系统稳定性,通常我们有两方面的计划:黑盒测试:自动化测试,以接口来主体,通过控制入参的形式,检验出参,来模拟用户在线上的实际业务;(可以覆盖绝大部分的业务)白盒测试:单元测试,以关键逻辑方法为主体,通过控制入参的形式,检验数据变化,站在开发的角度上来模拟实际调用(可以......
  • Taurus .Net Core 微服务开源框架:Admin 插件【4-8】 - 配置管理-Mvc【Plugin-Limit 接
    前言:继上篇:Taurus.NetCore微服务开源框架:Admin插件【4-7】-配置管理-Mvc【Plugin-Metric接口调用次数统计】本篇继续介绍下一个内容:1、系统配置节点:Mvc- Plugin-Limit接口访问限制、IP限制、Ack限制:配置界面如下:限制目前提供以下三个类别的限制:1、Rate访......
  • 在不受支持的 Mac 上安装 macOS Sonoma (OpenCore Legacy Patcher)
    在不受支持的Mac上安装macOSSonoma、Ventura、Monterey、BigSur(OpenCoreLegacyPatcher)InstallmacOSonunsupportedMacs作者主页:sysin.orgmacOSSonoma正式版已发布,OpenCoreLegacyPatcherv1.0.0版本已支持。随着OpenCoreLegacyPatcher1.0.0的发布,OLP项目组......
  • linux下java调用netcore程序
    代码备份仅供参考自述文件#JavaCallCSharpJavacallC#libbuildwith.NETCORE2.0viaC++aswraperThecodeisbasedon[examplefromcoreCLR](https://github.com/dotnet/coreclr/tree/master/src/coreclr/hosts/unixcoreruncommon)JavausingJNItocallC++......
  • Linux下netcore调用java代码
    代码备份,仅供参考自述文件#CSharpCallJavaC#invokeJavaviaC++asawraper.C#invokeC++viaP/invoke.C++startsaJVMtoruntheJavacode.C#codeshouldbecompiledin.NETcore2.0YoushouldedittheMakefiletosetthePathofJavaSDKexpor......
  • Asp.net WebApi Swagger Tag 标记分组归纳显示Api接口路由
    官方文档说明地址https://swagger.io/docs/specification/2-0/grouping-operations-with-tags/创建一个自定义的特性类publicclassControllerGroupAttribute:Attribute{publicControllerGroupAttribute(stringgroupName){if......
  • ASP.NET Core 授权一(简单的Cookie)
    Cookie1.HTTP无连接无状态,Cookie和Session就是解决此问题。2.客户端向服务器端发送一个请求的时,服务端向客户端发送一个Cookie然后浏览器将Cookie保存,之后每次HTTP请求浏览器都会将Cookie发送给服务器端,需要衡量把什么数据放到cookie中,很多数据并不是每次请求都需要发给服务端,......
  • 试试这 6 个小技巧,提升 EF Core 性能
    EntityFrameWork(简称EF)以面向对象的方式操作数据库给开发人员带来了很大的便利性,但其性能问题从面世以来就一直就被广大的.NET生态开发技术人员所吐槽,然而,它真的那么不堪使用吗?试试下面这6个小技巧,瞬间极大提升EFCore性能:AsNoTracking在项目开发的时候,如果查询出来......