Sunday, 9 August 2015

Tuple in ASP.NET MVC

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. 

No comments:

Post a Comment