MiniAPI(二)：Request

このシリーズの記事をわかりやすく説明するために、ビジネスシナリオを紹介します。それはシンプルな試験システムです(記事のデモ例としてのみ)。ER図は下記の通りです。1. システムには問題集があり、問題には答えがあり、問題のタイプとカテゴリーに分かれています；2. システムには受験者ユーザーがおり、問題集から試験を組織して受験者に割り当て、受験者は試験を受けて結果を記録できます。
![alt 画像](https://mmbiz.qpic.cn/mmbiz_png/4CAA63nvHRDDiaXTpVnZ5etLXHlxJXpHKozqoNGoKibLcHXeRcct86W5rfr5OViagystTTfQZMshtAIDlpadFln2A/640?wx_fmt=png)
```C#
app.MapGet("/", () => "Hello .NET Mini API！");
```
Mini APIの最大の利点は、そのシンプルさと使いやすさです。例えば、上記のコード行では、MapGetの引数が2つあります。最初の引数はルーティング情報、二番目の引数は実装方法です。「このチャネルが何をするか」(最初の引数はこのチャネル、二番目の引数はそれが何をするか)という意味です。ここで、2番目の引数は実際にはラムダ式で、メソッド（関数）に置き換えることもできます。このメソッドは静的なものでもインスタンスメソッドでもかまいません。主に何をするかを完了できればよいのです。
さらに詳細に見ていきましょう。2つの引数についてです：
最初の引数：
```C#
app.MapGet("/question/{id:int}", (int id) => $"ID{id}の問題を検索");
```
この時、リクエストURLは：/question/1ですが、ここでは整数型に限定されているため、文字列や小数を使用すると404エラーになります。URLを/question/-1に変更しても通りますが、次のようになります：
![alt 画像](https://mmbiz.qpic.cn/mmbiz_png/4CAA63nvHRDDiaXTpVnZ5etLXHlxJXpHKMUv6NtrfB50ic1ndvlwq4q71HAm2gLibSAB4CiblLQFrIdPhfDXYibN00Q/640?wx_fmt=png)
しかし、データベースには-1のようなIDは存在しません（データは1から始まり、増分値は1です）。したがって、ルーティングのルールを強化する必要があります：
```C#
app.MapGet("/question/{id:min(1)}", (int id) => $"ID{id}の問題を検索");
```
これにより、負の数だけでなく0も排除されます。uintを使うとどうなるでしょうか？
```C#
app.MapGet("/question/{id:uint}", (uint id) => $"ID{id}の問題を検索");
```
![alt 画像](https://mmbiz.qpic.cn/mmbiz_png/4CAA63nvHRDDiaXTpVnZ5etLXHlxJXpHKZtMobqUdvGa0aGr9GSgqRlQX1AiaSBU6jic18BWoqFqfK4k9ne8TasoA/640?wx_fmt=png)
エラーは、コンテナからuintを見つけることができないために発生します。これは、公式のルーティングパラメーターの制約にuintの処理が含まれていないためです。では、符号なし整数をパラメーターとして使用したい場合はどうすればよいでしょうか？その場合は、カスタムルーティングの制約を定義しましょう。
```C#
var builder = WebApplication.CreateBuilder();
builder.Services.AddRouting(options =>
{
    options.ConstraintMap["Uint"] = typeof(MyRouteConstraint.Uint);
});
var app = builder.Build();
//パラメーター・ルーティング
app.MapGet("/question/{id:Uint}", (uint id) => $"ID{id}の問題を検索");
app.Run();
namespace MyRouteConstraint
{
    public class Uint : IRouteConstraint
    {
        public bool Match(HttpContext? httpContext, IRouter? route, string routeKey,RouteValueDictionary values, RouteDirection routeDirection)
        {
            if (values == null || values.Count == 0 || string.IsNullOrWhiteSpace(routeKey)) 
            { 
                return false; 
            } 
            var result = uint.TryParse(values[routeKey].ToString(), out uint value); 
            if (result)
            { 
                return true; 
            } 
            return false; 
        } 
    }
}
```
ルーティングの制約には正規表現もサポートされています。詳細は以下のコードを参照してください：
```C#
app.MapGet("/area/{postcode:regex(^[0-9]{3}-[0-9]{4}$)}", (string postcode) => $"郵便番号：{postcode}");
```
二番目の引数：
1. クエリパラメーターからデータを取得する：
```C#
app.MapGet("/answer", ([FromQuery(Name="id")]int answerId) => $"[FromQuery]-リクエストされたAnswerIDは{answerId}");
```
![alt 画像](https://mmbiz.qpic.cn/mmbiz_png/4CAA63nvHRDDiaXTpVnZ5etLXHlxJXpHKA4nnBehRPKV6D0u8FwPxfEXAlJbDScMlgicdHOTQp1D0sxribbK6SiaSw/640?wx_fmt=png)
2. ヘッダーからデータを取得する
```C#
app.MapGet("/answers", ([FromHeader(Name = "key")] string secretkey ) => $"[FromHeader]-secretkeyは{secretkey}");
```
![alt 画像](https://mmbiz.qpic.cn/mmbiz_png/4CAA63nvHRDDiaXTpVnZ5etLXHlxJXpHKS2z25EJAnDEU2nj46LN15cWlHybJ9dUticqwk4CU2z7NExCqvBAJnvw/640?wx_fmt=png)
3. ルーティングからデータを取得する
```C#
app.MapGet("/question/{questiontype}", ([FromRoute]string questionType) => $"[FromRoute]-questiontype={questionType}");
```
![alt 画像](https://mmbiz.qpic.cn/mmbiz_png/4CAA63nvHRDDiaXTpVnZ5etLXHlxJXpHKoY8G5D9YZtsr1MjWkiba03eIs3SYTNkJxf895r3onPyRicqVgVkibukBg/640?wx_fmt=png)
4. ボディからデータを取得する
```C#
app.MapPost("/answer", ([FromBody] Answer answer) => $"[FromBody]-answer：{System.Text.Json.JsonSerializer.Serialize(answer)}");
```
![alt 画像](https://mmbiz.qpic.cn/mmbiz_png/4CAA63nvHRDDiaXTpVnZ5etLXHlxJXpHKQ0M2hZW4B6s55L1mEFDsyprUha0tfS997q0E3ibG2rE9hGGyiaTlw3FQ/640?wx_fmt=png)
5. フォームからデータを取得する（エラー、.net6はAPIのためサポートしていない）
```C#
app.MapPost("/questiontype", ([FromForm] string questionTypeName) => $"[FromForm]-questionTypeName：{questionTypeName}");
```
![alt 画像](https://mmbiz.qpic.cn/mmbiz_png/4CAA63nvHRDDiaXTpVnZ5etLXHlxJXpHKVU6crrWCQ1fnqVYZ0zcOpZcOTGOTEPl4G9wbTHWGSNehq2rtYycznQ/640?wx_fmt=png)
6. サービスコンテナからデータを取得する、後で説明する
上記のFromは、リクエストの異なる領域から明示的にデータを取得するものです。ここで質問があります、From属性を削除して、各インターフェースの訪問が正常に行えるかどうかを確認してみてください[画像]。なぜでしょうか？
カスタムパラメーターバインディング
二番目の引数がメソッドであり、そのメソッドのパラメータが複雑な場合はどうすればよいでしょうか？公式は2つのカスタムパラメーターバインディング方法を提供しています。以下のデモを見てください。たとえば、特定のエリアのすべてのホテルを検索したい場合、パラメータは座標グループで、その後でバックエンドでこの範囲内のホテルを検索できるようになります。複雑なタイプを受け付ける必要がありますが、Request.Queryで受け取るのは文字列なので、これら2つのカスタムバインディングが変換作業を担当します（注：もちろん、JsonのBodyをPostする方法もあります）
```C#
using System.Reflection;
var app = WebApplication.Create();
//カスタムパラメーターのバインディング area=[(35.721875, 139.786564),(35.723903, 139.803464),(35.705806, 139.806078),(35.705118, 139.779927)]
app.MapGet("/area1/hotel", (Area1 area) => $"TryParse Area1:{System.Text.Json.JsonSerializer.Serialize(area)}");
app.MapGet("/area2/hotel", (Area2 area) => $"BindAsync Area2:{System.Text.Json.JsonSerializer.Serialize(area)}");

app.Run();

public class Area1
{
    public Coordinates[]? Coordinateses { get; set; }
    public static bool TryParse(string? value, IFormatProvider? provider, out Area1? area)
    {
        var CoordinatesGroupStrings = value?.Split(new string[] { "[(", ")]", "),(" }, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
        if (CoordinatesGroupStrings != null)
        {
            var coordinatesList = new List<Coordinates>();
            foreach (var coordinateGroupString in CoordinatesGroupStrings)
            {
                var coordinateStrings = coordinateGroupString.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
                var latitudeResult = double.TryParse(coordinateStrings[0], out double latitude);
                var longitudeResult = double.TryParse(coordinateStrings[1], out double longitude);
                if (latitudeResult && longitudeResult)
                {
                    coordinatesList.Add(new Coordinates(latitude, longitude));
                }
            }
            area = new Area1 { Coordinateses = coordinatesList.ToArray() };
            return true;
        }
        area = null;
        return false;
    }
}
public record Coordinates(double Latitude, double Longitude);
public class Area2
{
    public Coordinates[]? Coordinateses { get; set; }
    public static ValueTask<Area2?> BindAsync(HttpContext context, ParameterInfo parameter)
    {
        var CoordinatesGroupStrings = context.Request.Query["area"].ToString().Split(new string[] { "[(", ")]", "),(" }, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
        if (CoordinatesGroupStrings != null)
        {
            var coordinatesList = new List<Coordinates>(); foreach (var coordinateGroupString in CoordinatesGroupStrings)
            {
                var coordinateStrings = coordinateGroupString.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
                var latitudeResult = double.TryParse(coordinateStrings[0], out double latitude); var longitudeResult = double.TryParse(coordinateStrings[1], out double longitude);
                if (latitudeResult && longitudeResult)
                {
                    coordinatesList.Add(new Coordinates(latitude, longitude));
                }
            }
            return ValueTask.FromResult<Area2?>(new Area2 { Coordinateses = coordinatesList.ToArray() });
        }
        return ValueTask.FromResult<Area2?>(null);
    }
}
```

(Translated by GPT)

元のリンク：https://mp.weixin.qq.com/s?__biz=MzA3NDM1MzIyMQ==&mid=2247484786&idx=1&sn=65524e0f60a2902a509e66ffe8315cb9&chksm=9f005a58a877d34e2770e0f6e6234f4484d03872796be6cdd77a2d537021b23094a14bae1ecc&token=2050451386&lang=zh_CN#rd