Wednesday, December 30, 2009

Restructuring ASP.Net MVC app

So the issue is, you have a very large ASP.Net MVC app with many controllers and actions and don't know (or do) on how to restructure it for better maintenance.

This article contains some thoughts around what my experiences were with building a large scale app with 10K+ lines of jQuery and 266 Actions dispersed over 98 Controllers.

In the beginning:

Yes God created .... no I created a relatively small scale Mvc app based on a very limited set of requirements, more like a XP experience or learn as you go. The user community did not know what they wanted and the prototype was based on a missed conception of what the business' core day-to-day focus was (of course we did not know this at the time). There were only a few controllers and they were small.

Then we discovered the jQuery Grid by Tony Tomov (awesome free plug-in) which supports a ton of ajax features, so we decided to use it for all of our lists and also for a lot of CRUD functions. This very quickly expanded the controllers to a very large size. At this time I decided to not split the controllers as it might introduce bugs. For example an Account has a New, Update, Delete, Create and Edit action, and now it has a List and CUD action (2 actions) added for each grid. Since the Account page has 9 tabs with 13 grids it now has an added 26 actions on that single AccountController controller and then some for other ajax type functions.

So then very quickly, as the customers saw the app, new requirements were added and the controllers grew even bigger.

The decision then came as to what to do and how to refactor the code. I ran this past a colleague of mine to find out what he thinks: Large controller with all the logically related actions together (yes all 39 actions at some point) or to split them into smaller but many different controllers? It seemed that keeping it all in one controller was a good decision since when you need to fix something on the account page with all the tabs with grids on them you know you need to go to the AccountController controller, right!? However, what if the requirements change and these grids need to be also displayed in other places, that's right, like on the Contact's maintenance page that has the same tabs as the Account page (only filtered by the contact for that account). Now I have a duplicate set of actions that perform essentially an identical function to those on the Account's controller. That is when it became clear that the controllers' actions should not be arranged according to how the screens look like but to adhere to the SRP.

Lets look at an example. The Account has a list of Calls, Contacts, Contracts and Lessons Learned. Even though these are tabs on the account's page, they need to be self contained. So this is how they were disseminated and re-arranged to adhere to SRP:

AccountController: New, Create, Edit, Update, Delete, List actions
CallController: New, Create, Edit, Update, Delete, List actions
ContactController: New, Create, Edit, Update, Delete, List actions
ContractController: New, Create, Edit, Update, Delete, List actions
LLController: New, Create, Edit, Update, Delete, List actions

The AccountController had a very large constructor since we used IoC Castle Windsor to inject the Repository (DAO type functions) into the constructor; to support all the DAO functions for calls, contacts, contracts and lessons learned. Now the constructor of each controller is very small and only cares about the Repositories it needs. In addition, to mention one example, the Calls tab on the account and contact screens now share the same action defined on another controller specifically to support the jqGrid functions for calls, the CallGridController with its 2 (yes only 2) actions ListCallsForjqGrid() and SaveCallForjqGrid(), rather than duplicating it. Now another developer knows that no matter where the jqGrid is displayed for calls, they can always find it on the CallGridController.

These are just some of the benefits of creating smaller controllers. Feel free to comment and share your ideas and experiences.

Wednesday, October 21, 2009

How to set the windows path in Windows 7


How to set the path in Windows 7.

Goal:

Setting the windows command path in Windows 7

Additional information:

Modifying the path statement will enable an MS-DOS window opened in Microsoft Windows as well as older programs to locate files that may be required to run the program.

In the old MS-DOS environment we used the PATH= command, located in the autoexec.bat file. Additional information about the MS-DOS path command that is still usable in Windows 2000 and Windows XP can be found on our path command page, additional information about the MS-DOS command can be found on our set command page.

See our dictionary path definition for additional information about this term and related definitions.

Answer:

The path is now managed by Windows 7 and not the autoexec.bat or autoexec.nt files. To change the system environment variables, follow the below steps.

  1. From the desktop, right-click My Computer and click Properties.
  2. In the System Properties window, click on the Advanced tab.
  3. In the Advanced section, click the Environment Variables button.
  4. Finally, in the Environment Variables window (as shown below), highlight the Path variable in the Systems Variable section and click the Edit button. Add or modify the path lines with the paths you wish the computer to access. Each different directory is separated with a semicolon as shown below.

    C:\Program Files;C:\Winnt;C:\Winnt\System32

Windows enviromental path settings

Friday, October 9, 2009

Do I have a 64-bit or 32-bit CPU (Processor)?

How to check what kind of processor you have (CPU) and what operating system can I run on it?

An X64 has a 64 bit microprocessor, whereas an X86, derived from the old 8086 PC, has a 32 bit microprocessor. A 32 bit machine and/or OS (operating system) is limited to about 3.55GB of memory, and an X64 is limited to about 17,179,869,184 gigabytes, 17,179,869 terabytes, or about 16 exabytes of RAM. Also a 32-bit OS cannot run an 64-bit OS.

So, how to check your CPU:

Since you cannot run WIndows7-64x on a 32-bit machine lets look at windows 7. From the Start icon on the toolbar select Control Panel. Look for Hardware and SOund and select View devices and printers. Double-click yur computer (will be labeled with your machine name). A Properties window pops-up with two tabs, select the Hardware tab. Look for a name starting with ACPI, with Type of Computer. If it says ACPI x64-based PC you know you have a 64-bit machine, CONGRATULATIONS! You can now install a 64-bit OS like Vista-64 or Windows7-64 and run any of your old software as the new 64-bit OS "knows" (is compatible) how to run the 32-bit apps.

What about memory?

If you want to install more than 4GB or RAM you will need to have a 64-bit CPU and also run a 64-bit OS otherwise you are wasting your money.

Monday, September 21, 2009

How to stop and prompt user when closing browser

Scenario:

For some reason you want to stop the user from closing the browser window or from redirecting away from the current page, based on some condition.


The solution:

window.onbeforeunload = function(event) {
return true;
}

This will prompt you for input (like the JavaScript confirm pop-up menu). There is no way to override the text though.

Some concerns:
This works great but it may not be what you want all the time, as no matter if you click a hyperlink, hit submit, etc will prompt the user. So here is an example where only when any input value on the screen has changed, I want to prompt the user only then and also only if the user decides to redirect from the page. It should not prompt the user when I try and save/delete or update the page:

var dataUpdated = false;

$(document).ready(function() {
SetupChangeEventOnAllInputElementsAndSetGlobalFlag();
})

function SetupChangeEventOnAllInputElementsAndSetGlobalFlag() {
$(':input').change(function() {
dataUpdated = true;

window.onbeforeunload = function(evt) {

var e = e || window.event;

// For IE and Firefox
if (e) {
if (DetermineSubmit(evt) == true)
return;
else
e.returnValue = 'You have not saved your changes - OK to ignore or Cancel to remain on this page?';
}
else {
// For Safari
if (DetermineSubmit(evt) == true)
return
else
return 'You have not saved your changes - OK to ignore or Cancel to remain on this page?';
}
}

});

}

//Determine if the original event is a submit button/control that triggered the window unload
function DetermineSubmit(evt) {
if ($(evt.explicitOriginalTarget).is(':submit')) {
return true;
}

return false;
}

Thursday, September 17, 2009

jQuery disable button intercepts Request Object

Scenario: Hijack all my save buttons via the script below to override the value, from Save to Saving...., and then also disabling the button so they cannot hit it 1000 times.

$(document).ready(function() {
form.find(':submit').each(function() {
$(this).attr("disabled", "disabled").val("Saving....");
});
});

The Issue: I have several submit type buttons on the page, like Save, Sand and Proceed, Delete, etc, so I use the Request.Form object to see what button was pressed to decide what to do. Well, the disable of the button by jQuery removes it from the Reuquest stream, even though it is only disabled after the click event has occurred, how strange.

The Solution: I don't know what the solution is other than removing the disable as below. Any ideas?
$(document).ready(function() {
form.find(':submit').each(function() {
$(this).val("Saving....");
});
});

Thursday, September 10, 2009

Why does F10 (continue) in Visual Studio not work?

Scenario:

I'm debugging a (web) project in Visual Studio 2008. It is breaking and the first break point only, and thereafter when i hit F10 it ignores and subsequent break points and runs all the way through. Why is this happening? Couple of pointers before we discuss the solution

  • I have tried 'clean solution'.
  • Other breakpoints sometimes(!) skipped, even in the same method
  • Make sure that you're in Debug mode not in Release.
  • There could be an exception while executing the instruction in question. Try enabling all exceptions to break into debugger and check.
The solution may be:

Tools -> Options -> Debugging -> General -> Un-check off Enable Just My Code.

Wednesday, September 9, 2009

ASP.NET MVC with jQuery Grid

Incorporating the jQuery Grid With ASP.NET MVC

Scenario: You have an ASP.NET MVC application and don't want to custom build smart controls like the ASP.NET GridView, ListView, etc, that support sorting and paging, as well as filtering and searching for data, and all of this using Ajax.

Solution: The jQuery Grid plug-in. What tools/plug-ins do I need?

  • jQuery version 1.3 and up
  • jqGrid version 3 and up (this post references version 3.4.4)
  • A strong cup of coffee

In the past you had to also add the jQuery modal and table row drag-and-drop plug-ins, but now the jqGrid team has made it easy and you can select to roll those into your download.

So where do I even start? Well, I am glad you asked.


Before we just jump in and start with the steps needed, let me just briefly summarize what you will need to do:

    Download the jQuery JavaScript files and plug-ins
  1. Create your ASP.NET MVC project in Visual Studio 2008 and add the three controller actions, one to render the account view, another to get the Ajax request's list of contacts, and lastly to support the update and delete operations of the contact.
  2. Create your own jQuery file that will contain your contact's jQuery Grid.
  3. Define the HTML needed to render the jqGrid (2 lines of HTML only!)

    1. Download the plug-in.

    It may seem obvious to some, but start downloading your plug-ins from these locations and save them to your hard-drive:

    jQuery

    http://docs.jquery.com/Release:jQuery_1.3.2

    jqGrid

    http://www.trirand.com/blog/?page_id=6 (select the models from Base and Editing only)

    2. Now create a standard ASP.NET MVC project.

    In short the jqGrid will make Ajax calls to your MCV controller actions. The application now has a HomeController and should have at least an Index action. In this article we will be demoing how to display data, so the Index will suffice to render our page, but we will be creating another action that will be responsible for rendering the content we need for our grid-list Ajax call later.

    What we will be showing in this demo is an Account's page that has several tabs on it. One of the tabs will contain the list of all contacts for this account. In stead of just showing a list of someting, in this example you will be exposed to filtering a list, in this case by account, and showing how this can be done with the jqGrid (look out for the "postData" property of the jqGrid definition later in this post).

    3. Add CSS and JavaScript files to project.

    Before we start building the app, let’s find a place to store our CSS and jQuery files/plug-ins in our ASP.NET MVC project. For this I like to create a folder under the UI (my MVC project folder's name):

    a. Content - contains all the CSS content, my own custom files as well as the jQuery Grid's and possibly other plug-in CSS files. This will also have an Images sub-folder that the CSS files’ image paths will need to point to, so you may have to do some find-replace-all to change the image paths in the CSS files to whatever your folder structure is.

    b. Scripts - All my own and the jQuery, jqGrid and possibly other JavaScript files.

    Solution Explorer Folder Structure

    In this example there are a large amount of jQuery custom files for a CRM application, hence the CRM folder. Wherever minification of the scripts was important, a Min sub-folder was created that would be rendered in a production and/or QA environment where the requirement to debug scripts was not necessary. The jQuery Base folder contains all jQuery files as well as sub-folders for all jQuery plug-ins called Plugins. When the plug-in itself may contain more than one js file, like in this example the jqGrid, a separate sub-folder was created under the Plugins folder with the plug-in’s name, like jQuery.grid. Of course you may not want to create a folder structure with this level of detail, and is completely up to you, none of the plug-ins have any requirement either way.


    4. Hook-up your files to the HTML page.

    Now you need to let your web application's Index page know that it needs to load your scripts for the jQuery and jqGrid. There are other, and arguably better ways to do this, especially if you have a large number of plug-ins, but for now simply load them like this in your HTML header:

    FYIThe first entry is a link to the CSS file. The jqGrid you downloaded came with a CSS file, you need to create a link to it. The second are the two js files, be sure to specify the jQuery-1.3.2 file before the jqGrid file as the jqGrid is dependent on the jQuery-1.3.2 file (JavaScript files are downloaded in sequence by our browser engine).

    5. Define the jqGrid.

    Now you are ready to define your jqGrid; provide a JSON object that will let the jqGrid know what columns to show, should it support paging, sorting, what is the URL to get the data and what is the URL to POST data back, and many other properties. Place this definition in your header, but since I have about a few of these grids and did not want to clutter the HTML header I created a separate js file that I link to.


    CRMScriptingAccountFileLocation

    We can get fancy here with drop downs, loading drop down dynamically, custom formatting and all kinds of built-in validations. We will not be getting into this in this post. Remember this is in its own js file, so no need for script tags:


    SetupJqGridListOfAccountChildrenCall

    Before we see what the definition of the grid looks like, we decided that we would group all the jqGrids into one function, SetupJqGridListOfAccountChildren. This function contains a call to twelve other functions, each one responsible for the definition of its own grid. One of these is our very own list of contact’s grid. You of course do not have to do this, especially if you only have one grid to display, in this demo we display twelve grids on one page! I kow…..keep it to yourselves.

    One thing you may take away from this is that when you define the properties of the grid there is some repetition and passing in the parms shown here may (or not) make your maintenance easier, especially if you cal the same grid in different situations, with different URLs (list contacts with a different controller and action for example), no need to change the hard-coded properties in the gird or create two very similar or identical grids, just pass in a different URL, etc.

    function SetupJqGridListOfAccountChildren()

    Now lets see what the actual jQuery Grid definition looks like. Note you may decide to use an XML formatted file, in this demo we went with a JSON formatted object. See the jqGrid documentation for a sample of this.

    function jqGridAccountContact(item, listURL, editURL, $rows, hideGrid) {

    var lastsel;

    var ignoreAlert = false;

    $(item)

    .jqGrid({

    url: listURL,

    postData: { accountId: $('fieldset#AccountDetails #Id').val() },

    datatype: "json",

    colNames: ['Id', 'First Name', 'Last Name', 'Phone', 'Title'],

    colModel: [

    { name: 'Id', index: 'Id', hidden: true, hidedlg: true },

    { name: 'FirstName', index: 'FirstName', width: 40 },

    { name: 'LastName', index: 'LastName', width: 40 },

    { name: 'PhoneNumber', index: 'PhoneNumber', width: 80 },

    { name: 'Title', index: 'Title', width: 80 }

    ],

    jsonReader: {

    root: "List",

    page: "CurrentPage",

    total: "TotalPages",

    records: "TotalRecords",

    userdata: "Errors",

    repeatitems: false,

    id: "0"

    },

    rowNum: $rows,

    rowList: [5, 10, 20, 50, 200],

    imgpath: jQueryImageRoot,

    pager: $(item + 'Pager'),

    recordtext: 'Contact(s)',

    sortname: 'Id',

    viewrecords: true,

    sortorder: "asc",

    altRows: true,

    loadComplete: function() {

    $(item).setColProp('EditUrl', { editoptions: { title: 'Edit this record', align: 'right', color: 'red'} });

    $(item + ' tr.jqgrow td:contains("Decommissioned")').css({ 'color': '#FF0000' })

    .parent().css({ 'background-color': '#EFEFEF' });

    },

    loadError: function(xhr, st, err) { if (!ignoreAlert) { alert(ajaxErrorMessage); } },

    loadBeforeSend: function(xhr) {

    if ($('#snames').val() == 'Id')

    if (isNaN($('#sval').val())) {

    alert('Sorry! the value for Id must be numeric');

    ignoreAlert = true;

    return false;

    }

    },

    search: true,

    searchdata: {},

    caption: "List of all Contacts",

    editurl: editURL + '?accountId=' + $('fieldset#AccountDetails #Id').val() + '&rows=' + $rows + '&page=1',

    hiddengrid: hideGrid,

    height: 215,

    width: 724

    })

    .navGrid(item + 'Pager',

    { refresh: true, edit: true, add: true, del: true, search: false }, //options

    {height: 280, width: 500,

    reloadAfterSubmit: true,

    modal: true,

    processData: 'Updating the Contact info...',

    editCaption: 'Edit a Contact',

    afterSubmit: function(response, postdata) {

    return SetupResponseMessage(item, response, "The Contact has been successfully updated.")

    }

    }, // edit options

    {height: 280, width: 500,

    reloadAfterSubmit: true, //need the new generated id

    modal: true,

    processData: 'Adding the new Contact...',

    addCaption: 'Add a Contact',

    afterSubmit: function(response, postdata) {

    return SetupResponseMessage(item, response, "The Contact has been successfully created.")

    }

    }, // add options

    {height: 120, width: 320,

    reloadAfterSubmit: true,

    afterShowForm: function(item) {

    $('#eData', item).focus();

    },

    afterSubmit: function(response, postdata) {

    var errors = eval('(' + response.responseText + ')').Errors.Errors;

    if (errors) {

    $('#FormError>td', '#TblGrid_' + item).text(errors);

    $('#FormError>td>ul', '#TblGrid_' + item)

    .css({ 'list-style-type': 'disc', 'list-style-position': 'outside' });

    $('#FormError', '#TblGrid_' + item).show();

    return [false, errors];

    }

    return true;

    }

    }, // del options

    {} // search options

    )

    .navButtonAdd(item + 'Pager', {

    caption: "",

    title: "Select Columns",

    buttonimg: jQueryImageRoot + '/column_Select.gif',

    position: 'last',

    onClickButton: function() { $(item).CRM_SetColumns(); }

    })

    ;

    }

    This looks scary, I know, but once you have a template you can copy and re-use it. You can also with jQuery extensions define it once, simply extend it as needed for customization. For example, we have dozens of lookup tables that we map using the jqGrid, we only have ONE, and simply change the URL as needed.

    Let’s break this down somewhat. There are many more properties you can specify to tune your columns’ properties, like alignment, etc, but for brevity will not be getting into it.

    · The function parameters:

    The function parameters

    · The URL property:

    The URL property

    · The postData:

    The postData

    Define any additional data you want to post back. In this example since I do not want to or can hardcode the account’s ID as part of the URL, I use jQuery to find it and add it to the postData. This is critical otherwise the MVC action will fail since it is expecting an accounted value.

    · The dataType:

    datatype: "json",

    The data type of the returned object, in this example it will be a JOSN serialized object, could be XML also. As a side note you could generate the static HTML table server-side if you waned to and have an option to point the jqGrid to it and all of the available paging and sorting, etc, will come out of the box, however, this requires that ALL data be loaded down to the client; if you have 500 contacts, all 500 will need to be specified in the HTML. I never use this option.

    · The colNames:

    colNames: ['Id', 'First Name', 'Last Name', 'Phone', 'Title'],]

    Simply a list of all the header labels. The number of header labels MUST match the number of properties you specify for “colModel”, otherwise jqGrid will not be able to render your grid and will throw a run-time alert. These names do not have to match the names you want to use for sorting and searching that is what the “name” and “index” properties are for in the “colModel” definition. See the jqGrid documentation for a sample of this.

    · The colModel:

    colModel: [

    This where you define the CSS, validation and operations properties for each column. The available set of properties is enormous. Only a few are shown here, only name and index need to be specified, you could ignore the rest and rely on defaults. In case you were wondering, yes you can protect fields, hide some and show them only when editing the record, create SELECT tags with static or dynamically (loaded from the database) lists, validate input, etc, etc, etc. No need to worry just about everything imaginable is supported with callback functions to create your own. It is the index property that is used when sorting data, so make sure it is right and can be recognized by the server. See the jqGrid documentation for a sample of this.

    · Jsonreader

    jsonReader: {}

    Optional, and can use it if you want to override the default JOSN properties for the JSON serialized DTO that will be passed back to the grid. In this example the property that will contain the actual list of data is called “root”. The “page”, “total” and “records” are used to support paging. Again entirely optional, but if not specified you need to make sure your DTO property names conform to the jqGrid definitions.

    · Overriding default properties (again optional):

    rowNum: $rows, rowList: [5,10,20,50,200],

    · All optional, but are very useful:

    1. rowNum: How many rows to display by default (one of the parms to the function)

    2. rowList: The tag.

    4. pager: Important, the HTML tag ID of the

    that contains the paging controls. Not sure why this was done this way, but as you will see later, you need to specify where the grid needs to be rendered in your HTML.

    5. recordtext: The text to display next to the counter for the number of rows that exist (not the number of rows retrieved or loaded into the grid!)

    6. sortname: The default sort column

    7. sortorder: “asc” well ascending of course J

    8. altRows: highlight alternate rows

    · Event loadComplete:

    loadComplete: function()

    One of many, this event fires once all the required data for the grid has been downloaded, before the grid is being rendered. In this example, although the column “EditUrl” does not exist shows a way some CSS can be passed into the jqGrid to change its color to red, align it right and give it a title. It also looks for any row that has “Decommissioned” and makes it red. It lastly also changes that text’s background color too. The possibilities are endless; you can pass in CSS or ATTR properties as key-value pairs as part of the “editOptions” property.

    · Paging, Sorting, editing….

    .navGrid()

    Included this, that although completely optional is important if you want to enable/disable certain operations. Like for example here the grid will allow the user to refresh the list, edit/update a contact, add a new contact, delete one and even search for a contact. The code definitions that follow in the code sample above can get a bit involved and not necessary to discuss here at this time, but is basically used to define how to perform the CRUD operations.

    6. Now let’s define the HTML required to get this working.

    " height="52" width="717">

    That’s right, two lines of HTML! The first to specify the table name of the grid and the second the

    container for the paging and where the other CRUD icons will appear. Remember the “item” parm in the “jqGridAccountContact” function? Well that’s the name for the “item” parm: “ListAccountContacts”. I prefer the naming convention for the pager to simply append Pager to the table name. Don’t ask why the jqGrid needs this, I honestly do not know. Well, that is all the HTML you will ever have to write. HA, that’s great you say, well which brings us to the next step, which is to hook up all this fancy stuff to the server that has to retrieve the data, perform a search if requested, convert the data to a serializable JSON object, and if requested also perform delete/update operations. To support delete/update operations we define a different MVC action to keep it clean (SRP).

    7. Define the action to render the Account’s page.

    public ActionResult Edit(int accountId)

    8. Define the action to retrieve the list of contacts:

    public JsonResult ListAllContactsForAccount(int accountId, int page, int rows, string sord, string sidx,

    string search, string searchField, string searchOper,

    string searchString)

    {

    JsonListDTO<ContactDTO> dto;

    switch (sidx)

    {

    case "Id":

    sidx = "l.id";

    break;

    case "FullNameUrl":

    sidx = "FirstName";

    break;

    case "ActivityStatus":

    sidx = "l.ActivityStatus";

    break;

    case "Title":

    sidx = "l.Title";

    break;

    }

    #region Validate input data

    IAccount account = Repository<IAccount>.Get(accountId);

    if (account == null)

    throw new ArgumentException("The account for which data has been requested does not exist.");

    #endregion

    #region If all validation passed, get the data and return Json data

    //Reset current page to 1 if the rows to display per page has changed

    if (TempData["rowsToDisplay"] != null && (int) TempData["rowsToDisplay"] != rows)

    page = 1;

    //Get all the contacts for this

    IEnumerable<ContactDTO> contacts = _contactRepository.GetAllAccountContacts(account, sord, sidx, rows, page);

    dto = SetupJsonDTOList(page, contacts, _contactRepository.NoOfRecordsRetrieved.GetValueOrDefault(),

    CalculateTotalNoOfPages(_contactRepository.NoOfRecordsRetrieved.GetValueOrDefault(),

    rows),

    _contactRepository.Message);

    TempData["rowsToDisplay"] = rows;

    return Json(dto);

    #endregion

    }

    This may seem like a lot, but it is quite remedial. The first thing you want to do it reformatted the “sidx”, the column to sort by if needed. Since we use NHibernate maps, the DAO that fetches the data used an alias called “l”, short for list, hence the “l.” notation. So for example if “sidx” is “Title” it is converted to “l.Title”, and in my NHibernate DAO’s HQL query then simply maps the title to the Contact alias “l”, thus NHibernate generates

    select l.FirstName, l.LastName, l.Title…. from Contact as l where l.AccountId = 1

    Notice the “JsonListDTO” generic object definition. You may select to use an anonymous definition; in this example the type of DTO that may be serialized must be specified.

    Then get the account: IAccount account = Repository<IAccount>.Get(accountId);

    If it does not exist, throw an exception.

    Note the method returns a JsonReult, using the MVC built-in Json conversion to create the JSON serialized object.

    Why a DTO? Well I am glad you asked. As with many examples, they are overly simplified and “haacked” together. The “Contact” entity has an “Account” property that because of recursion issues in the Json conversion cannot be serialized and converted directly (the account has list of contacts and contact has an account property). So in stead use NHibernate to refactor the list of contacts into a “dumb” DTO, that returns a DTO properties without any complex getters, just strings in this example, very light-weight, only retrieve what you need. You can create overloaded constructors for more scenarios.

    using System;

    [Serializable]

    public class ContactDTO

    {

    public ContactDTO(int id, string firstName, string lastName, string title, string phoneNumber, int totalRows)

    {

    Id = id;

    FirstName = firstName;

    LastName = lastName;

    Title = title;

    PhoneNumber = phoneNumber;

    TotalRows = totalRows;

    }

    public virtual int Id { get; set; }

    public virtual string FirstName { get; set; }

    public virtual string LastName { get; set; }

    public virtual string Title { get; set; }

    public virtual string PhoneNumber { get; set; }

    public int TotalRows { get; set; }

    }

    An important note you is that the property names MUST match those of the “colModel” “name” and index” jqGrid properties.

    jqGrid HTML View

    As you can see form the example above, I have one HTML page that is very busy indeed. So busy I had to cut off some of the tabs. Behind most tabs is a jQuery grid. In one particular instance it has 5 grids. They perform very well.

    Couple of pointers:

    • · The difference between traversing the object tree to get data, especially when using LINQ is not very practical in a surprising high number of instances. During the project the grid loaded very slowly. Once the LINQ queries were replaced by DTOs and light-weight HQL or ICriteria NHibernate queries, the performance increased exponentially.

    • · To create the update/delete functionality takes more server-side code, none more required on the client-side. All that is necessary for the jqGrid to work on the client has already been completed. I recommend creating another action on the HomeController that receives the parms that support CRUD operations fully, like the “oper” parm that is posted back together with the “Id” and other contact data. The “oper” parm needs to be evaluated by the server to determine what operation is requested. Then load the contact with the new data and save it to the database, and you’re done:

    if (String.Compare(oper, "Edit", true) != 0)

    • · To render server-side validation messages in the modal dialog (if that is the option you selected for CRUD, there are inline and cell-edit options as well), create the list of errors in your action and place them in the DTO’s “Errors” property (see jsonReader’s userdata properties).


    I sincerely hope that this has been helpful and will get you started with the jQuery Grid quickly. Please let me know what you thought of this post and if you would like to see more specific examples. Any feedback and comments will be appreciated and will help us all to share pertinent and useful information; no need to re-invent the wheel all over again :-)