So Tuple is a good thing. It is very useful in scenarios where you need to return multiple values from a method but do not intend to create a dedicated DTO for that sole purpose. It is not of structure type either which means it is passed by reference. Additionally, it is a read-only placeholder as all the values that you want to return must be set in the constructor. The properties Item1, Item2... are read-only properties. That indicates that the purpose of the Tuple class is to serve purely as a read-only DTO.
This brings us to another point. Given that it is quite useful, you may want to use Tuple for binding to ASP.NET MVC views. However, given its read-only design, it will be problematic during non-HttpGet operations. There is a way though. Custom ModelBinder.
I created 2 methods in my controller for testing Tuple - one for Get and other for Post.
[HttpGet]
public ActionResult TestTuple()
{
Tuple t = new Tuple("test", 123);
return View(t);
}
[HttpPost]
public ActionResult TestTuple(Tuple tuple)
{
return new EmptyResult();
}
The view part is straightforward:
@model Tuple
@{
ViewBag.Title = "TestTuple";
}
@Html.BeginForm("TestTuple", "Home", FormMethod.Post){
@Html.EditorFor(m => m);
input type="submit" value="Test Tuple"/
}
If you post the form, you get following exception:
No parameterless constructor defined for this object. Object type 'System.Tuple`2[[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]'.
Quite logical. Lets write a custom model binder and try to create Tuple from posted values.
public class CustomModelBinder : System.Web.Mvc.DefaultModelBinder
{
protected override object CreateModel(ControllerContext controllerContext, System.Web.Mvc.ModelBindingContext bindingContext, Type modelType)
{
if (modelType == typeof(Tuple))
{
Tuple t = new Tuple(bindingContext.ValueProvider.GetValue("Item1").AttemptedValue,
int.Parse(bindingContext.ValueProvider.GetValue("Item2").AttemptedValue));
return t;
}
return base.CreateModel(controllerContext, bindingContext, modelType);
}
}
And assign the same in App_Start method of Global.asax.
ModelBinders.Binders.DefaultBinder = new CustomModelBinder();
Everything works now :). Similar custom model binding steps can be applied wherever standard binding does not work.