Kendo TreeView indeterminate checkbox for remote datasource and ondemand loading

The task is to create a Treeview within a web application. The Treeview will contain more than 1k nodes in total and every node should have a checkbox to provide information about the check state of nodes within the tree. Checkboxes can have three states (checked, unchecked and indeterminate for parents where only some children are checked). I used Kendo UI Treeview component to solve the task. The problem was to get the indeterminate state of the checkboxes set without loading all the childnodes into the Treeview. For sure on server site the state information for every node has to be available.

My Solution:
Create a KendoUI Treeview with a checkbox template. Within that template add an attribute (here “data-indeterminate”) in case node is in indeterminate state. Additionally bind an event handler for DataBound (here “updateTree”).

@(Html.Kendo().TreeView()
    .Checkboxes(checkboxes =>
    {
        checkboxes.CheckChildren(true);
    })
    .LoadOnDemand(true)
    .DataTextField("Name")
    .Events(e => e.DataBound("updateTree"))
    .CheckboxTemplate("<input type='checkbox' name='checkedNodes' value='#= item.id #' #if(item.checked) { # checked # } if(item.indeterminate) { # data-indeterminate # } #/>")
    .DataSource(dataSource => dataSource.Read(read => read.Action("GetNodes", "TreeView")))
)

In the event handler for Treeview event DataBound find all input tags of type checkbox with attribute data-indeterminate and set the indeterminate state to true.

function updateTree() {
    $('input:checkbox[data-indeterminate]').each(function () {
        $(this)[0].indeterminate = true;
    });
};

This solution works pretty well for readonly Treeview.

Advertisements

Automapper Profile registration in Web Applications

Current Situation

While searching for a proper way to register AutoMapper Profiles in a ASP.NET Web Application I found mostly code snippets like this:

   1: private static Func<Type, bool> _validProfilesExpression = t => typeof(Profile).IsAssignableFrom(t) && t.GetConstructor(Type.EmptyTypes) != null;

   2:

   3: public static void Register()

   4: {

   5:     var profiles = AppDomain.CurrentDomain

   6:                            .GetAssemblies()

   7:                            .SelectMany(a => a.GetTypes().Where(_validProfilesExpression)

   8:                            .Select(Activator.CreateInstance).Cast<Profile>()).ToList();

   9:     Register(profiles);

  10: }

  11:

  12: private static void Register(List<Profile> profiles)

  13: {

  14:     Mapper.Initialize(configuration => profiles.ForEach(configuration.AddProfile));

  15: }

Register Profiles during Application start by querying the AppDomain for classes derived from AutoMapper.Profile and adding the found profiles to AutoMapper.

This looked like a proper solution. After deploying my solution too IIS7 I recognized the application is only working for a while until already available profiles are not  found anymore.

For some reason the current AppDomain contains all assemblies available in the bin folder of the application during the very first Application start. After the Applicationpool is recycled (by default after 20 minutes of idle time) the current AppDomain contains only a subset of the assemblies available in the bin folder of the application. For that reason only some profiles are registered during Application start what can result in missing profiles.

Solution

  • Subscribe to current AppDomain assembly load event and register Profiles available on the new loaded assembly.
  • Register profiles available on assemblies already available on the current AppDomain
   1: private static Func<Type, bool> _validProfilesExpression = t => typeof(Profile).IsAssignableFrom(t) && t.GetConstructor(Type.EmptyTypes) != null;

   2:

   3: public static void Register()

   4: {

   5:     // Apply handler for assembly load to register profiles

   6:     // available on the new loaded assembly

   7:     AppDomain.CurrentDomain.AssemblyLoad += CurrentDomainAssemblyLoad;

   8:

   9:     var profiles = AppDomain.CurrentDomain

  10:                            .GetAssemblies()

  11:                            .SelectMany(a => a.GetTypes().Where(_validProfilesExpression)

  12:                            .Select(Activator.CreateInstance).Cast<Profile>()).ToList();

  13:     Register(profiles);

  14: }

  15:

  16: private static void Register(List<Profile> profiles)

  17: {

  18:     Mapper.Initialize(configuration => profiles.ForEach(configuration.AddProfile));

  19: }

  20:

  21: private static void CurrentDomainAssemblyLoad(object sender, AssemblyLoadEventArgs args)

  22: {

  23:     if (args.LoadedAssembly.GetTypes().Any(_validProfilesExpression))

  24:     {

  25:         var profiles = args.LoadedAssembly.GetTypes().Where(_validProfilesExpression).Select(Activator.CreateInstance).Cast<Profile>().ToList();

  26:         Register(profiles);

  27:     }

  28: }

Update

Registering the profiles with “Mapper.Initialize()” is a wrong approach. Change the Register method to:

private static void Register(List<Profile> profiles)
{
    profiles.ForEach(Mapper.AddProfile);
}