Thursday, July 3, 2014

DevExpress Trees in ASP.NET

Today I figured out how to set up a DevExpress TreeList in ASP.NET in both web forms and MVC. Let's look at MVC first. I made this:

 
 

The first file to dress up to get this is Global.asax.cs. Mine looks like this:

using System;
using System.Web.Http;
using System.Web.Mvc;
using System.Web.Optimization;
using System.Web.Routing;
using DevExpress.Web.Mvc;
namespace DummyDevExpressApplication
{
   public class WebApiApplication : System.Web.HttpApplication
   {
      protected void Application_Start()
      {
         AreaRegistration.RegisterAllAreas();
         GlobalConfiguration.Configure(WebApiConfig.Register);
         FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
         RouteConfig.RegisterRoutes(RouteTable.Routes);
         BundleConfig.RegisterBundles(BundleTable.Bundles);
      }
      
      protected void Application_PreRequestHandlerExecute(object sender, EventArgs e)
      {
         DevExpressHelper.Theme = "Aqua";
      }
   }
}

 
 

Here and here, I bemoan struggling with CSS styling in MVC DevExpress implementations. Well, I figured out what I was missing today. The .GetStyleSheets content needs to go in _Layout.html.

<!DOCTYPE html>
<html>
<head>
   <meta charset="utf-8" />
   <meta name="viewport" content="width=device-width" />
   <title>@ViewBag.Title</title>
   @Styles.Render("~/Content/css")
   @Scripts.Render("~/bundles/modernizr")
   @Scripts.Render("~/bundles/jquery")
   @Scripts.Render("~/bundles/bootstrap")
   @RenderSection("scripts", required: false)
   @Html.DevExpress().GetScripts(
      new Script { ExtensionSuite = ExtensionSuite.NavigationAndLayout },
      new Script { ExtensionSuite = ExtensionSuite.HtmlEditor },
      new Script { ExtensionSuite = ExtensionSuite.TreeList },
      new Script { ExtensionSuite = ExtensionSuite.Editors },
      new Script { ExtensionSuite = ExtensionSuite.Scheduler }
   )
   @Html.DevExpress().GetStyleSheets(
      new StyleSheet { ExtensionSuite = ExtensionSuite.TreeList, Theme = "Aqua" }
   )
</head>
   <body>
      <div style="margin: 0 auto; width: 600px;">
         @RenderBody()
      </div>
   </body>
</html>

 
 

Here is the Index.cshtml view that @RenderBody above will resolve to:

@using (Html.BeginForm("Index", "Home", FormMethod.Post))
{
   @Html.Action("TreeListPartial")
   <input type="submit" value="Touch me." />
}

 
 

HomeController.cs summons that view. It follows. Here we see how we get back a list of keys (names of selected items) when submitting the form!

using System.Collections.Generic;
using System.Linq;
using System.Web.Mvc;
using DummyDevExpressApplication.Models;
namespace DummyDevExpressApplication.Controllers
{
   public class HomeController : Controller
   {
      public ActionResult Index()
      {
         return View();
      }
      
      [HttpPost]
      public ActionResult Index(FormCollection stuff)
      {
         List<string> selection = stuff["TreeList$Sel"].Split(' ').ToList();
         Session["selection"] = selection;
         return View();
      }
      
      [ValidateInput(false)]
      public ActionResult TreeListPartial()
      {
         List<HeavenlyBody> dataSource = Sun.Dependents();
         return PartialView("_TreeListPartial", dataSource);
      }
   }
}

 
 

_TreeListPartial.cshtml has the real meat and mechanics of the tree. The KeyFieldName specifies the getsetter on a model to use as the unique key and the ParentFieldName suggests which KeyFieldName value to consider a parent in the name of nesting any one object, otherwise side by side with its parent in a collection, as "of" its parent and within a tier beneath it.

@{
   var treeList = Html.DevExpress().TreeList(settings => {
      settings.Name = "TreeList";
      settings.CallbackRouteValues = new { Controller = "Home",
            Action = "TreeListPartial" };
      settings.AutoGenerateColumns = false;
      settings.KeyFieldName = "Name";
      settings.ParentFieldName = "Orbits";
      settings.Columns.Add("Name");
      settings.Columns.Add("AuInDistance");
      settings.Columns.Add("MilesInDiameter");
      settings.Columns.Add("DiscoveryDate");
      settings.SettingsPager.Visible = true;
      settings.SettingsSelection.Enabled = true;
   });
}
@treeList.Bind(Model).GetHtml()

 
 

Now, in web forms I have:

 
 

Default.aspx, my web form:

<%@ Page Title="Home Page" Language="C#" MasterPageFile="~/Site.Master"
      AutoEventWireup="true" CodeBehind="Default.aspx.cs"
      Inherits="DummyDevExpressApplication._Default" %>
<%@ Register Assembly="DevExpress.Web.ASPxTreeList.v13.1, Version=13.1.9.0,
      Culture=neutral, PublicKeyToken=b88d1754d700e49a"
      Namespace="DevExpress.Web.ASPxTreeList" TagPrefix="dx" %>
<%@ Register assembly="DevExpress.Web.v13.1, Version=13.1.9.0, Culture=neutral,
      PublicKeyToken=b88d1754d700e49a" namespace="DevExpress.Web.ASPxEditors"
      tagprefix="dx" %>
<asp:Content ID="BodyContent" ContentPlaceHolderID="MainContent" runat="server">
   <style type="text/css" media="all">
      #Tree {
         width: 600px !important;
      }
      #Tree_U div {
         width: 600px !important;
      }
      #Tree_U div:nth-child(2) {
         height: 300px !important;
      }
   </style>
   <dx:ASPxTreeList ID="Tree" ClientIDMode="Static" runat="server"
         AutoGenerateColumns="False" KeyFieldName="Name"
         ParentFieldName="Orbits">
      <Styles AlternatingNode-Enabled="True">
         <AlternatingNode Enabled="True">
         </AlternatingNode>
         <PopupEditFormWindowCloseButton CssClass="Hide" />
      </Styles>
      <Settings GridLines="Both" VerticalScrollBarMode="Auto"
            HorizontalScrollBarMode="Auto"/>
      <SettingsBehavior AllowSort="true" AutoExpandAllNodes="False" />
      <SettingsPager Mode="ShowPager" AlwaysShowPager="true" PageSize="13" />
      <SettingsSelection Enabled="True" />
      <Columns>
         <dx:TreeListTextColumn FieldName="Name" VisibleIndex="0">
         </dx:TreeListTextColumn>
         <dx:TreeListTextColumn FieldName="AuInDistance" VisibleIndex="1">
         </dx:TreeListTextColumn>
         <dx:TreeListTextColumn FieldName="MilesInDiameter" VisibleIndex="2">
         </dx:TreeListTextColumn>
         <dx:TreeListDataColumn FieldName="DiscoveryDate" VisibleIndex="3"
               Width="150">
         </dx:TreeListDataColumn>
      </Columns>
   </dx:ASPxTreeList>
   <asp:Button ID="Button1" runat="server" Text="Touch me." OnClick="Button1_Click" />
</asp:Content>

 
 

Default.aspx.cs, which is the code behind for Default.aspx:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.UI;
using DummyDevExpressApplication.Models;
namespace DummyDevExpressApplication
{
   public partial class _Default : Page
   {
      protected void Page_Load(object sender, EventArgs e)
      {
         Tree.DataSource = Sun.Dependents();
         Tree.DataBind();
      }
      
      protected void Button1_Click(object sender, EventArgs e)
      {
         List<string> selection = Tree.GetSelectedNodes().Select(node =>
               node.Key).ToList();
         Session["selection"] = selection;
      }
   }
}

 
 

In case you are wondering what Sun.Dependents() coughs up, here it is:

using System;
using System.Collections.Generic;
namespace DummyDevExpressApplication.Models
{
   public class Sun
   {
      public static List<HeavenlyBody> Dependents()
      {
         return new List<HeavenlyBody>()
         {
            new HeavenlyBody()
            {
               Name = "Mercury",
               AuInDistance = 0.4m,
               MilesInDiameter = 3032
            },
            new HeavenlyBody()
            {
               Name = "Venus",
               AuInDistance = 0.7m,
               MilesInDiameter = 7521
            },
            new HeavenlyBody()
            {
               Name = "Earth",
               AuInDistance = 1m,
               MilesInDiameter = 7926
            },
            new HeavenlyBody()
            {
               Name = "Luna",
               AuInDistance = 0.0025m,
               MilesInDiameter = 2159,
               Orbits = "Earth"
            },
            new HeavenlyBody()
            {
               Name = "Mars",
               AuInDistance = 1.5m,
               MilesInDiameter = 4222
            },
            new HeavenlyBody()
            {
               Name = "Phobos",
               AuInDistance = 0.00006m,
               MilesInDiameter = 14,
               DiscoveryDate = new DateTime(1877,8,18),
               Orbits = "Mars"
            },
            new HeavenlyBody()
            {
               Name = "Deimos",
               AuInDistance = 0.00016m,
               MilesInDiameter = 8,
               DiscoveryDate = new DateTime(1877,8,12),
               Orbits = "Mars"
            },
            new HeavenlyBody()
            {
               Name = "Ceres",
               AuInDistance = 2.77m,
               MilesInDiameter = 590,
               DiscoveryDate = new DateTime(1801,1,1)
            },
            new HeavenlyBody()
            {
               Name = "Jupiter",
               AuInDistance = 5.2m,
               MilesInDiameter = 88846
            },
            new HeavenlyBody()
            {
               Name = "Saturn",
               AuInDistance = 9.5m,
               MilesInDiameter = 74898
            },
            new HeavenlyBody()
            {
               Name = "Uranus",
               AuInDistance = 19.2m,
               MilesInDiameter = 31763,
               DiscoveryDate = new DateTime(1781,3,13)
            },
            new HeavenlyBody()
            {
               Name = "Neptune",
               AuInDistance = 30.1m,
               MilesInDiameter = 30778,
               DiscoveryDate = new DateTime(1846,9,23)
            },
            new HeavenlyBody()
            {
               Name = "Pluto",
               AuInDistance = 39.5m,
               MilesInDiameter = 1430,
               DiscoveryDate = new DateTime(1930,2,18)
            },
            new HeavenlyBody()
            {
               Name = "Haumea",
               AuInDistance = 43.1m,
               MilesInDiameter = 217,
               DiscoveryDate = new DateTime(2003,3,7)
            },
            new HeavenlyBody()
            {
               Name = "Makemake",
               AuInDistance = 45.3m,
               MilesInDiameter = 882,
               DiscoveryDate = new DateTime(2005,3,31)
            },
            new HeavenlyBody()
            {
               Name = "Eris",
               AuInDistance = 68m,
               MilesInDiameter = 1445,
               DiscoveryDate = new DateTime(2003,10,21)
            }
         };
      }
   }
}

 
 

Finally, here is my model. This file and the one above existing in both the web forms and MVC implementations.

using System;
namespace DummyDevExpressApplication.Models
{
   public class HeavenlyBody
   {
      public string Name { get; set; }
      public decimal AuInDistance { get; set; }
      public int MilesInDiameter { get; set; }
      public DateTime? DiscoveryDate { get; set; }
      public string Orbits { get; set; }
   }
}

a FormCollection may help reverse engineer what is coming back from a form post in an ASP.NET MVC application

This is some nasty stuff from MVC1 that you usually don't want to do.

[HttpPost]
public ActionResult Index(FormCollection formCollection)
{
   Dictionary<string, string> collection = formCollection.Keys.Cast<string>()
         .ToDictionary(key => key, key => formCollection[key]);
   Session["collection"] = collection;
   return View();
}

 
 

In just glancing at the HTML a DevExpress TreeList makes, I would not have been able to tell that TreeList$Sel (that is the name parameter on a hidden type input tag with an id parameter of TreeList_Sel) held the selections at the TreeList.

use nth-child in CSS

#Tree div:nth-child(2) {
   height: 600px !important;
}

Wednesday, July 2, 2014

kafka-zookeeper

....is, per this, "A high-level client library in Node.js for the Apache Kafka project with Zookeeper integration"

  • Apache Kafka is, per this, "A high-throughput distributed messaging system."
  • Apache ZooKeeper is, per this, "an effort to develop and maintain an open-source server which enables highly reliable distributed coordination."

Tuesday, July 1, 2014

using .Contains versus using .Any in C#

Given this class...

using System.Collections.Generic;
namespace ContainsExample.Models
{
   public class Planet
   {
      public string Name { get; set; }
      public List<string> Moons { get; set; }
   }
}

 
 

...this test will pass:

using System.Collections.Generic;
using System.Linq;
using ContainsExample.Models;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace ContainsExample.Tests
{
   [TestClass]
   public class ContainsTests
   {
      [TestMethod]
      public void TestAgainstPlanets()
      {
         List<Planet> planets = new List<Planet>()
         {
            new Planet()
            {
               Name = "Mercury",
               Moons = new List<string>(){}
            },
            new Planet()
            {
               Name = "Venus",
               Moons = new List<string>(){}
            },
            new Planet()
            {
               Name = "Earth",
               Moons = new List<string>(){ "Luna" }
            },
            new Planet()
            {
               Name = "Mars",
               Moons = new List<string>(){ "Phobos", "Deimos" }
            }
         };
         Planet planet = planets.Where(p => p.Moons.Contains("Deimos")).Single();
         Assert.AreEqual(planet.Name, "Mars");
      }
   }
}

 
 

However if we change the original class like so...

using System.Collections.Generic;
namespace ContainsExample.Models
{
   public class Planet
   {
      public string Name { get; set; }
      public List<Moon> Moons { get; set; }
   }
}

 
 

...and bring into the mix a second, new class like this...

namespace ContainsExample.Models
{
   public class Moon
   {
      public string Name { get; set; }
   }
}

 
 

...then we will need to use .Any instead of .Contains as seen here:

using System.Collections.Generic;
using System.Linq;
using ContainsExample.Models;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace ContainsExample.Tests
{
   [TestClass]
   public class ContainsTests
   {
      [TestMethod]
      public void TestAgainstPlanets()
      {
         List<Planet> planets = new List<Planet>()
         {
            new Planet()
            {
               Name = "Mercury",
               Moons = new List<Moon>(){}
            },
            new Planet()
            {
               Name = "Venus",
               Moons = new List<Moon>(){}
            },
            new Planet()
            {
               Name = "Earth",
               Moons = new List<Moon>(){ new Moon() { Name = "Luna" } }
            },
            new Planet()
            {
               Name = "Mars",
               Moons = new List<Moon>(){ new Moon() { Name = "Phobos" }, new Moon() {
                     Name = "Deimos" } }
            }
         };
         Planet planet = planets.Where(p => p.Moons.Any(m => m.Name
               == "Deimos")).Single();
         Assert.AreEqual(planet.Name, "Mars");
      }
   }
}

 
 

This second test passes as well!

In C#, you should see if a string may be cast to an int with TryParse instead of a try/catch.

This test will pass:

[TestMethod]
public void TestStringToIntegerCasting()
{
   int result = 0;
   string ate = "ate";
   bool judgement = int.TryParse(ate, out result);
   Assert.AreEqual(judgement, false);
   Assert.AreEqual(result, 0);
   
   string eight = "8";
   judgement = int.TryParse(eight, out result);
   Assert.AreEqual(judgement, true);
   Assert.AreEqual(result, 8);
}

 
 

.TryParse may be used with several other types too such as DateTime. There is going to be way to check to see if you may cast whenever you need to cast in lieu of just relying on a try/catch to figure it out. I used to do the try/catch thing because I didn't know any better. Now I know better. This touches on .IsAssignableFrom some.

get basic CRUD working at a DevExpress GridView in an MVC project

Following up on what I started this weekend I have gotten the canned CRUD stuff working within my application. I only really changed two files. The first one was _Layout.cshtml. I both stripped out a lot of the noise to dumb it down and I added some must have scripts which were not there by default. It now just looks like this:

<!DOCTYPE html>
<html>
<head>
   <meta charset="utf-8" />
   <meta name="viewport" content="width=device-width" />
   <title>@ViewBag.Title</title>
   @Styles.Render("~/Content/css")
   @Scripts.Render("~/bundles/modernizr")
   @Scripts.Render("~/bundles/jquery")
   @Html.DevExpress().GetScripts(
      new Script { ExtensionSuite = ExtensionSuite.NavigationAndLayout },
      new Script { ExtensionSuite = ExtensionSuite.HtmlEditor },
      new Script { ExtensionSuite = ExtensionSuite.GridView },
      new Script { ExtensionSuite = ExtensionSuite.Editors },
      new Script { ExtensionSuite = ExtensionSuite.Scheduler }
   )
</head>
   <body>
      <div style="margin: 0 auto; width: 90%;">
         @RenderBody()   
      </div>
   </body>
</html>

 
 

HomeController really changed. It now looks like this:

using System.Collections.Generic;
using System.Linq;
using System.Web.Mvc;
using DevExpress.Web.Mvc;
using DummyDevExpressApplication.Models;
using DummyDevExpressApplication.Utilities;
namespace DummyDevExpressApplication.Controllers
{
   public class HomeController : Controller
   {
      public ActionResult Index()
      {
         return View();
      }
      
      [ValidateInput(false)]
      public ActionResult GridViewPartial()
      {
         if (DevExpressHelper.IsCallback)
         {
            if (DevExpressHelper.CallbackArgument.Contains("STARTEDIT"))
            {
               string[] bits = DevExpressHelper.CallbackArgument.Split('|');
               Session["scope"] = bits[bits.Length - 1].Split(';')[0];
            }
         }
         return PartialView("_GridViewPartial", getPlanets());
      }
      
      [HttpPost, ValidateInput(false)]
      public ActionResult GridViewPartialAddNew([ModelBinder(typeof
            (DevExpressEditorsBinder))] Planet addition)
      {
         if (addition != null)
         {
            List<Planet> p = getPlanets();
            p.Add(addition);
            Session["planets"] = p.OrderBy(x =>
                  x.ClosestAstronomicalUnitDistanceFromSun).ToList();
         }
         return PartialView("_GridViewPartial", getPlanets());
      }
      
      [HttpPost, ValidateInput(false)]
      public ActionResult GridViewPartialUpdate([ModelBinder(typeof
            (DevExpressEditorsBinder))] Planet alteration)
      {
         string scope = Session["scope"] as string;
         if (scope != null && alteration != null)
         {
            List<Planet> p = getPlanets();
            for (int i = 0; i < p.Count; i++) if (p[i].Name == scope) p[i] = alteration;
            Session["planets"] = p.OrderBy(x =>
                  x.ClosestAstronomicalUnitDistanceFromSun).ToList();
         }
         return PartialView("_GridViewPartial", getPlanets());
      }
      
      [HttpPost, ValidateInput(false)]
      public ActionResult GridViewPartialDelete(string Name)
      {
         if (Name != null)
         {
            Session["planets"] = getPlanets().Where(planet => planet.Name
                  != Name).ToList();
         }
         return PartialView("_GridViewPartial", getPlanets());
      }
      
      private List<Planet> getPlanets()
      {
         List<Planet> planets = Session["planets"] as List<Planet>;
         if (planets == null)
         {
            planets = PlanetFactory.GetPlanets();
            Session["planets"] = planets;
         }
         planets = Session["planets"] as List<Planet>;
         return planets;
      }
   }
}

 
 

The GridViewPartialDelete previously had a System.Int32 variable named "Line" in it and Visual Studio, or perhaps JetBrains ReSharper, put a squiggly blue line under the word and when one moused over it some text popped up reading "Name 'Line' does not match rule 'Parameters'. Suggested name is 'line'." This was one of the more confusing things I bumped into. Why was there a "bad" name? It turns out that Line is a property on Antlr.Runtime.ICharStream which was the original model I picked through circumstance. In order to make the unique key value carry over into the method signature when this method was hit, I had to use the name of the property for the unique key (Name on Planet) as the name of this variable. What else was challeging? What if we are to edit a planet while changing its name which is its unique id? Well, we would need to know what planet id we just changed, correct? That won't be hanging off the alteration variable in the GridViewPartialUpdate method's signature. There is a callback event on any GridView and it should get triggered on the C# side whenever one clicks the Edit, New, or Delete buttons (links). It will happen, in the case of an Edit button click, just before a form for editing an item such as this is displayed:

 
 

Here, one may look to DevExpressHelper.CallbackArgument to get a string of gunk with pipe-separated notes on what is going on. The note at the very end of the string is the unique key for the item to be edited in the case of an edit. Also in the case of an edit STARTEDIT appears midway within all of the nastiness. An example of what is in DevExpressHelper.CallbackArgument is:

c0:KV|88;['Mercury','Venus','Earth','Mars','Ceres','Jupiter','Saturn','Uranus','Neptune','Pluto'];
      GB|18;9|STARTEDIT5|Earth;

 
 

That is really all I have to show off in this blog posting. I will mention that while I was figuring this out that I also figured out how to wire up client-side events. One could do so by adding a line like this to _GridViewPartial.cshtml:

settings.ClientSideEvents.BeginCallback = "BeforeCallback";

 
 

This means there has to be a "BeforeCallback" in JavaScript like so:

function BeforeCallback(s, e) {
   console.log(s);
   console.log(e);
}

 
 

I made a file for this stuff called solarsystemscripts.js and I referenced it in _Layout.cshtml like so:

<script src="/Scripts/solarsystemscripts.js"></script>

 
 

Having said that, I couldn't really find something spiffy to do with "BeforeCallback" though I did confirm that it would run before the if (DevExpressHelper.IsCallback) check on the C# side. My stylesheet styling still needs a lot of work, huh?