vipwan / biwen.quickapi Goto Github PK
View Code? Open in Web Editor NEWBiwen.QuickApi, WebApi,REPR
License: MIT License
Biwen.QuickApi, WebApi,REPR
License: MIT License
public class HelloApiRequest : BaseRequest<HelloApiRequest>
{
public string? Name { get; set; } = "default";
/// <summary>
/// 别名测试
/// </summary>
[AliasAs("a")]
public string? Alias { get; set; }
public HelloApiRequest()
{
RuleFor(x => x.Name).NotNull().Length(5, 10);
}
}
如果Req的属性配置的别名 级别为最高绑定特征
[Authorize]
[Authorize(policy: "admin")]
[QuickApi("an-auth")]
[QuickApiSummary("使用特性标记需要登录", "使用特性标记需要登录")]
public class AuthorizationTestApi : BaseQuickApiWithoutRequest<ContentResponse>
{
public override Task<ContentResponse> ExecuteAsync(EmptyRequest request)
{
return Task.FromResult("登录成功的请求!".AsRspOfContent());
}
public override RouteHandlerBuilder HandlerBuilder(RouteHandlerBuilder builder)
{
builder.WithGroupName("admin");
return base.HandlerBuilder(builder);
}
}
[AllowAnonymous]
[QuickApi("an-anonymous")]
[QuickApiSummary("使用特性标记可以匿名", "使用特性标记可以匿名")]
public class AllowAnonymousTestApi : BaseQuickApiWithoutRequest<ContentResponse>
{
public override Task<ContentResponse> ExecuteAsync(EmptyRequest request)
{
return Task.FromResult("无效登录的请求!".AsRspOfContent());
}
public override RouteHandlerBuilder HandlerBuilder(RouteHandlerBuilder builder)
{
builder.WithGroupName("admin");
return base.HandlerBuilder(builder);
}
}
该方式等效于 :
public abstract class BaseAdminApi<Req, Rsp> : BaseQuickApi<Req, Rsp> where Req : BaseRequest<Req>, new() where Rsp : BaseResponse
{
public override Task<Rsp> ExecuteAsync(Req request)
{
throw new NotImplementedException();
}
public override RouteHandlerBuilder HandlerBuilder(RouteHandlerBuilder builder)
{
//需要Admin的Policy才能访问
builder.RequireAuthorization("admin");
return base.HandlerBuilder(builder);
}
}
using Biwen.QuickApi.Events;
using Microsoft.AspNetCore.Mvc;
namespace Biwen.QuickApi.DemoWeb.Apis
{
public class MyEvent : BaseRequest<MyEvent>,IEvent
{
[FromQuery]
public string? Message { get; set; }
}
public class MyEventHandler : EventSubscriber<MyEvent>
{
private readonly ILogger<MyEventHandler> _logger;
public MyEventHandler(ILogger<MyEventHandler> logger)
{
_logger = logger;
}
public override Task HandleAsync(MyEvent @event, CancellationToken ct)
{
_logger.LogInformation($"msg 2 : {@event.Message}");
return Task.CompletedTask;
}
}
/// <summary>
/// 更早执行的Handler
/// </summary>
public class MyEventHandler2 : EventSubscriber<MyEvent>
{
private readonly ILogger<MyEventHandler> _logger;
public MyEventHandler2(ILogger<MyEventHandler> logger)
{
_logger = logger;
}
public override Task HandleAsync(MyEvent @event, CancellationToken ct)
{
_logger.LogInformation($"msg 1 : {@event.Message}");
return Task.CompletedTask;
}
}
/// <summary>
/// 抛出异常的Handler
/// </summary>
[EventSubscriber(Order =-2,ThrowIfError =false)]
public class MyEventHandler3 : EventSubscriber<MyEvent>
{
private readonly ILogger<MyEventHandler> _logger;
public MyEventHandler3(ILogger<MyEventHandler> logger)
{
_logger = logger;
}
public override Task HandleAsync(MyEvent @event, CancellationToken ct)
{
throw new Exception("error");
}
}
[QuickApi("event")]
public class EventApi : BaseQuickApi<MyEvent>
{
public override async ValueTask<IResultResponse> ExecuteAsync(MyEvent request)
{
//publish event
await PublishAsync(request);
return IResultResponse.Content("send event");
}
}
}
比如你可以这样定义Req
public class HelloApiRequest : BaseRequest<HelloApiRequest>
{
/// <summary>
/// DataAnnotations内建特性测试
/// </summary>
[Description("DataAnnotations内建特性 测试")]
[StringLength(12,MinimumLength =6)]
[EmailAddress]
[Required("Department是必须的")]
public string Department { get; set; }
public HelloApiRequest()
{
RuleFor(x => x.UserName).EmailAddress();//要求邮箱
}
}
//Req:
{
"Department": null,
"UserName": "vipwan",
"Password": "p234565"
}
//Rsp会得到一下报错
{
"type": "https://tools.ietf.org/html/rfc9110#section-15.5.1",
"title": "One or more validation errors occurred.",
"status": 400,
"errors": {
"UserName": [
"'User Name' 不是有效的电子邮件地址。"
],
"Department": [
"Department是必须的"
]
}
}
提供一个接口:
/// <summary>
/// Group HanderBuilder
/// </summary>
public interface IQuickApiGroupRouteBuilder
{
/// <summary>
/// 分组
/// </summary>
string Group { get; }
RouteGroupBuilder Builder(RouteGroupBuilder routeBuilder);
/// <summary>
/// 执行排序
/// </summary>
int Order { get; }
}
你可以很方便的拓展:
//当前模拟给所有 Group为空的QuickApi加上 Tag "Def"
public class DefaultGroupRouteBuilder : IQuickApiGroupRouteBuilder
{
public string Group => string.Empty;
public int Order => 1;
public RouteGroupBuilder Builder(RouteGroupBuilder routeBuilder)
{
routeBuilder.WithTags("Def");
return routeBuilder;
}
}
// 最后注册他
builder.Services.AddBiwenQuickApiGroupRouteBuilder<DefaultGroupRouteBuilder>();
[Description("比如tags=hello&tags=world")]
[FromQuery]
public string[]? Tags { get; set; }
[Description("querystring比如?member={\"id\":\"123\",\"userName\":\"vipwan\"}")]
[FromQuery(Name = "member")]
public Member CurrentMember { get; set; }
[Description("header比如{\"id\":\"123\",\"userName\":\"vipwan\"}")]
[FromHeader(Name = "headmember")]
public Member CurrentMemberFromHeader { get; set; }
public record Member(string Id,string UserName);
//并且支持属性多级的校验
public HelloApiRequest()
{
RuleFor(x=>x.CurrentMember.UserName).EmailAddress();
//请注意多级验证务必添加When表达式
RuleFor(x=>x.CurrentMemberFromHeader.UserName).EmailAddress().When(x => x.CurrentMemberFromHeader!= null);
}
/// <summary>
/// 上传文件FileUploadRequest
/// </summary>
public class FileUploadRequest : BaseRequest<FileUploadRequest>
{
public IFormFile? File { get; set; }
public FileUploadRequest()
{
RuleFor(x => x.File).NotNull();
}
}
/// <summary>
/// 上传文件测试
/// 请使用postman & apifox 测试
/// </summary>
[QuickApi("fromfile", Verbs = Verb.POST)]
public class FromFileApi : BaseQuickApi<FileUploadRequest, IResultResponse>
{
public override async Task<IResultResponse> ExecuteAsync(FileUploadRequest request)
{
//测试上传一个文本文件并读取内容
if (request.File != null)
{
using (var sr = new StreamReader(request.File.OpenReadStream()))
{
var content = await sr.ReadToEndAsync();
return Results.Ok(content).AsRsp();
}
}
return Results.BadRequest("no file").AsRsp();
}
public override RouteHandlerBuilder HandlerBuilder(RouteHandlerBuilder builder)
{
builder.Accepts<FileUploadRequest>("multipart/form-data");
builder.WithOpenApi(operation => new(operation)
{
Summary = "上传文件测试",
Description = "上传文件测试"
});
return builder;
}
}
请注意 :如果需要支持文件上传 必须 重写HandlerBuilder 设置 : builder.Accepts("multipart/form-data");
internal interface IQuickApiMiddlewareHandler
{
/// <summary>
/// 请求QuickApi前的操作
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
Task InvokeBeforeAsync(HttpContext context);
/// <summary>
/// 请求QuickApi后的操作
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
Task InvokeAfterAsync(HttpContext context);
}
/// <summary>
/// QuickApiMiddleware
/// </summary>
public sealed class QuickApiMiddleware
{
private readonly RequestDelegate _next;
public QuickApiMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
var md = context.GetEndpoint()?.Metadata.GetMetadata<QuickApiMetadata>();
if (md == null || md.QuickApiType == null)
{
await _next(context);
return;
}
var handler = context.RequestServices.GetRequiredService(md.QuickApiType) as IQuickApiMiddlewareHandler;
if (handler == null)
{
await _next(context);
return;
}
await handler.InvokeBeforeAsync(context);
await _next(context);
await handler.InvokeAfterAsync(context);
}
}
兼容NSwag的成本较高,需要做的工作比较多,欢迎有兴趣的伙伴贡献代码 😃
public class HelloApiRequest : BaseRequest<HelloApiRequest>
{
public string? Name { get; set; } = "default";
[AliasAs("a")]
public string? Alias { get; set; }
[FromQuery]
public string? Q { get; set; }
[FromServices]
public IService Svc { get; set; }
public HelloApiRequest()
{
RuleFor(x => x.Name).NotNull().Length(2, 36);
}
}
如果指定了绑定来源那绑定器会自动定位来源, 如果未指定来源, 绑定器将 从 Query&Body&Head&Route等依次匹配(原有模式匹配)
//AddOutputCache
builder.Services.AddOutputCache(options =>
{
//options.AddBasePolicy(builder => builder.Expire(TimeSpan.FromSeconds(10)));
});
//UseOutputCache(); before MapBiwenQuickApis(); or app.MapGenQuickApis(app.Services);
app.UseOutputCache();
请注意: OutputCache只针对WebApi端的Response输出有效,如果把Api当Service使用 该特性自动无效 ,需要自行AOP支持
/// <summary>
/// JustAsService 只会被服务发现,不会被注册到路由表
/// </summary>
[QuickApi(""), JustAsService]
public class JustAsService : BaseQuickApi<EmptyRequest, ContentResponse>
{
public override Task<ContentResponse> ExecuteAsync(EmptyRequest request)
{
return Task.FromResult(new ContentResponse("Hello World JustAsService!"));
}
}
提供对Antiforgery防伪令牌检测的支持
设置: public override bool IsAntiforgeryEnabled => true;
注意 缺省状态是不做验证的,所以需要验证务必设置为true
如果要禁止请设置IsAntiforgeryEnabled =false;
.NET8同时支持使用 builder.DisableAntiforgery(); 和设置 IsAntiforgeryEnabled =false;
[QuickApi("ant-ui")]
public class AntUI : BaseQuickApiWithoutRequest<IResultResponse>
{
private readonly IAntiforgery _antiforgery;
private readonly IHttpContextAccessor _httpContextAccessor;
public AntUI(IAntiforgery antiforgery, IHttpContextAccessor httpContextAccessor)
{
_antiforgery = antiforgery;
_httpContextAccessor = httpContextAccessor;
}
public override async Task<IResultResponse> ExecuteAsync(EmptyRequest request)
{
var token = _antiforgery.GetAndStoreTokens(_httpContextAccessor.HttpContext!);
var html = $"""
<html>
<body>
<h3>Upload a image test</h3>
<form name="form1" action="/quick/ant" method="post" enctype="multipart/form-data">
<input name="{token.FormFieldName}" type="hidden" value="{token.RequestToken}"/>
<input type="file" name="file" placeholder="Upload an image..." accept=".jpg,.png" />
<input type="submit" />
</form>
</body>
</html>
""";
await Task.CompletedTask;
return Results.Content(html, "text/html").AsRspOfResult();
}
}
public class AntRequest : BaseRequest<AntRequest>
{
/// <summary>
/// 上传的文件
/// </summary>
public IFormFile? File { get; set; }
public AntRequest()
{
RuleFor(x => x.File).NotNull();
}
}
[QuickApi("ant", Verbs = Verb.POST)]
public class AntApi : BaseQuickApi<AntRequest, IResultResponse>
{
/// <summary>
/// 启动防伪验证
/// </summary>
public override bool IsAntiforgeryEnabled => true;
public override async Task<IResultResponse> ExecuteAsync(AntRequest request)
{
await Task.CompletedTask;
//return "Successed!".AsRspOfResult();
return Results.File(request.File!.OpenReadStream(), "image/png").AsRspOfResult();
}
public override RouteHandlerBuilder HandlerBuilder(RouteHandlerBuilder builder)
{
//上传文件必须使用 multipart/form-data
builder.Accepts<AntRequest>("multipart/form-data");
return base.HandlerBuilder(builder);
}
}
能不能支持移植到MAUI或者wpf上
public sealed class IResultResponse : BaseResponse
{
public IResultResponse(IResult result)
{
Result = result;
}
public IResult Result { get; set; }
}
public interface IQuickApiExceptionResultBuilder
{
/// <summary>
/// 规范化返回异常
/// </summary>
/// <param name="exception"></param>
/// <returns></returns>
Task<IResult> ErrorResult(Exception exception);
}
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.