🎭

[ASP.NET Core WebAPI] application/x-www-form-urlencoded のモデルバインドを制御する

2021/04/13に公開

application/x-www-form-urlencoded におけるモデルバインドで、リクエストがsnake_caseなどになっていてプロパティと別名でもバインドする方法です。巷に全然情報が無いです。

通常の例

[FromForm] を付けることで、application/x-www-form-urlencoded で来るリクエストをモデルにバインドできます。

public record MyRequest
{
    public string FirstName { get; init; }
    public string LastName { get; init; }
}
[ApiController]
public class MyController : ControllerBase
{
    [HttpPost]
    [Consumes("application/x-www-form-urlencoded")]
    public IActionResult Post([FromForm] MyRequest request)
    {
        if (!ModelState.IsValid)
            return BadRequest(ModelState);
        return Ok($"FirstName={request.FirstName}, LastName={request.LastName}");
    }
}

これで例えば FirstName=Donald&LastName=Trump のようなリクエストを送れば、 MyRequest モデルにその値が入った状態でアクションメソッドが呼ばれます。

snake_caseの例

camelCaseからPascalCaseであれば名前解決してくれるようですが、snake_caseだとうまくいきません。

もちろん以下のようにするのは一つの解ですが、C#的には気持ち悪いですね。

public record MyRequest
{
    public string first_name { get; init; }    
    public string last_name { get; init; }
}

そこでこのようにすれば自由に設定できます。

public record MyRequest
{
    [FromForm(Name = "first_name")]
    public string FirstName { get; init; }
    
    [FromForm(Name = "last_name")]
    public string LastName { get; init; }
}

これで、first_name=Donald&last_name=Trump のようなリクエストでもちゃんとバインドできます。

それにしても [FromForm] ってややこしいですね。fromとformで間違い探しみたいで・・・

JSONとモデルを共用する

application/x-www-form-urlencoded でも application/json でも受け付けられるというWebAPIにしたい際、モデル定義は共用できます。

使うJSONシリアライザによりますが、例えば以下のようにできます。

[DataContract]
public record MyRequest
{
    [DataMember(Name = "first_name")]
    [FromForm(Name = "first_name")]
    public string FirstName { get; init; }
    
    [DataMember(Name = "last_name")]
    [FromForm(Name = "last_name")]
    public string LastName { get; init; }
}
[ApiController]
public class MyController : ControllerBase
{
    [HttpPost]
    [Consumes("application/x-www-form-urlencoded")]
    public IActionResult PostUrlEncoded([FromForm] MyRequest request)
    {
        return Ok("UrlEncoded");
    }
    
    [HttpPost]
    [Consumes("application/json")]
    public IActionResult PostJson([FromBody] MyRequest request)
    {
        return Ok("JSON");
    }
}

参考文献

Discussion