Friday, October 15, 2010

MVC 2 selct with optgroup support Html Helper

We are current building an Asp.Net MVC 2 application and we had the need for an html helper extention that renders a select with the options grouped by a field. Unfortunately there is no native helper for optgroup html tag support in Asp.Net MVC 2. I have to udmit that this is no common case either so I can not blame the guys in Microsoft for that.

The upside for this is that Asp.Net code is opensource, so I took a sneak peak in the upcomming MVC 3 source code to see what the DropDownListFor code looked like.

Lets cut to the chase.
for all kinds of select html variations MVC uses the SelectListItem class. We just need to subcass it and add a new string property named GroupKey. Here is the code fot that.

public class GroupedSelectListItem : SelectListItem
{
public string GroupKey { get; set; }
}

Then i took the helper overloads for DropDownListFor, renamed them to DropDownGroupListFor, and changed the selectlist parameter type to IEnumerable<GroupedSelectListItem>.

public static MvcHtmlString DropDownGroupListFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, IEnumerable<GroupedSelectListItem> selectList)

Finaly we just need to change a for loop inside the "GetSelectData" private method in order to take optgroup into consideration. Here is how it looks like.

foreach (var group in selectList.GroupBy(i => i.GroupKey)) {
listItemBuilder.AppendLine(string.Format("<optgroup label=\"{0}\">", group.Key));
foreach (GroupedSelectListItem item in group) {
listItemBuilder.AppendLine(ListItemToOption(item));
}
listItemBuilder.AppendLine("</optgroup>");
}


As requested here is a usage example. Say we have the following list of vehicles in our Model.

.. and the entity class looks like this


Now in our View we add the following statement.

<%: Html.DropDownGroupListFor(model => model.VehicleId,
Lookups.Vehicles.Select(v => new GroupedSelectListItem() {
GroupKey = v.Group,
Text = v.Description,
Value = v.Id.ToString()
}),
"---- select Vehicle ----")%>


Note that Lookups is a static helper class that gets the collection we need to bind to the dropdownlist.

public class Lookups
{
public static ICollection<Vehicle> Vehicles {
get {
ICollection<Vehicle> vehicles = null;
using (DBEntities context = new DBEntities()) {
vehicles = context.Vehicles.ToList();
}
return vehicles;
}
}
}

After that, we use a simple linq selection in order to transform our source collection in the form of List of GroupedSelectListItem. This is the format that the DropDownGroupListFor helper understands

If everything went according to plan then we expect to see something like this in the output:



You can download the code here (Updated: February 2011)

Friday, February 12, 2010

Filesystem out of inodes

Before version 0.9.0 redmine was storing session data in files on the tmp directory of the server. These files are usually of small size but a lot of them are produced quite fast on a production system. By default, redmine never deletes these files. A cron job could be used to delete session files older than a month or so, but to configure that cron job, you have to know of the problem first.

The easiest way to find out is to wait until you start getting “No space left on device” messages on the system log.

There are actually two reasons for such a thing to happen. The filesystem could have no more blocks available (the usual case) or the filesystem could be out of inodes (the not so usual case).

Inodes are filesystem data structures that store basic information about a regular file, directory, or other file system object. For each file, the filesystem occupies at least one inode.

The default inode ratio on an ubuntu server as configured in /etc/mke2fs.conf is one inode for every 16Kbytes of data. So creating a lot files of smaller size could lead to having enough space on the filesystem but no more inodes to create new files or directories.

You can see the amount of free inodes using df -i
Filesystem            Inodes   IUsed   IFree IUse% Mounted on
/dev/sda1            1251712 1251712       0  100% /
tmpfs                 129512       2  129510    1% /lib/init/rw
udev                  129512     488  129024    1% /dev
tmpfs                 129512       3  129509    1% /dev/shm
/dev/sda6            1251712  148360 1103352   12% /usr
/dev/sda7            35291136   21862 35269274    1% /home
/dev/sda8            35241984   31055 35210929    1% /var

Unfortunately, the system libraries of linux do not give out distinct error messages for a filesystem being out of blocks or being out of inodes. The message is always “No space left on device”.

Starting from version 0.9.0, redmine stores session data in cookies on the client side, so this problem will not occur any more.

Tuesday, November 10, 2009

Redmine Bulk import for LDAP users

For one of our clients we are integrating Redmine in their infrastructure. Redmine is a flexible project management web application written on the Ruby on Rails framework.

Redmine includes functionality for authenticating users against Active Directory (and LDAP in general). With "on the fly user creation" users can authenticate with their AD username and password: a user will be created on first login.

The problem is that until a user authenticates for the first time on Redmine, she does not appear in the user list. This means that when a project manager needs to start a new project, she needs to create it, notify project members to log in and then assign specific roles to each one of them (developers, managers, testers etc.).

It would be much easier if the project Manager could immediately see the new users listed within Redmine and assign roles to them without waiting for them to log in for the first time. There is a patch for Redmine that provides exactly this functionality. Unfortunately, applying a patch to the Redmine code will certainly break the possibility for easy updates. Redmine is still under heavy development and having to merge future versions with old patches could be painful.

Ruby to the rescue!

The standard Rails application skeleton includes a set of useful scripts under the {application root}/script folder:

>> indice@hostname:/0.8-stable/script$ ls
about  breakpointer  console  dbconsole  destroy  generate
performance  plugin  process  runner  server
indice@hostname:/0.8-stable/script$

Starting the console script gives a ruby shell with all redmine application classes and environment available. The console also features history and autocompletion.

indice@hostname:/0.8-stable/script$ ./console
Loading development environment(Rails 2.1.2)
GLoc v1.1 running in development mode. Strings can be modified at runtime.
>> Redmine.name=> 
"Redmine"
>>


Having the console loaded, I could load the one and only Ldap authentication source from the Redmine database by calling:



A cool feature of the ruby language is the ability to easily manipulate classes on runtime. I can add a method to the AuthSourceLdap class by typing it on the console. The new method adds functionality to import all users from the AD Server in Redmine.


The search filter that should be applied on the Ldap database to select all users is just hardcoded in the script, but it does not make such a big difference since this attribute is expected to never change.

The method will immediately become available for all objects of the class that are already loaded as well. So this method is now also available on the @auth_method object I had already loaded from the database. Calling it imports all found users:

>> @auth_method.import=> {:skipped=>0, :found=>67, :imported=>67}

The scripts directory of every Ruby on Rails application also includes a "runner" script, with which ruby scripts can be called in the context of the application.

Finally I could make a little script that loads the authentication source object from the Redmine Database, adds an import method to it and immediately calls it to import all users from AD.

A simple cron job synchronises the Redmine User Database with Active Directory every five minutes by calling my ruby script through the Redmine runner:

* crontab -e*/5 * * * * /0.8-stable/script/runner  \/script-location/ldapimport.rb
> \/log-file-location/ldapimport.log 2>&1

Monday, November 9, 2009

Introducing the Blog of Indice

This is going to be the Blog of Indice.gr
 

Athens

Indice,
13 Kazani str, 11 526 Athens, Greece
e-mail: contact@indice.gr Αυτή η διεύθυνση ηλεκτρονικού ταχυδρομείου προστατεύεται από κακόβουλη χρήση. Χρειάζεται να ενεργοποιήσετε την Javascript για να τη δείτε.
Tlf: +306945293858 και +306942615932

Copenhagen

Indice,
Asminderødgade 6, 4tv, 2200 Κøbenhavn N,
Denmark
e-mail: contact@indice.gr Αυτή η διεύθυνση ηλεκτρονικού ταχυδρομείου προστατεύεται από κακόβουλη χρήση. Χρειάζεται να ενεργοποιήσετε την Javascript για να τη δείτε.
Tlf: +4560753926

BERLIN

Indice,
Rubensstrasse 43, 12159 Berlin,
Germany
e-mail: contact@indice.gr Αυτή η διεύθυνση ηλεκτρονικού ταχυδρομείου προστατεύεται από κακόβουλη χρήση. Χρειάζεται να ενεργοποιήσετε την Javascript για να τη δείτε.
Tlf: +3015155616682