Sunday 28 September 2014

The difference when passing a reference type by or without the Ref keyword C#

Assumption : Reference type are always passed by reference in C#. 
Nota : the assumption becomes false if object is a value type ( int, float , struct ... so immutable ), and the difference is trivial.

Let's consider the following methods :

void Fill2(ref Object1 input)
void Fill3(Object1 input)

At a first glance, there is no difference between the 2 signatures because in the two cases, the parameter is passed by reference ( assumption). At the exit of those functions, the object should have some of its properties updated.


The main difference by using the Ref keyword actually stands in the method implementation as we will see in the example ( the source code is at the end of this post ).
In a few words :
- Ref guarantees that method will work on the same reference inside the method. So if the method handles a check if null against the object and creates a new instance if object is null , then sets some properties, Ref keyword ensures that the same reference is used during this process and the last updated values are available after the method call. If Ref keyword is not provided and the method creates a new instance, the reference is lost and the object remains as it was being before the method call.

Concrete example : Fill3 and Fill4 methods are the most relevant study case.

namespace Devenva.Demo.Solution.RefKeyword
{
       class Program
      {
        static void Main(string[] args)
        {
            {
               Object1 obj = new Object1() { Name = "Bob" };
               Console.WriteLine("Name before = " + obj.Name);
               Fill1(obj);
               Console.WriteLine("Name after = " + obj.Name);
            }

            {
              Object1 obj = new Object1() { Name = "Bob" };
              Console.WriteLine("Name before = " + obj.Name);
              Fill2(ref obj);
              Console.WriteLine("Name after = " + obj.Name);
            }

            {
             Object1 obj = null;
             Fill3(obj);
             Console.WriteLine(obj.Name);
            }

           {
              Object1 obj = null;
              Fill4(ref obj);
              Console.WriteLine(obj.Name);
           }

           Console.WriteLine("End");
          Console.ReadLine();
    }

         static public void Fill1(Object1 input)
        {
            Console.WriteLine("*** Apply Fill1 method ***");
            input.Name = "Peter";
        }
        static public void Fill2(ref Object1 input)
        {
            Console.WriteLine("*** Apply Fill2 method ***");
            input.Name = "Kely";
        }
        static public void Fill3(Object1 input)
        {
            Console.WriteLine("*** Apply Fill3 method ***");
            if (input == null) {
               input = new Object1();
            }
           input.Name = "Dylan";
        }
        static public void Fill4(ref Object1 input)
        {
            Console.WriteLine("*** Apply Fill4 method ***");
           if (input == null)
           {
              input = new Object1();
           }
            input.Name = "Greg";
        }
   }
   public class Object1
  {
    public string Name { get; set; }
  }

}
In the following example, an exception is thrown after the call of method Fill3 ( it's normal - refer to my previous commentary)

Sunday 7 September 2014

Two ways to pass a list or array from view to action in ASP.NET MVC4 : by javascript or by custom Modelbinder

In this post, I will talk about two tricks to pass a list from the View to the Action in asp.net MVC4.

Let's start with a asp.net mvc 4 basic project inside which I have created the view below :
 
 

The view /Test/Index seems very simple :
  • a table having 4 columns
  • on each row , the remove button deletes the current rows, remove it totally from the grid.
  • the Save button posts the current content of the table to the /Test/Action method on the server side.

The TestController contains the two actions :
  • Index : displays the table
  • Save1 and Save2 : manages the save process ( Save1 is used for the common way, Save2 for the use of a custom modelbinder)
public class TestController : Controller
{
public ActionResult Index()
{
List<TestModel> model = new List<TestModel>();
model.Add(new TestModel() { FirstName="Charly",LastName="Park.", Age=75 });
model.Add(new TestModel() { FirstName = "Stan", LastName = "Get.", Age = 80 });
model.Add(new TestModel() { FirstName = "Mick", LastName = "Bran.",Age=90 });
model.Add(new TestModel() { FirstName = "Georg", LastName = "Bras.",Age=65 });
return View(model);
}
[HttpPost]
public ActionResult Save1(List<TestModel> model)
{
return null;
}

[HttpPost]
public ActionResult Save2([ModelBinder(typeof(TestSaveModelBinder))] List<TestModel> model)
{
return null;
}
}

The model used is TestModel
namespace Devenva.Demo.Solution.ModelBinder.Models
{
public class TestModel
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int Age { get; set; }
}
}

The Index.cshtml view :

CASE 1 : the Javascript version

@model List<Devenva.Demo.Solution.ModelBinder.Models.TestModel>
<h2>Index</h2>
<script type="text/javascript" src="~/Scripts/jquery-1.8.2.js"></script>
<form id="frm_Test" method="post" action="/Test/Save1">
<table>
<thead>
<th>FirstName</th>
<th>LastName</th>
<th>Age</th>
<th></th>
</thead>
<tbody>
@{ int i=0;}
@foreach (var item in Model)
{
<tr>
<td><input type="text" name="Model[@i].FirstName" value="@item.FirstName" /></td>
<td><input type="text" name="Model[@i].LastName" value="@item.LastName" /></td>
<td><input type="button" class="remove" value="remove" /></td>
</tr>
i++;
}
</tbody>
</table>
<input type="submit" value="Save"/>
</form>
<script type="text/javascript">
$(function () {
$('.remove').click(function () {
$(this).parent().parent().remove();
});
});

</script>

If we do some research over the web, this is the common way to implement the table. In the tbody markup, we loop through the List of TestModel and we have to give a different name for each input to make it a unique element when the page is posted back to the server. This is the classical way to tell the mvc engine how data will processed.
Try to remove a row (for example the 2nd row) and save the form, the asp.net mvc is no longer able to parse our datas ( in the Save1 action, model is null). Why?
When removing a row, there was a gap in the index order ( here i=2 is missing ) so this case is not handled by the default modelbinder ( In few words, model binder is the component that manages the conversion of a html form data to a C# object, if the conversion goes well, the excpected object is implicitly mapped, otherwise an error is thrown or object is set to null ).
To prevent this behavoir, the view have to manage the list of index. This list should be updated each time a row is added or removed. This can be done easily by a javascript function. I will not focus on that function in this post.

CASE 2 : Using a custom modelbinder

@model List<Devenva.Demo.Solution.ModelBinder.Models.TestModel>
<h2>Index</h2>
<script type="text/javascript" src="~/Scripts/jquery-1.8.2.js"></script>
<form id="frm_Test" method="post" action="/Test/Save2">
<table>
<thead>
<th>FirstName</th>
<th>LastName</th>
<th>Age</th>
<th></th>
</thead>
<tbody>
@foreach (var item in Model)
{
<tr>
<td><input type="text" name="FirstName" value="@item.FirstName" /></td>
<td><input type="text" name="LastName" value="@item.LastName" /></td>
<td><input type="text" name="Age" value="@item.Age" /></td>
<td><input type="button" class="remove" value="remove" /></td>
</tr>
}
</tbody>
</table>
<input type="submit" value="Save"/>
</form>
<script type="text/javascript">
$(function () {
$('.remove').click(function () {
$(this).parent().parent().remove();
});
});

</script>

We have to notice that in the second version we don't take care of the uniqueness of the name of each input element.
The posted data looks like

We keep the view as simple as possible, the tricky part of the job is on the server side.

In the current case, the action public ActionResult Save(List<TestModel> model)
is not able to convert those data to a List<TestModel>
We need to build a custom model binder that will manage this specific stuff.
I have create the class TestSaveModelBinder :

namespace Devenva.Demo.Solution.ModelBinder.ModelBinders
{
public class TestSaveModelBinder : IModelBinder
{
static private string[] fieldSeparator =new string[]{ "&" };
static private string[] keyValueSeparator = new string[] { "=" };
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
HttpRequestBase request = controllerContext.HttpContext.Request;
var formStr = request.Form.ToString();
var listObject = ParseToModel(formStr);
List<TestModel> model = new List<TestModel>();
for (int i = 0; i < listObject.Length; i++)
{
model.Add(new TestModel() {
FirstName = listObject[i].Item1,
LastName = listObject[i].Item2,
Age = listObject[i].Item3
});
}
return model;
}
static private Tuple<string, string, int>[] ParseToModel(string input)
{
var fieldValues = input.Split(fieldSeparator, StringSplitOptions.None);
object[] properties = new object[fieldValues.Length];
for (int i = 0; i < fieldValues.Length; i++)
{
var value = fieldValues[i].Split(keyValueSeparator,StringSplitOptions.None);
properties[i] = value[1].ToString();
}
int dataNumber = properties.Length / 3;
var result = new Tuple<string, string, int>[dataNumber];

for (int i = 0; i < dataNumber ; i++)
{
var data = new Tuple<string, string, int>(properties[i].ToString(), properties[i + dataNumber * 1].ToString(),int.Parse( properties[i + dataNumber * 2].ToString()));
result[i] =data;
}
return result;
}
}
}

Working with modelbinder always starts from the posted data (controllerContext.HttpContext.Request.Form.ToString()).
The BindModel method implements the logic to parse the data to the type of the input parameter of the action ( List<TestModel> ).
Once the modelbinder was setup, we should tell the mvc engine to use it only that specific action:
[HttpPost]
public ActionResult Save2([ModelBinder(typeof(TestSaveModelBinder))] List<TestModel> model)
{
return null;
}

By putting a breakpoint on that method, we can see that model contains all the datas that we have posted.