Monday 10 November 2014

GoJS Tutorial: Using ASP.NET MVC Web API and Entity Framework to build an HTML5 Canvas Org Chart

Well, the title is a mouthful, but the project itself it pretty simple.

The samples included with GoJS all have a very simple model for where the data is, and that's in the HTML and JavaScript for the sample. In other words, they are all "client", and not "client / server". The purpose of the samples is to show how to do diagrams using GoJS with a minimum of fuss, so this is how it should be.
But, the whole point of a browser based application is that it actually stores some data somewhere on a server so you can retrieve the document later. So this article will build a simple ASP.NET MVC Web API server and a minimal org chart client.

Video and Text versions of this Tutorial

We start here with Part 1 of a 2 part video for this tutorial, followed by a read-along text version.

Here's what we're going to build:
Step 4

Building the Web API server

Start with Visual Studio 2012 and choose FIle > New Project.
New MVC Web API Project
and that will prompt for a Project Template. Pick Web API.
Web API Template
This will build a "ValuesController", which you can delete by opening the Controllers folder and right-click delete the ValuesController file.

Using Code First to define a Model

In the Models folder of my project I’ll add (right-click and Add > Class...) an Employee.cs file that contains the following class.
namespace OrgChartWebAPI.Models
{
  public class Employee
  {
    public int EmployeeId { get; set; }
    public int BossId { get; set; }
    public string Name { get; set; }
    public string Title { get; set; }
  }
}
Now do a Build.

Add the Web API Controller

Right-click on the Controllers Folder and select Add > Controller.
Name it EmployeesController, choose the Employee class, and pick <New data context> and give the data context a name (we use OrgChartContext).
New API Controller
If the Employee Model doesn't show up, you forgot to do the "Build" in the previous step.
Completing this will add a EmployeesController to the Controllers folder, and OrgChartContext will be added to the Models folder. It has added a connection string to the Web.Config file for a Local SQL database.
The API controller is coded to support
GET api/Employees
GET api/Employees/id
PUT api/Employees/id
POST api/Employees
DELETE api/Employees/id
and we now have a HTTP / REST Web API interface we can call from a web client to do CRUD (Create, Read, Update and Delete) operations on Employees.
If I run the program, I get an ASP.NET MVC Web API page that looks like this:
First Run
Note that if I add api/Employees to the application URL (http://localhost:49953/api/Employees in this case), it triggers the Web API to return all the employees stored in the database.
In this case, it returns these 2 characters:
[]
which is JSON notation for a null set of employees. (Also note that, as a side effect of doing this request, Entity Framework has now actually created the SQL database in the App_Data folder.)

Populating the database

To run the sample, we need to move the sample data from the orginal sample into the newly create SQL database. Without going into detail here, I used the Code First Migrations. See the Jason Zander blog entry for details. The code I added to Seed method is:
Employee cs = context.Employees.Add(new Employee { Name = "Corrado 'Junior' Soprano", Title = "The Boss", BossId = -1 });
context.SaveChanges();
Employee ts = context.Employees.Add(new Employee { Name = "Tony Soprano", Title = "Underboss", BossId = cs.EmployeeId });
context.Employees.Add(new Employee { Name = "Herman 'Hesh' Rabkin", Title = "Advisor", BossId = cs.EmployeeId });
context.SaveChanges();
Employee pw = context.Employees.Add(new Employee { Name = "Paulie Walnuts", Title = "Capo", BossId = ts.EmployeeId });
context.SaveChanges();
context.Employees.Add(new Employee { Name = "Ralph Cifaretto", Title = "Capo MIA", BossId = ts.EmployeeId });
context.Employees.Add(new Employee { Name = "Silvio Dante", Title = "Consigliere", BossId = ts.EmployeeId });
context.Employees.Add(new Employee { Name = "Bobby Baccilien", Title = "Capo", BossId = ts.EmployeeId });
context.Employees.Add(new Employee { Name = "Sal Bonpensiero", Title = "MIA", BossId = pw.EmployeeId });
context.Employees.Add(new Employee { Name = "Christopher Moltisanti", Title = "Made Man", BossId = pw.EmployeeId });
context.Employees.Add(new Employee { Name = "Furio Giunta", Title = "Muscle", BossId = pw.EmployeeId });
context.Employees.Add(new Employee { Name = "Patsy Parisi", Title = "Accountant", BossId = pw.EmployeeId });
Note you have to do SaveChanges() to be able to read back the EmployeeId for the BossId. And now peeking at the data using Server Explorer now shows:
employee table
Now running the program again and changing the URL to api/Employees again, I get this JSON data:
[{"EmployeeId":1,"BossId":-1,"Name":"Corrado 'Junior' Soprano","Title":"The Boss"},
{"EmployeeId":2,"BossId":1,"Name":"Tony Soprano","Title":"Underboss"},
{"EmployeeId":3,"BossId":1,"Name":"Herman 'Hesh' Rabkin","Title":"Advisor"},
{"EmployeeId":4,"BossId":2,"Name":"Paulie Walnuts","Title":"Capo"},
{"EmployeeId":5,"BossId":2,"Name":"Ralph Cifaretto","Title":"Capo MIA"},
{"EmployeeId":6,"BossId":2,"Name":"Silvio Dante","Title":"Consigliere"},
{"EmployeeId":7,"BossId":2,"Name":"Bobby Baccilien","Title":"Capo"},
{"EmployeeId":8,"BossId":4,"Name":"Sal Bonpensiero","Title":"MIA"},
{"EmployeeId":9,"BossId":4,"Name":"Christopher Moltisanti","Title":"Made Man"},
{"EmployeeId":10,"BossId":4,"Name":"Furio Giunta","Title":"Muscle"},{
"EmployeeId":11,"BossId":4,"Name":"Patsy Parisi","Title":"Accountant"}]

On to the Client

So the job now is to create a minimal GoJS OrgChart sample and get it running with this server. Since we're working with an ASP.NET MVC application here, I'm going to add a new Minimal View to the Home Views folder. Right-click on Views and pick Add -> View.
Create Minimal View
and add this code to the Controllers\HomeControler.cs file:
 public ActionResult Minimal() {
   return View();
 }
So, open Views\Home\Minimal.cshtml, it should look like this:
@{
   ViewBag.Title = "Minimal";
   Layout = "~/Views/Shared/_Layout.cshtml";
 }

<h2>Minimal Org Chart</h2>
Above the <h2>, add this script section:
@section Scripts {
<script type="text/javascript" src="/Scripts/go.js"></script>
<script type="text/javascript">

  window.onload = function init() {
    var $ = go.GraphObject.make;  // for conciseness in defining templates
    myDiagram = new go.Diagram("myDiagramDiv");
    myDiagram.initialContentAlignment = go.Spot.Center;
    myDiagram.model = go.GraphObject.make(go.TreeModel,
      {
        nodeKeyProperty: "EmployeeID",
        nodeParentKeyProperty: "BossID",
      });

    load();
  }
    
  function load() {
    var xmlhttp = new XMLHttpRequest();
    xmlhttp.open("GET", "/api/Employees/", false);
    xmlhttp.send();
    var nodeArray = JSON.parse(xmlhttp.responseText);
    myDiagram.model.nodeDataArray = nodeArray;
  }

</script>
}

<h2>Minimal Org Chart</h2>
<div id="myDiagramDiv" style="border: solid 1px blue; width: 800px; height: 400px">
</div>
and below the <h2>, add the <div> that is where GoJS will create the canvas.
The gojs.net website samples use <body onload="init()"> to execute the init function. With the way ASP.NET MVC works, we don't have immediate access to the <body> tag here, so we'll add "window.onload =" before the "function init()". (Note: there actually is a way in MVC to stick things in the body tag, but what I've done here is simpler.)

Using the Web API data

GoJS has Models to tell it how to transform data into graphs. Since an Org Chart is simple and each node can only have one parent, we use the simpler TreeModel. Each node (Employee) in the graph will have one link from another node (another Employee indicated by BossId).
So , we initialize the diagram's model to be a go.TreeModel with a nodeKeyProperty of "EmployeeId" and a nodeParentKeyProperty of "BossId". The code in init() that does that is:
  myDiagram.model = go.GraphObject.make(go.TreeModel,
    {
      nodeKeyProperty: "EmployeeId",
      nodeParentKeyProperty: "BossId"
    });
So now we need to read the "Employees" data from the server. We do that by doing an HTTP GET from the URL /api/Employees. The code in load() that does that is:
  var xmlhttp = new XMLHttpRequest();
  xmlhttp.open("GET", "/api/Employees/", false);
  xmlhttp.send();
  var nodeArray = JSON.parse(xmlhttp.responseText);
  myDiagram.model.nodeDataArray = nodeArray;
This reads the Employees from the Web API in JSON format and then parses that data into nodeArray. Then we tell the diagram's model about the nodeArray.
And... the call to XMLHttpRequest is synchronous here for simplicity. Doing a synchronous request in browser code is a bad thing. We'll fix that in Part 2 of the tutorial when we add the Create, Update and Delete operations.
So when you run the program now, it should look like this:
Minimal Org Chart Step 1
So, we now have a simple directed graph, but it doesn't look much like an org chart. We are still missing 3 things:
  1. A Layout. By default, GoJS uses a simple layout that gives every node a position if it doesn't have one.
  2. A Node Template. When GoJS doesn't have a Template for the node, it just uses the node's key.
  3. A Link Template. When none is provided, a simple directional link is displayed.
So let's add these one at a time to see the changes they make. First, we'll add a simple TreeLayout. In init(), before the call to load, add this:
myDiagram.layout =
  $(go.TreeLayout,
    {
      angle: 90
    });
and run the program again to see:
Org Chart with layout
So now we have the "tree", let's improve the links by adding a linkTemplate to describe the appearance of the links. In init(), before the call to load, add this:
       // define the Link template
      myDiagram.linkTemplate =
        $(go.Link, go.Link.Orthogonal,
          {
            corner: 5,
            relinkableFrom: true, relinkableTo: true
          },
          $(go.Shape, {
            strokeWidth: 2,
            stroke: "lightslategray"
          }));  
and you will see this:
Org Chart with orthogonal links
and finally, let's define a simple node template
      myDiagram.nodeTemplate =
        $(go.Node, go.Panel.Auto,
          $(go.Shape,
            {
              figure: "RoundedRectangle",
              fill: "lightsteelblue",
              stroke: "lightslategray"
            }),
          $(go.TextBlock,
            { margin: 3 },  // some room around the text
            // TextBlock.text is bound to Node.data.Name
            new go.Binding("text", "Name"))); 
Auto Panels fit a "main" element just around the other elements of the panel. The main element is usually the furthest back in the Z-order, i.e. the first element. In this case, the RoundedRectangle figure is the main element, and it gets wrapped around a TextBlock with a margin of 3.
And note that the value of the TextBlock's text is via a data binding to the "Name" in the nodeArray. (Remember the Employee class on the server had a "Name" property.)
And this nodeTemplate will change the graph to look like this:
Step 4
to add the Title, we need a second line of TextBlock. But we need a vertical panel (like a list box) to hold the Name and Title inside the go.Panel.Auto. So, we add a go.Panel.Vertical, and place both tof the go.Textblock definitions inside that, like this:
myDiagram.nodeTemplate =
$(go.Node, go.Panel.Auto,
  $(go.Shape,
  {
    figure: "RoundedRectangle",
    fill: "lightsteelblue",
    stroke: "lightslategray"
   }),
   $(go.Panel, go.Panel.Vertical,
     $(go.TextBlock,
       { margin: 3}, // some room around the text
       new go.Binding("text", "Name")
      ),
     $(go.TextBlock,
       { margin: 3}, // some room around the text
       new go.Binding("text", "Title"))
     )
   );
And this nodeTemplate will change the graph to look like this:
Step 4
and now everything will display correctly. (Note that I've left out the Employee Title. We'll get to that in a future tutorial.)

Full Minimal.cshtml listing

@{
  ViewBag.Title = "Minimal Org Chart";
  Layout = "~/Views/Shared/_Layout.cshtml";
}

@section Scripts {

  <script type="text/javascript" src="/Scripts/go.js"></script>
  <script type="text/javascript">

   window.onload = function init() {
      var $ = go.GraphObject.make;  // for conciseness in defining templates

      myDiagram = new go.Diagram("myDiagramDiv");
      myDiagram.initialContentAlignment = go.Spot.Center;

      // define the Node template
      myDiagram.nodeTemplate =
        $(go.Node, go.Panel.Auto,
          $(go.Shape,
            {
              figure: "RoundedRectangle",
              fill: "lightsteelblue",
              stroke: "lightslategray"
            }),
          $(go.TextBlock,
            { margin: 3 },  // some room around the text
            // TextBlock.text is bound to Node.data.name
            new go.Binding("text", "name")));
      // define the Link template
      myDiagram.linkTemplate =
        $(go.Link, go.Link.Orthogonal,
          {
            corner: 5,
            relinkableFrom: true, relinkableTo: true
          },
          $(go.Shape,
            {
              strokeWidth: 2,
              stroke: "lightslategray"
            }));
      // define the layout
      myDiagram.layout =
        $(go.TreeLayout,
          { angle: 90 });
      myDiagram.model = $(go.TreeModel,
      {
        nodeKeyProperty: "EmployeeId",
        nodeParentKeyProperty: "BossId"
      });

      load();
    }

    function load() {
      var xmlhttp = new XMLHttpRequest();
      xmlhttp.open("GET", "/api/Employees/", false);
      xmlhttp.send();
      var nodeArray = JSON.parse(xmlhttp.responseText);
      myDiagram.model.nodeDataArray = nodeArray;
    }
}

<h2>Minimal Org Chart</h2>
<div id="myDiagramDiv" style="border: solid 1px blue; width: 800px; height: 400px">
</div>

References

Jason Zander's blog entry My Favorite Features: Entity Framework Code First and ASP.NET Web API was a great resource when doing this tutorial.
IETF Draft for HTTP pro

10 comments:

  1. This article's pictures are not displayed.

    ReplyDelete
  2. Hi Jason,

    This is a great blog, I have a table that I am having a problem creating genealogy table for. My table is as follows:

    using MvcNOK.Controllers;
    using System;
    using System.Collections.Generic;
    using System.ComponentModel.DataAnnotations;
    using System.Linq;
    using System.Web;

    namespace MvcNOK.Models
    {
    public class Relationship
    {
    [Key]
    public int RelationshipId { get; set; }

    [Required]
    [Display(Name = "Family Type")]
    public int RTypeId { get; set; }

    [Required]
    [Display(Name = "Family Kinship")]
    public int KinshipId { get; set; }

    [Required]
    [Display(Name = "First Individual Last Name")]
    public int FirstIndividualId { get; set; }

    [Required]
    [Display(Name = "First Individual Given Name")]
    public int FirstGivenId { get; set; }

    [Required]
    [Display(Name = "First Individual Relationship")]
    public int FirstRelationshipId { get; set; }

    [Required]
    [Display(Name = "Second Individual")]
    public int SecondIndividualId { get; set; }

    [Required]
    [Display(Name = "Second Individual")]
    public int SecondGivenId { get; set; }

    [Required]
    [Display(Name = "Second Individual Realtionship")]
    public int SecondRelationshipId { get; set; }

    [Required]
    [Display(Name = "Relationship start date, The era is AD if after Christ birth or BC if before Christ era.")]
    public string Start { get; set; }


    [Required]
    [Display(Name = "Country of relationship started")]
    public int CountrieId { get; set; }

    [Required]
    [Display(Name = "State of relationship started")]
    public int StateId { get; set; }

    [Required]
    [Display(Name = "City of relationship started")]
    public int CitieId { get; set; }

    [Required]
    [Display(Name = "Relationship Source for start")]
    public string Source { get; set; }

    //[Required]
    [Display(Name = "Source Notes")]
    public string Notes { get; set; }

    //[Required]
    [Display(Name = "Relationship end date, The era is AD if after Christ birth or BC if before Christ era.")]
    public string End { get; set; }


    // [Required]
    [Display(Name = "Country of relationship ended")]
    public int DCountrieId { get; set; }

    // [Required]
    [Display(Name = "State of relationship ended")]
    public int DStateId { get; set; }

    // [Required]
    [Display(Name = "City of relationship ended")]
    public int DCitieId { get; set; }

    //[Required]
    [Display(Name = "Relationship Source for ending")]
    public string ESource { get; set; }

    //[Required]
    [Display(Name = "Source Notes")]
    public string ENotes { get; set; }

    public virtual Countrie Countries { get; set; }
    public virtual State States { get; set; }
    public virtual Citie Cities { set; get; }
    public virtual DCountrie DCountries { get; set; }
    public virtual DState DStates { get; set; }
    public virtual DCitie DCities { set; get; }
    public virtual RType RTypes { get; set; }
    public virtual Kinship Kinships { set; get; }

    public virtual Individual FirstIndividuals { get; set; }
    public virtual Individual SecondIndividuals { get; set; }
    public virtual Individual FirstGivenIndividuals { get; set; }
    public virtual Individual SecondGivenIndividuals { get; set; }
    public virtual FRole FirstRoles { get; set; }
    public virtual FRole SecondRoles { get; set; }
    }
    }

    Do you have any suggestions, it would be greatly appreciated.

    I look forward to hearing from you. My email is aswhalen@hush.com.

    Thanks

    A

    ReplyDelete
  3. Congratulations for your great site! Congratulations and good continuation!
    voyance gratuite en ligne par mail

    ReplyDelete
  4. I have read your blog its very attractive and impressive. I like it your blog.

    Dot Net Online Training

    ReplyDelete
  5. Nice! you are sharing such helpful and easy to understandable blog. i have no words for say i just say thanks because it is helpful for me.



    Dot Net Training in Chennai | Dot Net Training in anna nagar | Dot Net Training in omr | Dot Net Training in porur | Dot Net Training in tambaram | Dot Net Training in velachery







    ReplyDelete
  6. Please can anyone show the video for the same? GoJS Org Chart Editor (https://gojs.net/latest/samples/orgChartEditor.html) for ASP.NET

    I'm beginner

    Thank-you 😇

    ReplyDelete
  7. Please can anyone show the video for the same? GoJS Org Chart Editor (https://gojs.net/latest/samples/orgChartEditor.html) for ASP.NET

    I'm beginner

    Thank-you 😇

    ReplyDelete
  8. Thanks, this is generally helpful. Still, I followed step-by-step your method in thisDot net core web API tutorial

    ReplyDelete
  9. Thank you Anjan Sir.
    If you want to know more about Dot net core web API tutorial then, visit here at Technology Crowds.

    ReplyDelete

Angular Tutorial (Update to Angular 7)

As Angular 7 has just been released a few days ago. This tutorial is updated to show you how to create an Angular 7 project and the new fe...