36

Do you know why this works:

public struct UserNameAndPassword
{
    public string username;
    public string password;
}


[HttpPost]
public IActionResult Create([FromBody]UserNameAndPassword usernameAndPassword)
{
    Console.WriteLine(usernameAndPassword);
    if (this.AuthenticationService.IsValidUserAndPasswordCombination(usernameAndPassword.username, usernameAndPassword.password))
        return new ObjectResult(GenerateToken(usernameAndPassword.username));
    return BadRequest();
}

But when I replace it with a tuple, this doesn’t work?

[HttpPost]
public IActionResult Create([FromBody](string username, string password) usernameAndPassword) //encrypt password?
{
    Console.WriteLine(usernameAndPassword);
    if (this.AuthenticationService.IsValidUserAndPasswordCombination(usernameAndPassword.username, usernameAndPassword.password))
        return new ObjectResult(GenerateToken(usernameAndPassword.username));
    return BadRequest();
}

usernameAndPassword.username and .password are both null.

Are you not allowed to use tuples in a controller?

9
  • 2
    Pretty sure tuples aren't going to work like that unless you write a custom model binder. Why would you want this anyway, it's far less readable? Make a proper class or just have two parameters. Commented Apr 24, 2018 at 15:53
  • 8
    As far as using a proper class, this is what tuples are partly designed to do - reduce the need for throw away classes that clutter stuff up. Commented Apr 24, 2018 at 16:01
  • But these aren't throwaway classes, and the code looks worse in this context. As a return type for a method it looks far more readable, but not so much as a parameter. Commented Apr 24, 2018 at 16:03
  • 1
    I agree with @DavidG. The flaw here is in looking at these classes as "throw-aways". The classes you bind to create a contract and self-document how your actions work. They are anything but "throw-away". Commented Apr 24, 2018 at 17:02
  • 2
    Can't you only have one FromBody param? The tuple thing would be kinda nice for having multiple params in the body without making a class for them. It would be a nice in-between for query params and a full-fledged body object. Commented Nov 9, 2018 at 20:46

2 Answers 2

44

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.

Sign up to request clarification or add additional context in comments.

Comments

17

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});
    }
}

2 Comments

Good one. Though not fully implemented. Issue I have found so far, no support for swagger, supports post method only, no tuple return, no support for json convertors
Interestingly, despite all its flaws, it is the only search result on nuget.org for tuple binder nuget.org/packages?q=tuple+binder So, nobody has tried to write a better one. Oxpecker (F# backend-to-frontend web framework) does support this, albeit in an indirect way github.com/Lanayx/Oxpecker/blob/… and github.com/Lanayx/Oxpecker/blob/….

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.