Clear Code for Minimal API

YataoFeng發表於2024-05-20

我在寫MinimalAPI的時候,發現不能最清晰的看到每個API,原因就是:WebAPI中不斷增長邏輯處理過程

於是我在想如何簡化API至一行,在一點一點想辦法中,發現了簡化DotNET Minimal API的方式。特此記錄下來這個思路給需要幫助的人。我的靈感來源於 C# 11 功能 - 介面中的靜態虛擬成員,透過靜態虛擬成員清晰整個API。


這是我思路的最終結果:在 Program.cs 中我們能透過一行程式碼,清晰的看到程式碼情況。
而無需指定平常不是很關心的處理過程和請求方式。

app.MapGroup("Connect", o =>
{
    o.MapMethods<Authorize>("Authorize");
    o.MapMethods<Authorize.Callback>("Authorize/Callback");
    o.MapMethods<Token>("Token");
    o.MapMethods<UserInfo>("UserInfo").RequireAuthorization(new AuthorizeAttribute()
    {
        AuthenticationSchemes = OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme
    });
    o.MapMethods<Endsession>("Endsession");
});
app.MapGroup("Account", o =>
{
    o.MapMethods<Login>("Login");
    o.MapMethods<Externallogin>("Externallogin");
    o.MapMethods<Externallogin.Callback>("Externallogin/Callback");
    o.MapMethods<ConfirmationLogin>("ConfirmationLogin");
    o.MapMethods<ForgotPassword>("ForgotPassword");
    o.MapMethods<ResetPassword>("ResetPassword");
});

我們只需要簡單的三步就可以做到這個事情,並且可以不用反射和其他的複雜過程。
第一步,我們需要建立(附帶靜態抽象函式的介面)IEndpointBase。

public interface IEndpointBase
{
    public static abstract IEnumerable<string> HTTPMethods();
    public static abstract Delegate Handler();
}

第二步,需要實現IEndpointBase。

public class Login : IEndpointBase
{
    public record AccountLoginRequest
    {
        [JsonPropertyName("u"), Required]
        public string UserName { get; set; } = default!;

        [JsonPropertyName("p"), Required]
        public string Password { get; set; } = default!;

        [JsonPropertyName("r"), Required]
        public string ReturnUrl { get; set; } = default!;

        [FromQuery]
        public bool UseCookies { get; set; }
    }
    public static Delegate Handler()
    {
        var callback = async ([FromBody] AccountLoginRequest request, [FromServices] SignInManager signInManager) =>
        {
            // Todo: returnUrl validate is success

            var result = await signInManager.PasswordSignInAsync(request.UserName, request.Password, request.UseCookies, lockoutOnFailure: true);
            return Results.Text(result.ToString());
        };
        return callback;
    }

    public static IEnumerable<string> HTTPMethods() => [HttpMethods.Post];
}

第三步:處理靜態IEndpointBase,此時我們已經完成了這件事情。

public static RouteHandlerBuilder MapMethods<T>(this IEndpointRouteBuilder app, [StringSyntax("Route")] string pattern) where T : IEndpointBase
{
    return app.MapMethods(pattern, T.HTTPMethods(), T.Handler());
}

單純的使用擴充套件好的 MapMethods 已經足夠清晰了。


但如果有對API進行分組的要求,使用原生的還是不會清晰,原因是:

  1. 要對 MapGroup 的值賦予變數名,比如說 var accountGroup = app.MapGroup("Account"),每個組都要想個名字。accountGroup

  2. 不能清楚自己的邊界,比如說 寫程式碼時,有可能出現插隊的情況,本來 accountGroup下面都是屬於它的端點,結果不小心插進來一個別的。

於是簡單的對MapGroup進行了擴充套件,最終結果在本文最上面。

public static void MapGroup(this IEndpointRouteBuilder endpoints, [StringSyntax("Route")] string prefix, Action<IEndpointRouteBuilder> action)
{
    var group = endpoints.MapGroup(prefix);
    action(group);
}

總結:透過這種方式,程式碼結構已經清晰多了。若是有議,可以在評論區聯絡我。

相關文章