Giter Club home page Giter Club logo

biwen.quickapi's People

Contributors

vipwan avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar

biwen.quickapi's Issues

提供代码生成器功能

提供代码生成器功能,以显著提升执行期间Emit带来的性能问题 ,

提供代码诊断分析,对没有标注QuickApi特性的接口类 告警
image

提供Req别名化的绑定支持

    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的属性配置的别名 级别为最高绑定特征

提供对 AuthorizeAttribute和AllowAnonymousAttribute特性的鉴权支持

    [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");
        }
    }
}

提供内建验证器同时对DataAnnotations和FluentValidation同时支持!

比如你可以这样定义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是必须的"
    ]
  }
}

提供RouteGroup的拓展支持

提供一个接口:

    /// <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>();

提供[FromQuery][FromHeader]绑定器绑定复杂类型和数组的支持

[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);
}

提供对FileUpload支持

    /// <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);
        }
    }

提供绑定器对 FromQuery,FromHeader,FromRoute 的原生支持!

    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等依次匹配(原有模式匹配)

Output caching Support

//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支持

支持QuickApi仅注册为服务JustAsServiceAttribute

    /// <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防伪令牌检测的支持

提供对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);
        }
    }

提供对IResults的直接支持

    public sealed class IResultResponse : BaseResponse
    {
        public IResultResponse(IResult result)
        {
            Result = result;
        }

        public IResult Result { get; set; }
    }

QuickApi异常规范化返回

    public interface IQuickApiExceptionResultBuilder
    {
        /// <summary>
        /// 规范化返回异常
        /// </summary>
        /// <param name="exception"></param>
        /// <returns></returns>
        Task<IResult> ErrorResult(Exception exception);
    }

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.