Using DHTMLX Gantt Chart with ASP.NET and REST API

| Comments (11)

Today, we’ll talk about creating a Gantt chart application with ASP.NET and REST API on the server side. We will make use of ASP.NET MVC 5 web platform and Web API 2 Controller for REST API to create a Gantt application. To organize communication with the database, we will use the Entity Framework. We will build our application with the help of the MS Visual Studio 2015 IDE.

And now, let’s start by running Visual Studio and creating a new project.

Step 1. Creating a New Project

Open the File menu tab and choose: New -> Project. Then select ASP.NET Web Application and give it a name. We’ll name it gantt-rest-net for our example:

vs_project

Select an Empty project among available templates and check MVC and Web API checkboxes below the list of templates:

select_template

Now, we need to install dhtmlxGantt using NuGet. Run the following command in the Package Manager Console:

Install-Package DHTMLX.Gantt

Step 2. Adding Models, Views, and Controllers

At this step we have three main tasks:

  • Creating Models, special objects that will represent data in the Gantt chart
  • Creating Views that will visualize the data
  • Creating Controller that will process the requests that come from the user and run the needed logic. It’ll call a particular view to generate a necessary HTML for the request

We’ll start from creating a controller.

Creating a Controller

Call the context menu for the Controllers folder and choose Add->Controller.

creating_controller

In the opened window select MVC 5 Controller -> Empty and name a newly added controller “HomeController”.

Our controller has the Index() method of the ActionResult class by default, so it doesn’t require any additional logic. All we need to do is to add a view for it:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
 
namespace gantt_rest_net.Controllers
{
    public class HomeController : Controller
    {
        // GET: Home
        public ActionResult Index()
        {
            return View();
        }
    }
}

Creating a View

In the Views folder find the Home directory. Right click on it to call the context menu and select Add -> View.

creating_view

Name the new view “Index”, then open the newly created view and put the following code into it:

@{
    ViewBag.Title = "Index";
}
<script src="~/scripts/dhtmlxgantt.js"
type="text/javascript"></script>


<link rel="stylesheet"
href="~/content/dhtmlxgantt/dhtmlxgantt_terrace.css" />
<div id="gantt_here" style="width: 100%; height: 500px;"></div>
 
<script>
   (function () {
        // specifying the date format
        gantt.config.xml_date = "%Y-%m-%d %H:%i";
        // initializing gantt
        gantt.init("gantt_here");
 
        // enabling data loading
        gantt.load("/api/data");
        // initializing dataProcessor
        var dp = new gantt.dataProcessor("/api/data/");
        // and attaching it to gantt
        dp.init(gantt);
        // setting the REST mode for dataProcessor
        dp.setTransactionMode("REST");
    })();
</script>

In the above code we have done the following:

  • set a container for the Gantt chart
  • specified the dates format to load data from the server side
  • initialized the Gantt chart
  • enabled data loading
  • initialized dataProcessor in the REST mode to work with the server side

The server side will be implemented a bit later. Now, you can run the application and see that the Gantt chart is rendered on the page:

adding_gantt

Creating Models

There are two types of data representation in Gantt: Tasks shown as horizontal bars and Links between them. Thus, we should create two models.

Task Model

Right-click on the Models folder to call the context menu and select Add->Class.

creating_model

Name the created class “Task”, then open it and add the necessary properties:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
 
namespace gantt_rest_net.Models
{
    public class Task
    {
        public int id { get; set; }
        public string text { get; set; }
        public DateTime start_date { get; set; }
        public int duration { get; set; }
        public double progress { get; set; }
        public int parent { get; set; }
    }
}

Link Model

To create the Link model, create the Link class in the Models folder and add the following obligatory properties:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
 
namespace gantt_rest_net.Models
{
    public class Link
    {
        public int id { get; set; }
        public int source { get; set; }
        public int target { get; set; }
        public string type { get; set; }
    }
}

Step 3. Configure DataBase Connection

As we have mentioned earlier, we’ll use the Entity Framework for working with the database. To install it, run the following command in the Package Manager Console:

Install-Package EntityFramework

To use the Entity Framework functionality, we need to apply the following namespace:

using System.Data.Entity;

Creating Context

Context represents a session with the DataBase and allows loading and saving data.

Call the context menu for the Models folder and select Add->Class. The new class will be called “GanttContext” and will have the following content:

using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Web;
 
namespace gantt_rest_net.Models
{
    public class GanttContext: DbContext
    {
        public DbSet<Task> Tasks { get; set; }
        public DbSet<Link> Links { get; set; }
    }
}

Adding Records to Database

The Entity Framework can automatically create a database when application runs. We should specify that a database should be dropped and re-created whenever the model changes.

First, we should create a database initializer. To do so, we need to add a new class in the App_Start folder that will be inherited from the DropCreateDatabaseIfModelChanges class. Let’s call it “GanttContextInitializer”.

In this class we’ll redefine the Seed() method to add some test data. Then we’ll add a collection of entities into the context using the AddRange() method.

The full code of the GanttContextInitializer class is given below:

using gantt_rest_net.Models;
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Web;
 
namespace gantt_rest_net.App_Start
{
  public class GanttContextInitializer: DropCreateDatabaseIfModelChanges<GanttContext>
    {
        protected override void Seed(GanttContext context)
        {
            context.Tasks.AddRange(new List<Task>
            {
                new Task { id = 1, text = "Test Event 1",
                    start_date = new DateTime(2016, 5, 26), duration = 4 },
                new Task { id = 2, text = "Test Event 2",
                    start_date = new DateTime(2016, 5, 25), duration = 6 },
                new Task { id = 3, text = "Test Event 3",
                    start_date = new DateTime(2016, 5, 27), duration = 8 },
                new Task { id = 4, text = "Test Event 4",
                    start_date = new DateTime(2016, 5, 31), duration = 2 },
                new Task { id = 5, text = "Test Event 5",
                    start_date = new DateTime(2016, 5, 26), duration = 7 },
            });
 
            context.Links.AddRange(new List<Link>
            {
                new Link { id = 1, source = 1, target = 4, type = "0" },
                new Link { id = 2, source = 3, target = 4, type = "1" },
            });
        }
    }
}

Open the Global.asax file. It contains code that runs on the application start.

global_asax

Add the necessary namespace and the code line that will set Initializer for our context into theApplication_Start() method:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
using System.Web.Security;
using System.Web.SessionState;
using System.Web.Http;
using System.Data.Entity;          
using gantt_rest_net.App_Start;      
namespace gantt_rest_net
{
    public class Global : HttpApplication
    {
        void Application_Start(object sender, EventArgs e)
        {
            Database.SetInitializer(new GanttContextInitializer());             AreaRegistration.RegisterAllAreas();
            GlobalConfiguration.Configure(WebApiConfig.Register);
            RouteConfig.RegisterRoutes(RouteTable.Routes);
        }
    }
}

Adding Response Helpers

Helpers will form answers on CRUD requests. Let’s create the Helpers folder and add a class named GanttResponseHelper. Place the following code into the newly created class:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Http.Results;
 
namespace gantt_rest_net.Helpers
{
    public class GanttResponseHelper
    {
        public static Dictionary<string, string> GetResult(string action, int? tid)
        {
            var res = new Dictionary<string, string>();
            res.Add("action", action);
            if (tid != null)
                res.Add("tid", tid.ToString());
            return res;
        }
    }
}

The GetResult() method will generate an appropriate response for Data Processor according to its parameters. Each response will contain the name of an action.
There is also an optional parameter tid. It will be used for “insert” actions when database specifies a new id for the newly inserted entity.

Step 4. Creating API for Loading and Editing Data

Adding object containing Gantt data

Gantt holds both tasks and links within a single object. Let’s create a controller that will be responsible for the loading tasks and links data into the Gantt chart.

Open a context menu for the Controllers folder and select Add -> Controller.

Choose the Web API 2 Controller -> Empty and call a new controller “DataController”. Then, fill it with the following code:

using gantt_rest_net.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;
 
namespace gantt_rest_net.Controllers
{
    public class DataController : ApiController
    {
        private string _dateFormat = "yyyy-MM-dd HH:mm";
        private GanttContext db = new GanttContext();
 
        [HttpGet]
        public IHttpActionResult Get()
        {
            var events = new List<object>();
            foreach (var item in db.Tasks)
            {
                events.Add(new
                {
                    id = item.id,
                    text = item.text,
                    start_date = item.start_date.ToString(_dateFormat),
                    duration = item.duration,
                    progress = item.progress,
                    parent = item.parent
                });
            }
 
            return Json(new { data = events, links = db.Links });
        }
    }
}

This code will create an object with data for a Gantt chart. It will contain a list of events and a list of links. The dates of events should be converted into appropriate strings.

Task Controller

Now, let’s add one more controller named TaskController, that will provide CRUD API for the Task.

We will describe the logic of each request inside of try-catch blocks to handle any errors that might occur when the database is modified:

using gantt_rest_net.Helpers;
using gantt_rest_net.Models;
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;
 
namespace gantt_rest_net.Controllers
{
    public class TaskController : ApiController
    {
        private GanttContext db = new GanttContext();
 
        [HttpPost]
        public IHttpActionResult Post(Task task)
        {
            try
            {
                if (ModelState.IsValid)
                {
                    db.Tasks.Add(task);
                    db.SaveChanges();
                    return Json(GanttResponseHelper.GetResult("inserted", task.id));
                }
            }
            catch (Exception) { }
            return Json(GanttResponseHelper.GetResult("error", null));
        }
 
        [HttpDelete]
        public IHttpActionResult Delete(int id)
        {
            try
            {
                var task = db.Tasks.Find(id);
                db.Tasks.Remove(task);
                db.SaveChanges();
                return Json(GanttResponseHelper.GetResult("deleted", null));
            }
            catch (Exception) { }
            return Json(GanttResponseHelper.GetResult("error", null));
        }
 
        [HttpPut]
        public IHttpActionResult Put(int id, Task task)
        {
            try
            {
                if (ModelState.IsValid)
                {
                    task.id = id;
                    db.Entry(task).State = EntityState.Modified;
                    db.SaveChanges();
                    return Json(GanttResponseHelper.GetResult("updated", null));
                }
            }
            catch (Exception) { }
            return Json(GanttResponseHelper.GetResult("error", null));
        }
    }
}

The code of TaskController includes the following types of requests:

  • POST request means that a new item needs to be inserted into the database
  • PUT request updates an existing record
  • DELETE request goes for deleting

The GET request wasn’t used here, since Gantt loads tasks and links together. A common GET request is described within the DataController code.

All actions return a JSON response containing the type of the performed operation or “error” if something went wrong.

Notice that response for the insert action also contains a database id of the new record. It will be applied on the client side so that the new item could be mapped to the database entity.

Link Controller

We can create the Link controller the same way as the Task controller. It will provide CRUD API for Link and include the code as in:

using gantt_rest_net.Helpers;
using gantt_rest_net.Models;
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;
 
namespace gantt_rest_net.Controllers
{
    public class LinkController : ApiController
    {
        private GanttContext db = new GanttContext();
 
        [HttpPost]
        public IHttpActionResult Post(Link link)
        {
            try
            {
                if (ModelState.IsValid)
                {
                    db.Links.Add(link);
                    db.SaveChanges();
                    return Json(GanttResponseHelper.GetResult("inserted", link.id));
                }
            }
            catch (Exception) { }
            return Json(GanttResponseHelper.GetResult("error", null));
        }
 
        [HttpDelete]
        public IHttpActionResult Delete(int id)
        {
            try
            {
                var link = db.Links.Find(id);
                db.Links.Remove(link);
                db.SaveChanges();
                return Json(GanttResponseHelper.GetResult("deleted", null));
            }
            catch (Exception) { }
            return Json(GanttResponseHelper.GetResult("error", null));
        }
 
        [HttpPut]
        public IHttpActionResult Put(int id, Link link)
        {
            try
            {
                if (ModelState.IsValid)
                {
                    link.id = id;
                    db.Entry(link).State = EntityState.Modified;
                    db.SaveChanges();
                    return Json(GanttResponseHelper.GetResult("updated", null));
                }
            }
            catch (Exception) { }
            return Json(GanttResponseHelper.GetResult("error", null));
        }
    }
}

Like in case of TaskController, in the code above we’ve described the following types of requests:

  • POST request means that a new item should be inserted into the database
  • PUT request updates an existing record
  • DELETE request is used for deleting

Actions return a JSON response with the type of the operation or “error”.

Configuring Routes for API

Now we should add custom routes to our controllers. These routes will map incoming requests to specific handlers.

Open App_Start -> WebApiConfig and add routes for Task, Link, and Data into it.

webapiconfig

Pay attention that these routes should be placed before the default route.

The resulting code should look similar to this:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.Http;
 
namespace gantt_rest_net
{
    public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
            config.MapHttpAttributeRoutes();
 
            config.Routes.MapHttpRoute(
                name: "Task",
                routeTemplate: "api/data/task/{id}",
                defaults: new { controller = "Task", id = RouteParameter.Optional }
            );
 
            config.Routes.MapHttpRoute(
                name: "Link",
                routeTemplate: "api/data/link/{id}",
                defaults: new { controller = "Link", id = RouteParameter.Optional }
            );
 
            config.Routes.MapHttpRoute(
                name: "Data",
                routeTemplate: "api/data",
                defaults: new { controller = "Data" }
            );
 
            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional }
            );
        }
    }
}

The Register() method is set in the WebApiConfig class by default, it configures HttpConfiguration and sets API routes. So, we just added the necessary routes into it.

Now everything is ready. Run the application and the fully-fledged Gantt should appear on the page:

ready_gantt_dotnet

That’s it. We’ve created a full-functioned Gantt chart. We have spent more efforts as compared to some of our previous examples, but it is worth it. Keep checking our blog and we’ll keep providing you with detailed recipes for using our products in different ways.

download gantt

Comments

  1. bilal hassan July 11, 2017 at 2:38 pm

    I am getting invalid link type on each button link.

    • Aras Kairys (DHTMLX team) July 11, 2017 at 3:00 pm

      Hi Bilal,
      Can you please specify what links you mean? Everything works fine for us.

      • Bilal Hassan July 12, 2017 at 9:10 am

        Hi Aras,

        I was trying to use this without entityframework and get an invalid link type error on each button click,can you give me a download link to this code example, I would greatly appreciate it.

  2. Bilal Hassan July 12, 2017 at 10:25 am

    Hi Aras,
    Sorry works perfectly fine, had a small issue which is fixed now.

  3. julie December 13, 2017 at 10:32 pm

    on developpement gannt call localhost:54517/api/data and all works perfectly.
    When i deploy on IIS, i have an issue :
    gannt call ipadress/api/data and it’s not works.
    this route works : ipadress/Website/api/data

    could you please help me ?

    • Aras Kairys (DHTMLX team) December 14, 2017 at 3:31 pm

      Hi Julie,
      That’s probably because of the absolute urls used for the api endpoint, they specify path starting from the domain root:

      // enabling data loading
      gantt.load(“/api/data”);
      // initializing dataProcessor
      var dp = new gantt.dataProcessor(“/api/data/”);

      If you deploy the app to the virtual directory, you need urls relative to the app root. A simple solution would be to use relative urls, e.g.:

      // enabling data loading
      gantt.load(“api/data”);
      // initializing dataProcessor
      var dp = new gantt.dataProcessor(“api/data/”);

      For a more reliable approach, you could use the UrlHelper to generate urls https://blog.mariusschulz.com/2011/06/30/how-to-build-absolute-action-urls-using-the-urlhelper-class

      Please use our forum if you have any technical questions: https://forum.dhtmlx.com/

      • julie December 14, 2017 at 7:37 pm

        thanks
        with relative path as ../api/data it’s working on dev and on prod.

  4. Julie January 5, 2018 at 7:26 pm

    I’ve implemented gantt according to this tuto : https://dhtmlx.com/blog/using-dhtmlx-gantt-chart-asp-net-rest-api/

    I’ve added some variable to the task model :

    public class Task
    {

    public int Id { get; set; }
    [MaxLength(255)]
    public string Text { get; set; }
    public DateTime start_date { get; set; }
    public int Duration { get; set; }
    public DateTime end_date
    {
    get
    {
    return start_date.AddDays(+Duration);
    }
    }

    public double Progress { get; set; }
    public int SortOrder { get; set; }
    public string type { get; set; }
    public int? parent { get; set; }

    [Required, DefaultValue(“grey”)]
    public string color { get; set; }

    public int? ProjectId { get; set; }
    public virtual Project Project { get; set; }

    public int? EmployeeId { get; set; }
    public virtual Employee Employee { get; set; }
    }

    When i create or update a task:
    -the database is updated
    -the view (clientSide) is updated only for standard parameter, not for color, project, employee.

    Is there a way to do that ?

    thnaks

  5. Aras Kairys (DHTMLX team) January 5, 2018 at 7:34 pm

    Hi Julie,
    If you have any technical questions, please use our support system: http://support.dhtmlx.com or create a topic on our forum: https://forum.dhtmlx.com/
    Thank you for cooperation.

  6. Manjunath May 31, 2018 at 11:53 am

    We r looking gantt component for production planning,
    ill DHTMLX(gantt) supports us to develop in required format for us

Leave a Reply