web-dev-qa-db-ja.com

同じメソッドの複数のタイプ[FromBody] .netコアWebAPI

1つのPOSTメソッドを持つコントローラーがあります。これは2つのタイプのxml文字列を受け取ります。例:

[HttpPost("postObj")]
    public async Task<IActionResult> postObj([FromBody]firstClass data)
    {
        if (data != null)...

同じルートで複数のタイプにバインドできるようにしたい([HttpPost( "postObj")]) http://127.0.0.1:5000/api/postObj で受信できるようにする=本文にfirstClassxml、または本文にsecondClass xmlを使用し、それに応じて動作します。

同じルートでタイプが異なる別のメソッドを作成してみました。

    [HttpPost("postObj")]
    public async Task<IActionResult> postObj([FromBody]secondClass data)
    {
        if (data != null)...

しかし、予想どおり、「リクエストが複数のアクションに一致し、あいまいさが生じた」というメッセージが表示されます。

本文を読み取ってチェックしてから、xmlをそれぞれのオブジェクトにシリアル化してみましたが、パフォーマンスが大幅に低下しました。

1秒あたり最大100のリクエストを期待しており、FromBodyを使用してバインドするとそれが得られますが、本文を手動で読み取ってシリアル化すると、約15しか得られません。

どうすればそれを達成できますか?

6
Tarek

同じルートで2つのアクションを定義することはできません。アクションセレクターは、パラメータータイプを考慮しません。では、このアクションをマージしてみませんか。

public async Task<IActionResult> postObj([FromBody]EntireData data)
{
    if (data.FirstClass != null)
    {
        //Do something
    }
    if (data.SecondClass != null)
    {
        //Do something
    }
}

public class EntireData
{
    public FirstClass  firstClass { get; set; }

    public SecondClass secondClass { get; set; }
}
2
lucky

同じ問題で遊んでいました、これが私が最終的にするものです:

次のAPIが欲しいです:

PATCH /persons/1
{"name": "Alex"}

PATCH /persons/1
{"age": 33}

また、次のような個別のコントローラーアクションが必要です。

[HttpPatch]
[Route("person/{id:int:min(1)}")]
public void PatchPersonName(int id, [FromBody]PatchPersonName model) {}

[HttpPatch]
[Route("person/{id:int:min(1)}")]
public void PatchPersonAge(int id, [FromBody]PatchPersonAge model) {}

そのため、APIドキュメントの生成中にSwashbuckleで使用できます。

さらに重要なのは、検証機能を組み込みたいと思っていることです(他の提案されたソリューションでは機能しません)。

これを実現するために、着信リクエストの本文を逆シリアル化しようとする独自のアクションメソッドセレクター属性を作成します。それが可能な場合はアクションが選択され、そうでない場合は次のアクションがチェックされます。

public class PatchForAttribute : ActionMethodSelectorAttribute
{
    public Type Type { get; }

    public PatchForAttribute(Type type)
    {
        Type = type;
    }

    public override bool IsValidForRequest(RouteContext routeContext, ActionDescriptor action)
    {
        routeContext.HttpContext.Request.EnableRewind();
        var body = new StreamReader(routeContext.HttpContext.Request.Body).ReadToEnd();
        try
        {
            JsonConvert.DeserializeObject(body, Type, new JsonSerializerSettings { MissingMemberHandling = MissingMemberHandling.Error });
            return true;
        }
        catch (Exception)
        {
            return false;
        }
        finally
        {
            routeContext.HttpContext.Request.Body.Position = 0;
        }
    }
}

pros:検証は機能しており、3番目のアクションやベースモデルは不要で、スワッシュバックルで機能します

cons:このアクションでは、本体を2回読み取って逆シリアル化します

note:ストリームを巻き戻すことが重要です。そうしないと、他の人が本文を読み取ることができなくなります。

コントローラは次のようになります。

[HttpPatch]
[Route("person/{id:int:min(1)}")]
[PatchFor(typeof(PatchPersonName))]
public void PatchPersonName(int id, [FromBody]PatchPersonName model) {}

[HttpPatch]
[Route("person/{id:int:min(1)}")]
[PatchFor(typeof(PatchPersonAge))]
public void PatchPersonAge(int id, [FromBody]PatchPersonAge model) {}

完全なサンプルコードを見つけることができます ここ

2
mac

免責事項として、私はこれを少しハッキーだと思います。送信されるオブジェクトを、ケースを表すことができる1つのオブジェクトに変更するか、2つの異なるオブジェクトタイプを2つの異なるURIに投稿するようにプッシュします。

それが邪魔にならないように、それを機能させるためのオプションは、このチュートリアルに従ってカスタムIModelBinderを作成することです: https://dotnetcoretutorials.com/2016/12/28/custom-model-binders- asp-net-core /

ここで重要なのは、バインドするモデルが、そこから派生した2つの異なるクラスを持つ基本クラスになるということです。

  • カスタムモデルバインダーは、入力データに基づいて作成するモデルを選択します。
  • コントローラのアクションは、入力として基本タイプを取り、カスタムバインダーを使用するように構成されます。
  • アクションでは、渡されたオブジェクトの実際のタイプを確認し、それに応じて処理する必要があります。
0
Nick