Using a C# 7 tuple in an ASP.NET Core Web API Controller

Solution 1:

It doesn't work because named tuple names are not quite "real", it's mostly syntax sugar provided by compiler. If you look at ValueTuple set of types, by which named tuples are represented, you will see that they have properties like Item1, Item2 and so on.

Compiler will rewrite all your references to named tuple names to their real names (Item1 etc). For example you have this:

static void Create((string username, string password) usernameAndPassword) {
    Console.WriteLine(usernameAndPassword.username);
    Console.WriteLine(usernameAndPassword.password);
}

But when you compile that, what you really will have is this:

static void Create([TupleElementNames(new string[] {"username", "password"})] ValueTuple<string, string> usernameAndPassword)
{
  Console.WriteLine(usernameAndPassword.Item1);
  Console.WriteLine(usernameAndPassword.Item2);
}

Your names are now only in metadata attribute TupleElementNames, but not in code.

For that reason, when you post something like:

{"username": "x", "password": "y"}

to your action, asp.net cannot bind. But if you would post:

{"item1": "x", "item2": "y"}

then it will bind with no problems. You can write custom binder probably, which can use TupleElementNames attribute, but there is no reason to really. Just use separate parameters or real model as suggested in comments. Your action input parameters is not some throwaway thing. You might later want to validate them, generate documentation from the model and so on.

Solution 2:

You can use this package. This package binds json body to your models.

Github Repo

Installation

//Nuget
Install-Package M6T.Core.TupleModelBinder -Version 1.0.0

//dotnet cli
dotnet add package M6T.Core.TupleModelBinder --version 1.0.0

Usage

Modify startup.cs like

using M6T.Core.TupleModelBinder;
....

public void ConfigureServices(IServiceCollection services)
{
  services.AddMvc(options =>
  {
      options.ModelBinderProviders.Insert(0, new TupleModelBinderProvider());
  });
}

Post request body

{
  "user" : {
    "Name":"Test",
    "Surname":"Test2",
    "Email":"[email protected]"
  },
  "someData" : "If you like it, you put a data on it"
}

And in your controller use it like

[HttpPost]
public IActionResult CreateUser((User user, string someData) request)
{
    using (var db = new DBContext())
    {
        var newUser = db.Users.Add(request.user);
        db.SaveChanges();
        return Json(new { userId = request.user.Id, someData = request.someData});
    }
}