DHTMLX Gantt是用于跨浏览器和跨平台应用程序的功能齐全的Gantt图表。可满足项目管理应用程序的大部分开发需求,具备完善的甘特图图表库,功能强大,价格便宜,提供丰富而灵活的JavaScript API接口,与各种服务器端技术(PHP,http://ASP.NET,Java等)简单集成,满足多种定制开发需求。
DHTMLX JavaScript UI 库所开发的 JavaScript 组件易于使用且功能丰富,非常适合任何领域和任何复杂性的解决方案,能够节省创建和维护业务应用程序的时间,提高生产力。
DHTMLX Gantt官方最新版免费下载试用https://link.zhihu.com/?target=https%3A//www.evget.com/product/4213/download
本教程将逐步指导你如何用http://ASP.NET Core在服务器端创建甘特图。
第一步 创建一个项目
启动Visual Studio 2022并创建一个新的项目。选择: 创建一个新的项目。
接下来选择 "ASP.NET Core Web App "并命名为DHX.Gantt。
自此,你已经创建了一个项目,可以继续为甘特图添加标记和脚本。
第二步 添加甘特图标记和JS
进入wwwroot并创建一个index.html文件。
在新创建的文件中,为甘特图制作一个简单的页面。
请注意,甘特图文件是在此演示中从 CDN 添加的。如果您有该组件的专业版本, 您需要手动将甘特图文件添加到项目中。
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Index</title>
<link href=" https://cdn.dhtmlx.com/gantt/edge/dhtmlxgantt.css"
rel="stylesheet" type="text/css" />
<script src=" https://cdn.dhtmlx.com/gantt/edge/dhtmlxgantt.js"></script>
<script>
document.addEventListener("DOMContentLoaded", function(event) {
// specifying the date format
gantt.config.date_format = "%Y-%m-%d %H:%i";
// initializing gantt
gantt.init("gantt_here");
// initiating data loading
gantt.load("/api/data");
// initializing dataProcessor
var dp = new gantt.dataProcessor("/api/");
// and attaching it to gantt
dp.init(gantt);
// setting the REST mode for dataProcessor
dp.setTransactionMode("REST");
});
</script>
</head>
<body>
<div id="gantt_here" style="width: 100%; height: 100vh;"></div>
</body>
</html>
当页面被加载时,除了初始化甘特图的数据加载外,还立即调用dataProcessor,所以用户对甘特图的所有修改都将被保存到后台。后台还没有实现,所以以后会更有意义。
接下来进入Program.cs,告诉应用程序使用index.html页面。为了做到这一点,你需要将应用程序配置为从wwwroot文件夹中提供静态文件。为此,你需要添加app.UseDefaultFiles()方法。你可以在这里找到更多细节。
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddRazorPages();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days.
// You may want to change this for production scenarios,
// see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseDefaultFiles();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.MapRazorPages();
app.Run();
该方法允许提供默认文件。它将搜索wwwroot文件夹中的以下文件:app.UseDefaultFiles()
- index.html
- index.htm
- default.html
- default.htm
因此,你可以选择其中的任何一个,而在本教程中使用的是 "index.html"。只是一个URL-rewriter,并不实际提供文件。为此,你还需要添加文件。UseDefaultFiles()UseStaticFiles()
一旦你完成了它,当你运行应用程序时,一个空的甘特图就会出现在页面上。注意,右上角的 "无效数据 "标签显示,因为因为仍然没有合适的后台来提供数据,所以被调用。当控制器实现后,gantt将能够显示任务和链接。gantt.load()
现在基本部分已经完成,是时候实现后端了。让我们从实现模型类开始,之后再进行WebAPI控制器。
第三步 创建模型和数据库
让我们从数据模型开始。甘特的数据模型包括链接和任务,从.NET世界的角度来看,dhtmlxGantt对模型属性使用非常规的名称。有时客户端的模型也包含一些客户端或后端逻辑的属性,但这些属性不应该存储在数据库中。
为了处理这个问题,将使用数据传输对象(DTO)模式。两种模型将被定义:
- 领域模型类,它将被用于EF Core和应用程序内部
- DTO类,将用于与Web API进行通信。
然后,需要实现这两种模型之间的映射。
模型
在项目文件夹中创建一个名为Models的新文件夹。这是实现模型类和 EF 上下文的位置。
任务模型
首先,为任务创建一个类。在Models文件夹中创建一个文件,命名为Task.cs。这可以通过调用Models文件夹的上下文菜单并选择Add->Class来完成。
这个模型必须是这样的:
namespace DHX.Gantt.Models
{
public class Task
{
public int Id { get; set; }
public string? Text { get; set; }
public DateTime StartDate { get; set; }
public int Duration { get; set; }
public decimal Progress { get; set; }
public int? ParentId { get; set; }
public string? Type { get; set; }
}
}
你可以查询任务对象的所有属性列表。
链接模型
再添加一个文件,为链接创建一个类:
namespace DHX.Gantt.Models
{
public class Link
{
public int Id { get; set; }
public string? Type { get; set; }
public int SourceTaskId { get; set; }
public int TargetTaskId { get; set; }
}
}
模型已经准备好了,你可以开始配置数据库连接。
为了配置数据库连接,你需要采取下面列出的步骤:
安装Entity Framework Core
Entity Framework Core将被用来管理应用程序与数据库的通信。让我们来安装该框架:
- 在项目树中找到DHTMLX.Gantt的依赖项
- 调用上下文菜单并选择管理NuGet包
- 打开浏览标签并安装Microsoft.EntityFrameworkCore.SqlServer, Microsoft.EntityFrameworkCore和Microsoft.EntityFrameworkCore.Design
或者使用软件包管理器命令行:
PM> Install-Package Microsoft.EntityFrameworkCore.SqlServer
PM> Install-Package Microsoft.EntityFrameworkCore
PM> Install-Package Microsoft.EntityFrameworkCore.Design
实体框架核心将用来管理应用程序与数据库的通信。
创建实体上下文
接下来你需要定义一个与数据库的会话,并启用加载和保存数据。为此,创建Context:
- 在Models文件夹中添加GanttContext.cs文件
- 在创建的文件中定义GanttContext类
using Microsoft.EntityFrameworkCore;
namespace DHX.Gantt.Models
{
public class GanttContext : DbContext
{
public GanttContext(DbContextOptions<GanttContext> options)
: base(options)
{
}
public DbSet<Task> Tasks { get; set; } = null;
public DbSet<Link> Links { get; set; } = null;
}
}
向数据库添加第一批记录
现在你可以向数据库添加记录了。让我们创建数据库初始化器,将任务填充到数据库中。在Models文件夹中定义一个类,并将其称为GanttSeeder。该类将有Seed()方法,将添加任务和链接到数据库。
using Microsoft.EntityFrameworkCore;
namespace DHX.Gantt.Models
{
public static class GanttSeeder
{
public static void Seed(GanttContext context)
{
if (context.Tasks.Any())
{
return; // DB has been seeded
}
using (var transaction = context.Database.BeginTransaction())
{
List<Task> tasks = new List<Task>()
{
new Task()
{
Id = 1,
Text = "Project #2",
StartDate = DateTime.Today.AddDays(-3),
Duration = 18,
Progress = 0.4m,
ParentId = null
},
new Task()
{
Id = 2,
Text = "Task #1",
StartDate = DateTime.Today.AddDays(-2),
Duration = 8,
Progress = 0.6m,
ParentId = 1
},
new Task()
{
Id = 3,
Text = "Task #2",
StartDate = DateTime.Today.AddDays(-1),
Duration = 8,
Progress = 0.6m,
ParentId = 1
}
};
tasks.ForEach(s => context.Tasks.Add(s));
context.Database.ExecuteSqlRaw("SET IDENTITY_INSERT Tasks ON;");
context.SaveChanges();
context.Database.ExecuteSqlRaw("SET IDENTITY_INSERT Tasks OFF;");
List<Link> links = new List<Link>()
{
new Link() {Id = 1, SourceTaskId = 1, TargetTaskId = 2, Type = "1"},
new Link() {Id = 2, SourceTaskId = 2, TargetTaskId = 3, Type = "0"}
};
links.ForEach(s => context.Links.Add(s));
context.Database.ExecuteSqlRaw("SET IDENTITY_INSERT Links ON;");
context.SaveChanges();
context.Database.ExecuteSqlRaw("SET IDENTITY_INSERT Links OFF;");
transaction.Commit();
}
}
}
}
注册数据库
现在你应该在Program.cs中注册数据库。但首先你需要一个连接字符串。它将被存储在应用程序设置的JSON文件中。创建appsettings.json文件(如果你已经有了,则打开它),并为数据库添加一个连接字符串:
{
"ConnectionStrings": {
"DefaultConnection": "Server=(localdb)\\mssqllocaldb;
Database=GanttDatabase;Trusted_Connection=True;"
}
}
数据库上下文将通过依赖关系注入进行注册。
将以下命名空间添加到 Program.cs:
using Microsoft.EntityFrameworkCore;
using DHX.Gantt.Models;
该声明将看起来像这样:
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<GanttContext>(
options => options.UseSqlServer(connectionString));
要启用控制器,需要调用services.AddControllers()方法:
builder.Services.AddControllers();
然后我们调用app.MapControllers()来注册我们的控制器路线:
app.MapControllers();
下面是Program.cs的完整代码:
using Microsoft.EntityFrameworkCore;
using DHX.Gantt.Models;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddRazorPages();
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<GanttContext>(
options => options.UseSqlServer(connectionString));
builder.Services.AddControllers();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days.
// You may want to change this for production scenarios,
// see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseDefaultFiles();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.MapRazorPages();
app.MapControllers();
app.Run();
最后,你需要在应用启动时初始化和播种数据库。通常情况下,你会希望使用迁移来完成这个任务,但为了简单起见,这里没有使用迁移。
让我们首先创建一个初始化的类。在Models文件夹中创建GanttInitializerExtension.cs文件:
namespace DHX.Gantt.Models
{
public static class GanttInitializerExtension
{
public static IHost InitializeDatabase(this IHost webHost)
{
var serviceScopeFactory =
(IServiceScopeFactory?)webHost.Services.GetService(typeof(IServiceScopeFactory));
using (var scope = serviceScopeFactory!.CreateScope())
{
var services = scope.ServiceProvider;
var dbContext = services.GetRequiredService<GanttContext>();
dbContext.Database.EnsureDeleted();
dbContext.Database.EnsureCreated();
GanttSeeder.Seed(dbContext);
}
return webHost;
}
}
}
接下来调用InitializeDatabase():
app.InitializeDatabase();
正如上面提到的,本教程中没有使用迁移。而是使用简单的EnsureCreated和种子。
当前部分已经完成,让我们回到甘特图。
定义DTOs和映射
现在是定义将用于Web API的DTO类的时候了。让我们从任务的DTO类开始。在Models文件夹中创建一个文件并定义WebApiTask.cs类:
namespace DHX.Gantt.Models
{
public class WebApiTask
{
public int id { get; set; }
public string? text { get; set; }
public string? start_date { get; set; }
public int duration { get; set; }
public decimal progress { get; set; }
public int? parent { get; set; }
public string? type { get; set; }
public bool open
{
get { return true; }
set { }
}
public static explicit operator WebApiTask(Task task)
{
return new WebApiTask
{
id = task.Id,
text = task.Text,
start_date = task.StartDate.ToString("yyyy-MM-dd HH:mm"),
duration = task.Duration,
parent = task.ParentId,
type = task.Type,
progress = task.Progress
};
}
public static explicit operator Task(WebApiTask task)
{
return new Task
{
Id = task.id,
Text = task.text,
StartDate = task.start_date != null ? DateTime.Parse(task.start_date,
System.Globalization.CultureInfo.InvariantCulture) : new DateTime(),
Duration = task.duration,
ParentId = task.parent,
Type = task.type,
Progress = task.progress
};
}
}
}
而这是定义在Models文件夹中名为WebApiLink.cs的文件中的Link的DTO类:
namespace DHX.Gantt.Models
{
public class WebApiLink
{
public int id { get; set; }
public string? type { get; set; }
public int source { get; set; }
public int target { get; set; }
public static explicit operator WebApiLink(Link link)
{
return new WebApiLink
{
id = link.Id,
type = link.Type,
source = link.SourceTaskId,
target = link.TargetTaskId
};
}
public static explicit operator Link(WebApiLink link)
{
return new Link
{
Id = link.id,
Type = link.type,
SourceTaskId = link.source,
TargetTaskId = link.target
};
}
}
}
当你完成这一步骤时,你应该得到以下的文件夹结构:
现在你可以运行该应用程序,以检查一切是否到位。如果你没有看到一个运行时错误,那么一切都很好。
第四步 实现网络API
现在是实际实现REST API的时候了。
添加控制器
创建控制器文件夹并创建三个空的API控制器:一个用于任务,另一个用于链接,还有一个用于整个数据集:
任务控制器
让我们为任务创建一个控制器。它将定义甘特任务的基本CRUD操作。
它是如何工作的:
在GET请求中,任务从数据库加载,输出是任务的数据传输对象;
在PUT/POST请求中,任务作为WebAPITask类来自客户端。它们在dhtmlxGantt中以这种方式表示。因此,你应该把它们转换为我们的EntityFramework数据模型的格式(任务类)。之后,就可以在DatabaseContext中保存更改。
using Microsoft.AspNetCore.Mvc;
using DHX.Gantt.Models;
namespace DHX.Gantt.Controllers
{
[Produces("application/json")]
[Route("api/task")]
public class TaskController : Controller
{
private readonly GanttContext _context;
public TaskController(GanttContext context)
{
_context = context;
}
// GET api/task
[HttpGet]
public IEnumerable<WebApiTask> Get()
{
return _context.Tasks
.ToList()
.Select(t => (WebApiTask)t);
}
// GET api/task/5
[HttpGet("{id}")]
public Models.Task? Get(int id)
{
return _context
.Tasks
.Find(id);
}
// POST api/task
[HttpPost]
public ObjectResult Post(WebApiTask apiTask)
{
var newTask = (Models.Task)apiTask;
_context.Tasks.Add(newTask);
_context.SaveChanges();
return Ok(new
{
tid = newTask.Id,
action = "inserted"
});
}
// PUT api/task/5
[HttpPut("{id}")]
public ObjectResult? Put(int id, WebApiTask apiTask)
{
var updatedTask = (Models.Task)apiTask;
var dbTask = _context.Tasks.Find(id);
if (dbTask == null)
{
return null;
}
dbTask.Text = updatedTask.Text;
dbTask.StartDate = updatedTask.StartDate;
dbTask.Duration = updatedTask.Duration;
dbTask.ParentId = updatedTask.ParentId;
dbTask.Progress = updatedTask.Progress;
dbTask.Type = updatedTask.Type;
_context.SaveChanges();
return Ok(new
{
action = "updated"
});
}
// DELETE api/task/5
[HttpDelete("{id}")]
public ObjectResult DeleteTask(int id)
{
var task = _context.Tasks.Find(id);
if (task != null)
{
_context.Tasks.Remove(task);
_context.SaveChanges();
}
return Ok(new
{
action = "deleted"
});
}
}
}
链接控制器
接下来你应该为链接创建一个控制器:
using Microsoft.EntityFrameworkCore;
using Microsoft.AspNetCore.Mvc;
using DHX.Gantt.Models;
namespace DHX.Gantt.Controllers
{
[Produces("application/json")]
[Route("api/link")]
public class LinkController : Controller
{
private readonly GanttContext _context;
public LinkController(GanttContext context)
{
_context = context;
}
// GET api/Link
[HttpGet]
public IEnumerable<WebApiLink> Get()
{
return _context.Links
.ToList()
.Select(t => (WebApiLink)t);
}
// GET api/Link/5
[HttpGet("{id}")]
public Link? Get(int id)
{
return _context
.Links
.Find(id);
}
// POST api/Link
[HttpPost]
public ObjectResult Post(WebApiLink apiLink)
{
var newLink = (Link)apiLink;
_context.Links.Add(newLink);
_context.SaveChanges();
return Ok(new
{
tid = newLink.Id,
action = "inserted"
});
}
// PUT api/Link/5
[HttpPut("{id}")]
public ObjectResult Put(int id, WebApiLink apiLink)
{
var updatedLink = (Link)apiLink;
updatedLink.Id = id;
_context.Entry(updatedLink).State = EntityState.Modified;
_context.SaveChanges();
return Ok(new
{
action = "updated"
});
}
// DELETE api/Link/5
[HttpDelete("{id}")]
public ObjectResult DeleteLink(int id)
{
var Link = _context.Links.Find(id);
if (Link != null)
{
_context.Links.Remove(Link);
_context.SaveChanges();
}
return Ok(new
{
action = "deleted"
});
}
}
}
数据控制器
最后,你需要为一个数据动作创建一个控制器:
using Microsoft.AspNetCore.Mvc;
using DHX.Gantt.Models;
namespace DHX.Gantt.Controllers
{
[Produces("application/json")]
[Route("api/data")]
public class DataController : Controller
{
private readonly GanttContext _context;
public DataController(GanttContext context)
{
_context = context;
}
// GET api/data
[HttpGet]
public object Get()
{
return new
{
data = _context.Tasks.ToList().Select(t => (WebApiTask)t),
links = _context.Links.ToList().Select(l => (WebApiLink)l)
};
}
}
}
一切都准备好了。你可以运行该应用程序,看到成熟的甘特图。
你也可以在GitHub上查看完整的源代码。
DHTMLX Gantt享有超十年声誉,支持跨浏览器和跨平台,性价比高,可满足项目管理控件应用的所有需求,是最完善的甘特图图表库。