Sunday, June 28, 2015

ASP.NET MVC Create Editable Views for Collections

In this post, I want to focus on a few common mistakes when creating editable views for collections in ASP.NET MVC. These mistakes are related to the ASP.NET MVC wiere format and are pretty common and a new ASP.NET MVC programmer can spend hours trying to figure out what's wrong.

If you are new to ASP.NET MVC wire format, here is a quick primer - It is basically about how your model or viewmodel collection objects are rendered in the views. Let's say you have the following class -

public class Person 
{
public int Id {get;set;}
public string Name {get;set;}
}


Your Controller would look like -

public ActionResult PersonAction(Person[] employeeList) { ... }


Your View takes an IEnumerable of the Person class and looks like the below -
@model IEnumerable<Person>


Now, this Person collection/array
Person employeeList[] = new Person[] {new Person() {Id=1, Name="person1" }, new Person() {Id=2, Name="person2"} }; 

should show up as below in the rendered HTML -
<input type='hidden' value='0' name='employeeList.Index'/>

<input type='text' value='1' name='employeeList[0].Id' />

<input type='text' value='person1' name='employeeList[0].Name' />
<input type='hidden' value='1' name='employeeList.Index'/>

<input type='text' value='2' name='employeeList[1].Id' />
<input type='text' value='person2' name='employeeList[1].Name' />


Note: This hidden index element is needed in case your list is unordered. For example, if your View supports deletion and addition of new collection items then the indexing can get out of order in a hurry.

So, what are the most overlooked things that might save you hours of debugging? Here goes -

  1. The "name" attribute of your HTML elements that render the collection properties must have the
    ActionParameterName.PropertyName signature
    -
    So, if your controller action method looks like
    public ActionResult GetList(Person[] employeeList)

    and a property of the Person class is Id, then the name of the input elements must be like
    <input type='text' value='1' name='employeeList[0].Id' />

    (As in the the underlined parts). And it goes without saying that this naming convention will be used for all properties that are to be rendered.
  2. The name of the hidden "index" element must have the ActionParameterName.Index signature - Again, if your controller action method looks like
    public ActionResult GetList(Person[] employeeList
    )
    then the name of the hidden index element will be like
    <input type='hidden' value='0' name=&#39employeeList.Index'/>

  3. The index of all array elements must be same as the value of the hidden "index" element - So, for rendering the first element of the employeeList array (the index is zero), the hidden "index" element will look like
    <input type='hidden' value='0' name='employeeList.Index'/> 

    (notice the value is 0) and the element that render the property Id will look like
    <input type='text' value='1' name='employeeList[0].Id' />

Tuesday, June 23, 2015

MVC4 Uncaught ReferenceError: $ is not defined

"Uncaught ReferenceError: $ is not defined"


This is a typical newbie error, specially when starting an ASP.NET MVC project. What's really going on is that the browser can't find the jquery file. But since  ASP.NET MVC automatically includes the javascript files as Script Bundles via the @Scripts.Render("~/bundles/"), we don't know why the jquery script file is not included even though it is present under References. 


Script.Render method basically allows automatic minification (compression) of javascripts and the Visual Studio IDE automatically adds some default bundles and placeholders for an ASP.NET MVC project.It's all nice and good but the problem is that the way ASP.NET MVC project is initially created, it makes it look like the references have already been added. The other equally confusing thing is the place ASP.NET MVC adds those script references by default. Unlike an ASP.NET Webforms application, the actual names of the javascript files don't appear in the aspx or cshtml files. So, they can't be found in the _Layout.cshtml or any other MVC *html file that is around.


So, what do we do? We go to the BundleConfig.cs file in the App_Start folder. Over there there is a method (that has already been created by the ASP.NET MVC project) that contains all of the references to the actual javascript files.


The javascript registration code in BundleConfig.cs file will look something like this -

bundles.Add(new ScriptBundle("~/bundles/jquery").Include(
                        "~/Scripts/jquery-{version}.js"));


Now the {version} part in the problematic part there. It doesn't seem to always work. Before I knew better, I thought it was a placeholder for us developers to fill in. But since the Visual Studio IDE doesn't give any indication how this can be fixed or even that something is wrong, a new developer trying to figure things out will never know what's going on.


But fear not, the fix is simple. Simply change the file name to the actual jquery file name. It's not the ideal solution but will work in a pinch -


bundles.Add(new ScriptBundle("~/bundles/jquery").Include(
                        "~/Scripts/jquery-2.8.2.js"));


And add the following to your _Layout.cshtml - @Scripts.Render("~/bundles/jquery")

And you are good to go! That's it. Now the script reference will show up in your browser as - src="/Scripts/jquery-2.8.2.js"

Another Victory! Now go have a beer!

Saturday, June 20, 2015

MVC4 - How to Bind Dropdowns lists and retain selected values during postbacks

I recently had a lot of trouble with dropdowns in MVC4. Either the dropdowns don't get populated, or they don't show the selected value, or they just don't postback the selected values. It's pretty straightforward to do these things in weakly typed Views but I wanted to make it all work with strongly typed Views because it results in a cleaner code in the long run.

So, without much ado, here is the code -

Your ViewModel (or Model depending on the architecture/approach) and helper classes look like the below -

//This class contains the keys, values and whatever other model related data that needs to be bound to the dropdown
public class DropDownItem {
public int DropDownKey  { get; set; }
public string DropDownValue  { get; set; }


//This class is an overly simplified ViewModel
public class DropDownTestViewModel
    {
public DropDownSelectedValue { get; set; } //This represents the actual selected value
public IList DropDownItemCollection  = new List(); //This represents the list of all options in the dropdown
}


//This is the simplified View code -
@Html.DropDownListFor(imf => Model.DropDownSelectedValue, new SelectList(Model.DropDownItemCollection.AsEnumerable(), "DropDownKey", "DropDownValye", Model.DropDownSelectedValue), " -- Please Select --", new { id = "ddl" + rowCount })


The code looks pretty straightforward but the way MVC4 is designed, it'll never work. The dropdownlist will neither show the selected value nor post it back. And it will not tell you what is wrong either!! :)

So, what is wrong here? After many iterations of writing and rewriting different variations of the code, I found that the problem is that the model items **MUST** be properties. So, to fix the above, we just need to add getters and setters as follows -

public IList DropDownItemCollection  = new List();


becomes this -
public IList DropDownItemCollection   { get; set; } //Initialize in constructor etc as needed


And hooray, the dropdown shows and posts back correct selected values and the world makes sense again. :)