MASA.Framework icon indicating copy to clipboard operation
MASA.Framework copied to clipboard

[Proposal] Minimal APIs ServiceBase Refactor

Open doddgu opened this issue 3 years ago • 4 comments

假设标准服务写法

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("api/v1/catalog/{id}", GetAsync);
app.MapGet("api/v1/catalog/{id}/product", GetProductAsync);
app.MapPost("api/v1/catalog/{id}/product", CreateProductAsync);
app.MapPut("api/v1/catalog/{id}/product", UpdateProductAsync);
app.MapDelete("api/v1/catalog/{id}/product", DeleteProductAsync);
app.Map("api/v1/catalog/test", TestAsync);

app.Run();

public async Task<IResult> GetAsync(int id)
{
    // do something
}

public async Task<IResult> GetProductAsync(int id)
{
    // do something
}

public async Task<IResult> CreateProductAsync(int id, CreateProductDto product)
{
    // do something
}

public async Task<IResult> UpdateProductAsync(int id, UpdateProductDto product)
{
    // do something
}

public async Task<IResult> DeleteProductAsync(int id)
{
    // do something
}

public async Task<IResult> TestAsync()
{
    // do something
}

优化目标

自动解析ServiceBase,按照默认Route规则进行注册: {Prefix}/{Version}/{DefaultTrimServiceName(ServiceName)}/{DefaultTrimMethod(method)}/{id?} DefaultTrimServiceName

  1. TrimEnd:Service

DefaultTrimMethod:

  1. TrimStart:Get/Post/Create/Put/Update/Delete/Remove 等
  2. TrimEnd:Async

PS:如api/v1/user/GetAsync/1,将会变为 api/v1/user/1

优化过程

分离Map到独立的服务文件

program.cs

var builder = WebApplication.CreateBuilder(args);
var app = builder.Services.AddService(builder); //可能需要将builder,也有可能会去掉,要看MasaApp中保存Builder的顺序是否更早。
app.Run();

CatalogService.cs

public class CatalogService: ServiceBase
{
    public CatalogService() // 去掉将IServiceCollection传递给基类
    {
        App.MapGet("api/v1/catalog/{id}", GetAsync);
        App.MapGet("api/v1/catalog/{id}/product", GetProductAsync);
        App.MapPost("api/v1/catalog/{id}/product", CreateProductAsync);
        App.MapPut("api/v1/catalog/{id}/product", UpdateProductAsync);
        App.MapDelete("api/v1/catalog/{id}/product", DeleteProductAsync);
        App.Map("api/v1/catalog/test", TestAsync);
    }
}

扫描非当前程序集的ServiceBase

program.cs

var builder = WebApplication.CreateBuilder(args);
var app = builder.Services.AddService(builder, options => { AdditionalAssemblies = new[] {} }); 
app.Run();

修改固定前缀

program.cs,全局设置,注意:所有参数为空则表示忽略,如Version = "",则Url将是api/{controller}/...

var builder = WebApplication.CreateBuilder(args);
var app = builder.Services.AddService(builder, options => 
    {
        Prefix = "api",
        Version = "v1",
        PluralizeServiceName = false // 设置为true则自动将service name变成复数,如 UserService -> api/v1/users
    }); 
app.Run();

CatalogService.cs,单服务设置

public class CatalogService: ServiceBase
{
    public CatalogService()
    {
        // 可有可无,设置后优先级将覆盖全局配置
        RouteOptions.Prefix = "api";
        RouteOptions.Version = "v2";
        RouteOptions.PluralizeServiceName = true;
    }
}

自动注册方法

program.cs,全局设置

var builder = WebApplication.CreateBuilder(args);
var app = builder.Services.AddService(builder, options => 
    {
        // *Prefixes 将会告诉自动扫描的方法,如何通过前缀将方法注册到对应的HttpMethod下
        GetPrefixes = new string[] { "Get", "Select" } // 默认值是 Get
        PostPrefixes = new string[] { "Upsert", "Create" } // 默认值是 Add, Create
        PutPrefixes = new string[] { "TryPut", "Put" } // 默认值是 Update, Modify
        DeletePrefixes = new string[] { "TryDelete" } // 默认值是 Delete/Remove
    }); 
app.Run();

CatalogService.cs,单服务设置

public class CatalogService: ServiceBase
{
    public CatalogService()
    {
        // 留空即可
        // 1. 根据全局设置 Get, Post, Put, Delete 自动扫描方法注册
        // 2. 自动TrimEnd("Async")
        // 3. 未被识别的将通过Map的方式公开所有HttpMethod
    }
}

个别破坏规则的需要单独指定

CatalogService.cs,单服务设置

public class CatalogService: ServiceBase
{
    public CatalogService()
    {
        App.MapGet("v1-beta/demo/test", TestAsync); // 注册为HttpMethod = HttpGet,Url = "v1-beta/demo/test",直接通过App的Map系列方法将绕开所有的规则,直接注册到MinimalAPIs上
    }
}

任务列表

可以通过 [x] 选择已完成

  • [ ] 分离Map到独立的服务文件
  • [ ] 扫描非当前程序集的ServiceBase
  • [ ] 修改固定前缀
  • [ ] 自动注册方法
  • [ ] 个别破坏规则的需要单独指定

doddgu avatar Sep 14 '22 04:09 doddgu

#238

doddgu avatar Sep 14 '22 05:09 doddgu

此处为Route.Prefix = "api";

public class CatalogService: ServiceBase
{
    public CatalogService()
    {
        // 可有可无,设置后优先级将覆盖全局配置
        Route.Prefix = "api";
        Route.Version = "v2";
        Route.PluralizeServiceName = true;
    }
}

zhenlei520 avatar Sep 14 '22 07:09 zhenlei520

按照默认Route规则进行注册:api/v1/{controller}/{id?}/{method}

这里如果参数有id,默认注册为api/v1/{controller}/{id}api/v1/{controller}/{id?}

路由的规则多种多样,无法满足所有情况,如果需要的路由地址为api/v1/{controller}/{id?}/{method},则可以重写string GetMethodName(MethodInfo methodInfo)方法

zhenlei520 avatar Sep 14 '22 08:09 zhenlei520

已更新

doddgu avatar Sep 15 '22 01:09 doddgu

In 0.6.0 this has been handled

zhenlei520 avatar Dec 21 '22 01:12 zhenlei520