快速展示原型之Minimal API開發

2023-10-19 12:00:58

Minimal API官網地址: https://learn.microsoft.com/zh-cn/aspnet/core/fundamentals/minimal-apis/security?view=aspnetcore-7.0

Minimal API 背景介紹

Minimal APIs 是指在 ASP.NET Core 中引入的一種輕量級的 API 開發模式。它的產生背景是為了簡化 API 的建立和開發流程,減少樣板程式碼,並提供更靈活的方式來定義和設定 API 端點。

在傳統的 ASP.NET Core 中,建立 API 需要定義控制器類和路由設定,並編寫一系列的動作方法和路由規則。這樣的開發模式在一些簡單的場景下可能會顯得過於繁瑣和冗餘。

Minimal APIs 的定義和設定更加簡潔和直觀,可以在一個單獨的檔案中定義整個 API,而無需建立控制器類和路由設定。它通過使用 Lambda 表示式和鏈式呼叫的方式,將路由、HTTP 方法和請求處理邏輯直接繫結在一起,減少了樣板程式碼的數量。

Minimal APIs 的應用場景包括但不限於:

  1. 快速原型開發:在快速原型開發階段,Minimal APIs 可以幫助開發者快速建立和測試 API 端點,減少開發時間和程式碼量。

  2. 小型專案和微服務:對於小型專案或者微服務,Minimal APIs 提供了一種簡潔的方式來定義和設定 API,減少了專案結構的複雜性。

  3. 單個端點的簡單 API:對於只有一個或者少量端點的簡單 API,Minimal APIs 可以提供更加直觀和簡潔的開發方式。

  4. 快速驗證和測試:Minimal APIs 可以用於快速驗證和測試一些想法或者概念,以及進行一些臨時性的 API 開發。

需要注意的是,Minimal APIs 並不適用於所有場景。對於複雜的 API 或者需要更多設定和擴充套件的情況,傳統的控制器和路由設定方式可能更加合適。

功能程式碼範例

在 Minimal API 中,ASP.NET Core 的一些功能可以通過簡化的語法和約定來體現。下面是一些常見的功能在 Minimal API 中的體現方式:

  1. 路由和端點定義:

    • 使用 app.MapGet()app.MapPost() 等方法來定義路由和處理不同的 HTTP 請求方法。

    • 可以直接在路由路徑中使用引數,如 {name},並將其作為方法引數進行處理。

      var builder = WebApplication.CreateBuilder(args);
      var app = builder.Build();
      ​
      app.MapGet("/", () => "Hello, Minimal API!");
      ​
      app.MapPost("/", () => "Received a POST request!");
      ​
      app.MapPut("/", () => "Received a PUT request!");
      ​
      app.MapDelete("/", () => "Received a DELETE request!");
      ​
      app.MapMethods("/hello", new[] { "GET", "POST", "PUT", "DELETE" }, () =>
      {
          var method = Context.Request.Method;
          return $"Received a {method} request to /hello";
      });
      ​
      app.Run();
      ​
      app.MapGet() 方法用於處理 HTTP GET 請求,當請求的路徑為根路徑 / 時,返回 "Hello, Minimal API!"。
      app.MapPost() 方法用於處理 HTTP POST 請求,當請求的路徑為根路徑 / 時,返回 "Received a POST request!"。
      app.MapPut() 方法用於處理 HTTP PUT 請求,當請求的路徑為根路徑 / 時,返回 "Received a PUT request!"。
      app.MapDelete() 方法用於處理 HTTP DELETE 請求,當請求的路徑為根路徑 / 時,返回 "Received a DELETE request!"。
      app.MapMethods() 方法用於處理指定的 HTTP 請求方法,當請求的路徑為 /hello 且方法為 GET、POST、PUT 或 DELETE 時,返回相應的資訊。

       

  2. 請求和響應處理:

    • 使用方法引數來獲取請求中的資料,如路由引數、查詢字串引數和請求體引數。

    • 使用 Results 類來生成響應,如 Results.Ok()Results.BadRequest() 等。

      var builder = WebApplication.CreateBuilder(args);
      ​
      builder.Services.AddSingleton<ICustomService, CustomService>();
      ​
      var app = builder.Build();
      ​
      app.MapGet("/hello/{name}", (string name) =>
      {
          return $"Hello, {name}!";
      });
      ​
      app.MapPost("/api/submit", async (HttpRequest request) =>
      {
          // 從請求體中獲取資料
          var data = await request.ReadFromJsonAsync<CustomData>();
      ​
          // 處理資料並生成響應
          var result = ProcessData(data);
      ​
          return Results.Ok(result);
      });
      ​
      app.Run();
      ​
      public class CustomData
      {
          public string Name { get; set; }
          public int Age { get; set; }
      }
      ​
      public class CustomService : ICustomService
      {
          // 服務實現程式碼
      }
      ​
      public interface ICustomService
      {
          // 服務介面定義
      }
      ​
      在上面的範例中,我們定義了兩個端點:
      ​
      - `GET /hello/{name}`:使用路由引數 `name` 來獲取請求中的資料,並返回相應的問候訊息。
      - `POST /api/submit`:使用 `HttpRequest` 引數來獲取請求體中的資料,並進行處理,然後生成響應。
      ​
      在 `POST /api/submit` 端點中,我們使用 `ReadFromJsonAsync<T>()` 方法從請求體中非同步讀取 JSON 資料,並將其轉換為 `CustomData` 物件。然後,我們可以對資料進行處理,並生成相應的結果。最後,我們使用 `Results.Ok()` 方法將結果作為 JSON 響應返回。
      ​
      希望這個更全面的程式碼範例能夠幫助你理解在 Minimal API 中如何處理請求和響應。如果你有任何其他問題,請隨時提問。

       

  3. 中介軟體和管道:

    • 使用 app.UseMiddleware<CustomMiddleware>() 來新增自定義的中介軟體。

    • 使用 app.MapGet().UseMiddleware<AuthenticationMiddleware>() 在特定的端點上使用中介軟體。

      ```csharp
      using Microsoft.AspNetCore.Builder;
      using Microsoft.AspNetCore.Http;
      using Microsoft.Extensions.DependencyInjection;
      using System;
      using System.Threading.Tasks;
      ​
      var builder = WebApplication.CreateBuilder(args);
      ​
      // 註冊自定義中介軟體
      builder.Services.AddTransient<CustomMiddleware>();
      ​
      var app = builder.Build();
      ​
      // 使用自定義中介軟體
      app.UseMiddleware<CustomMiddleware>();
      ​
      // 定義路由和處理請求的方法
      app.MapGet("/", () => "Hello, Minimal API!");
      ​
      app.Run();
      ​
      // 自定義中介軟體
      public class CustomMiddleware
      {
          private readonly RequestDelegate _next;
      ​
          public CustomMiddleware(RequestDelegate next)
          {
              _next = next;
          }
      ​
          public async Task InvokeAsync(HttpContext context)
          {
              // 在請求處理之前執行的邏輯
              Console.WriteLine("CustomMiddleware: Before request");
      ​
              // 呼叫下一個中介軟體或處理程式
              await _next(context);
      ​
              // 在請求處理之後執行的邏輯
              Console.WriteLine("CustomMiddleware: After request");
          }
      }
      ```
      ​
      在上面的範例中,我們首先通過 `builder.Services.AddTransient<CustomMiddleware>()` 將自定義中介軟體註冊到容器中。然後,在 `app.UseMiddleware<CustomMiddleware>()` 中使用自定義中介軟體。這樣,每次請求進入應用程式時,都會先經過自定義中介軟體的處理。
      ​
      自定義中介軟體的實現類 `CustomMiddleware` 必須具有 `InvokeAsync` 方法,該方法接收一個 `HttpContext` 物件和一個 `RequestDelegate` 物件作為引數。在 `InvokeAsync` 方法中,我們可以在請求處理之前和之後執行一些邏輯。
      ​
      在上面的範例中,我們在自定義中介軟體的 `InvokeAsync` 方法中列印了一些紀錄檔資訊,以展示自定義中介軟體的使用方式。
      ​

       

  4. 模型繫結和驗證:

    • 可以通過方法引數直接進行模型繫結,無需顯式地使用 [FromBody][FromQuery] 等特性。

    • 使用 ModelState 進行模型驗證,並返回相應的結果。

      var builder = WebApplication.CreateBuilder(args);
      ​
      // 定義模型類
      public class Person
      {
          [Required(ErrorMessage = "Name is required")]
          public string Name { get; set; }
      ​
          [Range(18, 100, ErrorMessage = "Age must be between 18 and 100")]
          public int Age { get; set; }
      ​
          public string Email { get; set; }
      }
      ​
      // 註冊模型驗證器
      builder.Services.Configure<ApiBehaviorOptions>(options =>
      {
          options.InvalidModelStateResponseFactory = context =>
          {
              var errors = new List<string>();
              foreach (var modelState in context.ModelState.Values)
              {
                  foreach (var error in modelState.Errors)
                  {
                      errors.Add(error.ErrorMessage);
                  }
              }
      ​
              return new BadRequestObjectResult(errors);
          };
      });
      ​
      var app = builder.Build();
      ​
      app.MapPost("/person", async (Person person) =>
      {
          if (!ModelState.IsValid)
          {
              return Results.BadRequest(ModelState);
          }
      ​
          // 處理有效的 person 物件
          return Results.Ok(person);
      });
      ​
      app.Run();
      ​
      ```
      ​
      在上面的範例中,我們定義了一個 `Person` 類作為模型,並在屬性上使用了一些資料註解來定義驗證規則。在 `app.MapPost` 方法中,我們直接將 `Person` 類作為引數,Minimal API 會自動進行模型繫結和驗證。如果模型驗證失敗,我們通過自定義的 `InvalidModelStateResponseFactory` 來返回驗證錯誤資訊。
      ​
      當我們傳送 POST 請求到 `/person` 路徑時,Minimal API 會自動將請求體中的 JSON 資料繫結到 `Person` 物件,並進行模型驗證。如果模型驗證失敗,將返回包含驗證錯誤資訊的 BadRequest 響應;如果模型驗證成功,將返回包含有效的 `Person` 物件的 Ok 響應。
      ​
      這樣,我們就可以通過方法引數來實現模型繫結和驗證,而無需顯式地使用 `[FromBody]` 或 `[FromQuery]` 等特性。
      ​

       

  5. 例外處理:

    • 使用 app.RunExceptionHandler() 來捕獲並處理應用程式中的異常。

      var builder = WebApplication.CreateBuilder(args);
      ​
      // 註冊自定義的例外處理中介軟體
      builder.Services.AddSingleton<ExceptionHandlerMiddleware>();
      ​
      var app = builder.Build();
      ​
      // 使用自定義的例外處理中介軟體
      app.UseMiddleware<ExceptionHandlerMiddleware>();
      ​
      app.MapGet("/", () => throw new Exception("Something went wrong!"));
      ​
      app.Run();
      ​
      // 自定義例外處理中介軟體
      public class ExceptionHandlerMiddleware
      {
          private readonly RequestDelegate _next;
      ​
          public ExceptionHandlerMiddleware(RequestDelegate next)
          {
              _next = next;
          }
      ​
          public async Task InvokeAsync(HttpContext context)
          {
              try
              {
                  // 執行下一個中介軟體
                  await _next(context);
              }
              catch (Exception ex)
              {
                  // 處理異常並生成響應
                  context.Response.StatusCode = 500;
                  await context.Response.WriteAsync($"An error occurred: {ex.Message}");
              }
          }
      }
      ```
      ​
      在上面的程式碼範例中,我們定義了一個自定義的例外處理中介軟體 `ExceptionHandlerMiddleware`,它接受一個 `RequestDelegate` 引數作為下一個中介軟體。在 `InvokeAsync` 方法中,我們使用 `try-catch` 塊來捕獲應用程式中的異常。如果發生異常,我們設定響應的狀態碼為 500,並將異常資訊寫入響應。
      ​
      在應用程式的主函數中,我們註冊了自定義的例外處理中介軟體,並在根路徑上丟擲一個異常,以模擬應用程式中的異常情況。當存取根路徑時,例外處理中介軟體將捕獲並處理異常,並返回相應的錯誤響應。
      ​
      通過這種方式,你可以自定義例外處理中介軟體來捕獲和處理應用程式中的異常,並生成適當的響應。
      ​

       

  6. 依賴注入:

    • 使用 app.Services.AddSingleton<TService, TImplementation>()app.Services.AddScoped<TService, TImplementation>() 將服務註冊到容器中。

    • 可以直接在方法引數中使用依賴注入的服務。

      當使用 Minimal API 進行開發時,可以使用依賴注入來註冊和使用服務。下面是一個更全面的程式碼範例,演示瞭如何在 Minimal API 中使用依賴注入:
      ​
      ```csharp
      using Microsoft.AspNetCore.Builder;
      using Microsoft.AspNetCore.Http;
      using Microsoft.Extensions.DependencyInjection;
      ​
      var builder = WebApplication.CreateBuilder();
      ​
      // 註冊服務
      builder.Services.AddSingleton<IMyService, MyService>();
      ​
      var app = builder.Build();
      ​
      // 使用服務
      app.MapGet("/", (IMyService myService) =>
      {
          var result = myService.DoSomething();
          return result;
      });
      ​
      app.Run();
      ​
      // 服務介面
      public interface IMyService
      {
          string DoSomething();
      }
      ​
      // 服務實現
      public class MyService : IMyService
      {
          public string DoSomething()
          {
              return "Hello from MyService!";
          }
      }
      ```
      ​
      在上面的範例中,我們首先使用 `builder.Services.AddSingleton<IMyService, MyService>()` 將 `MyService` 註冊為 `IMyService` 介面的實現,這樣就可以在應用程式中使用 `IMyService` 進行依賴注入。
      ​
      然後,在 `app.MapGet()` 方法中,我們將 `IMyService` 作為引數傳入,Minimal API 將自動從容器中解析並提供一個 `IMyService` 範例。在處理請求時,我們可以直接使用 `myService` 來呼叫 `IMyService` 中的方法。
      ​
      這樣,我們就可以在 Minimal API 中方便地使用依賴注入來管理和使用服務。
      ​

       

  7. 身份驗證和授權:

    • 使用 app.UseAuthentication()app.UseAuthorization() 啟用身份驗證和授權中介軟體。

    • 使用 RequireAuthorization() 方法來標記需要進行身份驗證和授權的端點。

      ​
      1. 首先,確保已經在專案中新增了所需的身份驗證和授權相關的 NuGet 包,如 `Microsoft.AspNetCore.Authentication` 和 `Microsoft.AspNetCore.Authorization`。
      ​
      2. 在 `Program.cs` 檔案中,使用 `builder.Services.AddAuthentication()` 和 `builder.Services.AddAuthorization()` 方法來註冊身份驗證和授權服務:
      ​
      ```csharp
      var builder = WebApplication.CreateBuilder(args);
      ​
      // 註冊身份驗證服務
      builder.Services.AddAuthentication(options =>
      {
          options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
          options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
      })
      .AddJwtBearer(options =>
      {
          // 設定 JWT Bearer 認證選項
          options.TokenValidationParameters = new TokenValidationParameters
          {
              ValidateIssuer = true,
              ValidateAudience = true,
              ValidateLifetime = true,
              ValidateIssuerSigningKey = true,
              ValidIssuer = Configuration["Jwt:Issuer"],
              ValidAudience = Configuration["Jwt:Audience"],
              IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Jwt:Key"]))
          };
      });
      ​
      // 註冊授權服務
      builder.Services.AddAuthorization();
      ​
      var app = builder.Build();
      ```
      ​
      3. 在 `Configure` 方法中,使用 `app.UseAuthentication()` 和 `app.UseAuthorization()` 方法來啟用身份驗證和授權中介軟體:
      ​
      ```csharp
      app.UseAuthentication();
      app.UseAuthorization();
      ```
      ​
      4. 在需要進行身份驗證和授權的端點上,使用 `RequireAuthorization()` 方法來標記需要進行身份驗證和授權的端點:
      ​
      ```csharp
      app.MapGet("/hello", () => "Hello, Minimal APIs!")
          .RequireAuthorization();
      ```
      ​
      5. 在需要進行授權的端點上,可以使用 `[Authorize]` 特性來標記需要進行授權的端點:
      ​
      ```csharp
      app.MapGet("/secret", () => "This is a secret page.")
          .RequireAuthorization()
          .WithMetadata(new AuthorizeAttribute());
      ```
      ​
      6. 在需要獲取當前使用者資訊的地方,可以通過方法引數的方式獲取 `HttpContext`,並使用 `User` 屬性來獲取當前使用者的身份資訊:
      ​
      ```csharp
      app.MapGet("/profile", (HttpContext context) =>
      {
          var user = context.User;
          // 獲取使用者資訊並進行處理
          // ...
          return "User Profile";
      })
      .RequireAuthorization();
      ```
      ​
      通過以上程式碼範例,你可以在 Minimal API 中實現身份驗證和授權的功能。請注意,這只是一個基本範例,實際應用中可能需要根據具體的需求進行設定和擴充套件。
      ​

       

  8. Swagger:

    • 使用 app.UseSwagger()app.UseSwaggerUI() 啟用 Swagger 支援和 Swagger UI。

      ​
      var builder = WebApplication.CreateBuilder(args);
      ​
      // 新增 Swagger
      builder.Services.AddSwaggerGen(c =>
      {
          c.SwaggerDoc("v1", new OpenApiInfo { Title = "My API", Version = "v1" });
      });
      ​
      var app = builder.Build();
      ​
      // 啟用 Swagger UI
      app.UseSwagger();
      app.UseSwaggerUI(c =>
      {
          c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API v1");
      });
      ​
      app.MapGet("/", () => "Hello, Minimal APIs!");
      ​
      app.Run();
      ```
      ​
      在上述範例中,我們首先在 `CreateHostBuilder` 方法中使用 `AddSwaggerGen` 方法設定 Swagger,指定了 API 的標題和版本資訊。
      ​
      然後,在應用程式構建完成後,我們使用 `UseSwagger` 方法啟用 Swagger 支援,並使用 `UseSwaggerUI` 方法設定 Swagger UI 的終結點。
      ​
      最後,我們定義了一個簡單的根路徑處理程式,以便在瀏覽器中檢視 Swagger UI。
      ​
      執行應用程式後,你可以通過存取 `http://localhost:5000/swagger` 來檢視 Swagger UI,並瀏覽和測試你的 API。
      ​
      ​

       

  9. Filter:

    • 使用 app.AddFilter<CustomFilter>() 將過濾器新增到應用程式。

      當使用 Minimal API 開發時,可以通過自定義 Filter 來實現全域性的過濾器功能。下面是一個更全面的程式碼範例:
      ​
      1. 建立自定義的過濾器類 `CustomFilter`:
      ​
      ```csharp
      public class CustomFilter : IAsyncActionFilter
      {
          public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
          {
              // 在執行操作之前的邏輯
              Console.WriteLine("Before action execution");
      ​
              // 執行下一個過濾器或操作
              var resultContext = await next();
      ​
              // 在執行操作之後的邏輯
              Console.WriteLine("After action execution");
          }
      }
      ```
      ​
      2. 在應用程式中註冊過濾器:
      ​
      ```csharp
      var builder = WebApplication.CreateBuilder(args);
      ​
      // 註冊過濾器
      builder.Services.AddFilter<CustomFilter>();
      ​
      var app = builder.Build();
      ​
      app.MapGet("/", () => "Hello, Minimal APIs!")
          .RequireAuthorization()
          .WithFilter<CustomFilter>(); // 新增過濾器到端點
      ​
      app.Run();
      ```
      ​
      在上面的範例中,我們建立了一個名為 `CustomFilter` 的自定義過濾器類,並實現了 `IAsyncActionFilter` 介面。在 `OnActionExecutionAsync` 方法中,我們可以編寫在執行操作之前和之後需要執行的邏輯。
      ​
      在應用程式中,我們使用 `builder.Services.AddFilter<CustomFilter>()` 將自定義過濾器新增到服務容器中。然後,我們在 `app.MapGet()` 方法鏈中使用 `.WithFilter<CustomFilter>()` 將過濾器新增到特定的端點。
      ​
      當請求到達該端點時,過濾器的邏輯將在執行操作之前和之後被呼叫。
      ​

       

通過這些簡化的語法和約定,Minimal API 提供了一種更簡潔、更直觀的方式來開發和設定 ASP.NET Core 應用程式,使得程式碼更加清晰和易於維護。