跳到主要内容

dhtmlxGantt 与 ASP.NET Core 集成指南

本指南将带您逐步完成在服务端使用 ASP.NET Core 搭建甘特图的过程。

同时,我们还为其他服务端平台提供了教程:

数据库交互部分采用了 Entity Framework Core。项目开发工具为 Visual Studio 2022。

注释

完整源代码可在 GitHub 查看。

第一步:创建项目

启动 Visual Studio 2022,选择 Create a new project 新建项目。

dhtmlxGantt with ASP.NET Core 创建项目

选择 "ASP.NET Core Web App",项目名称设置为 DHX.Gantt

dhtmlxGantt with ASP.NET Core 创建项目

dhtmlxGantt with ASP.NET Core 配置项目

dhtmlxGantt with ASP.NET Core 配�置项目信息

项目创建完成后,即可添加甘特图所需的标记和脚本。

第二步:添加甘特图标记和 JS

进入 wwwroot 目录,新建名为 index.html 的文件。

dhtmlxGantt with ASP.NET Core 创建项目第3步

dhtmlxGantt with ASP.NET Core 创建项目第4步

在该文件中,构建一个简单页面用于展示甘特图。

请注意,本例中甘特图文件是从 CDN 加载的。如果您拥有 Professional 版本,需要 手动将甘特图文件添加到项目中

index.html

<!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>

页面加载后,甘特图会被初始化,并通过 gantt.load() 立即开始加载数据。同时 dataProcessor 也被配置好,用户在图表上的修改会自动保存到服务器。由于后端尚未搭建,完整功能将在后续实现后体现。

接下来,打开 Program.cs,配置应用以便能够访问 index.html 页面。需要通过添加 app.UseDefaultFiles() 启用从 wwwroot 文件夹提供静态文件。

更多信息请参考 这里

Program.cs

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();

app.UseDefaultFiles() 方法会查找 wwwroot 目录下的默认文件:

  • index.html
  • index.htm
  • default.html
  • default.htm

您可以使用上述任意文件名,本教程采用 "index.html"。

请注意,UseDefaultFiles() 仅重写 URL,并不会实际提供文件。要真正提供静态文件,还需添加 UseStaticFiles()

完成上述步骤后,运行应用将显示一个空的甘特图。右上角会出现 "Invalid data" 提示,因为 gantt.load() 被调用但后端尚未提供数据。待控制器实现后,甘特图会正确显示任务和链接。

dhtmlxGantt with ASP.NET Core 添加甘特图

基础环境搭建完毕,接下来构建后端。首先定义模型类,然后创建 WebAPI 控制器。

第三步:创建模型与数据库

首先定义数据模型。典型的甘特图数据模型包括 links 和 tasks。 dhtmlxGantt 使用的属性名与 .NET 习惯不同,且部分属性仅用于客户端或后端逻辑,不应存入数据库。

为处理此问题,将采用 数据传输对象(DTO) 模式。将创建两类模型:

  • 供 EF Core 和内部应用使用的领域模型类
  • 用于与 Web API 通信的 DTO 类

还需要实现这两类模型之间的映射。

模型

在项目目录下新建 Models 文件夹。此文件夹用于存放模型类与 EF 上下文。

Task 模型

创建任务类。在 Models 文件夹下新建 Task.cs 文件(右键文件夹选择 Add->Class)。

类定义如下:

DHX.Gantt/Models/Task.cs

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; }
}
}

完整的 Task 对象属性列表参见 这里

为 Link 新建文件:

DHX.Gantt/Models/Link.cs

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 下的 Dependencies
  • 右键选择 Manage NuGet packages
  • 切换到 Browse 标签,安装 Microsoft.EntityFrameworkCore.SqlServerMicrosoft.EntityFrameworkCoreMicrosoft.EntityFrameworkCore.Design

dhtmlxGantt with ASP.NET Core 安装 EF core

也可使用 Package Manager Console:

PM> Install-Package Microsoft.EntityFrameworkCore.SqlServer
PM> Install-Package Microsoft.EntityFrameworkCore
PM> Install-Package Microsoft.EntityFrameworkCore.Design

这些包将为数据库交互提供所需工具。

创建实体上下文

接下来,定义与数据库的会话以实现数据加载和保存。新建上下文类:

  • Models 文件夹下新建 GanttContext.cs 文件
  • 在其中定义 GanttContext

DHX.Gantt/Models/GanttContext.cs

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() 方法,用于向数据库添加任务和链接。

DHX.Gantt/Models/GanttSeeder.cs

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 文件(如果已经存在则直接打开),并添加数据库的连接字符串:

appsettings.json

{
"ConnectionStrings": {
"DefaultConnection": "Server="(localdb)mssqllocaldb;"
Database=GanttDatabase;Trusted_Connection=True;"
}
}

数据库上下文将通过 依赖注入" 的方式进行注册。

Program.cs 文件中添加以下命名空间:

Program.cs

using Microsoft.EntityFrameworkCore;
using DHX.Gantt.Models;

注册的代码如下所示:

Program.cs

var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<GanttContext>(
options => options.UseSqlServer(connectionString));

为了启用控制器,需要添加 services.AddControllers() 方法:

Program.cs

builder.Services.AddControllers();

并通过调用 app.MapControllers() 注册控制器路由:

Program.cs

app.MapControllers();

以下是 Program.cs 文件的完整内容:

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();

最后,当应用启动时应该初始化并填充数据库。虽然通常会使用迁移(migrations)来实现,但本例为简化起见未使用迁移。

首先,在 Models 文件夹下创建一个类用于初始化。添加 GanttInitializerExtension.cs 文件:

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() 方法:

Program.cs

app.InitializeDatabase();

如前所述,本教程未使用迁移,而是通过 EnsureCreated 和数据填充(seeding)来实现。

到此为止,本部分内容已经完成。接下来,将继续处理甘特图相关工作。

定义 DTO 和映射

现在需要为 Web API 创建将要使用的 DTO 类。 首先为任务(Task)创建 DTO 类。在 Models 文件夹下新建文件并定义 WebApiTask.cs 类:

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
};
}
}
}

接下来是 Link 的 DTO 类,在 Models 文件夹下的 WebApiLink.cs 文件中定义:

Models/WebApiLink.cs

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
};
}
}
}

完成上述步骤后,文件夹结构应如下所示:

Gantt ASP.NET Core 2 All models

此时建议运行应用程序以验证一切是否设置正确。如果没有出现运行时错误,说明设置成功。

Step 4. 实现 Web API

现在是实现 REST API 的时候了。

添加控制器

创建一个 Controllers 文件夹,并添加三个空的 API 控制器:分别用于 Tasks、Links 以及整个数据集:

Gantt ASP.NET Core 2 adding controllers

任务控制器(Task Controller)

以下是用于管理任务(Tasks)的控制器,实现了甘特任务的基本 CRUD 操作。

功能说明:

  • 对于 GET 请求,从数据库获取任务并以数据传输对象的形式返回;
  • 对于 PUT/POST 请求,任务以 WebApiTask 实例的形式从客户端传来,这也是 dhtmlxGantt 使用的格式。保存到 DatabaseContext 之前,需要将其转换为 EntityFramework 使用的数据模型(Task 类)。

Controllers/TaskController.cs

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"
});
}
}
}

接下来是 Links 的控制器:

Controllers/LinkController.cs

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"
});
}
}
}

Data Controller

最后,这里是数据操作的控制器:

Controllers/DataController.cs

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)

};
}

}
}

至此,所有内容都已就绪,应用程序可以运行,你将看到一个完整可用的甘特图。

Gantt ASP.NET Core Gantt is ready

完整源码也可在 GitHub 上获取

错误处理

为了有效管理错误,你需要创建一个特殊的 middleware class" 来捕获运行时异常并发送适当的响应。该中间件随后会被添加到应用的请求管道中。操作步骤如下:

  1. 使用模板向你的项目添加一个中间件类。

Gantt ASP.NET Core middleware class

  1. 为 ASP.NET Core 安装 JSON 框架。可以通过 NuGet 包管理器完成:

Gantt ASP.NET Core Install NewtonSoft Json

或者使用包管理器控制台:

PM> Install-Package NewtonSoft.JSON
  1. 找到中间件中的 Invoke 方法。由于有些处理程序可能会抛出异常,请将 _next 调用放入 try-catch 块中,在发生错误时处理异常。

GanttErrorMiddleware.cs

public async Task Invoke(HttpContext httpContext)
{
try
{
await _next(httpContext);
}catch(Exception e)
{
await HandleExceptionAsync(httpContext, e);
}
}
private static Task HandleExceptionAsync(HttpContext context, Exception exception)
{
var result = JsonConvert.SerializeObject(new {
action = "error"
});
context.Response.ContentType = "application/json";
context.Response.StatusCode = StatusCodes.Status500InternalServerError;
return context.Response.WriteAsync(result);
}
  1. GanttErrorMiddleware.cs 文件中添加如下命名空间:
using Newtonsoft.Json;
  1. 中间件已准备就绪。接下来,打开 Program.cs 并注册该中间件,添加如下内容:

Program.cs

using DHX.Gantt;

然后通过以下调用将中间件加入管道:

Program.cs

app.UseGanttErrorMiddleware();

存储任务顺序

当用户在客户端通过拖拽重新排序任务时,新顺序应保存到数据库。详细内容请参阅 本节

下面介绍如何在甘特图中启用任务顺序存储。

客户端排序

首先,在客户端启用任务排序,在 index.html 文件中添加如下代码:

wwwroot/index.html

gantt.config.order_branch = true;
gantt.config.order_branch_free = true;

// specifying the date format
gantt.config.date_format = "%Y-%m-%d %H:%i";
// initializing gantt
gantt.init("gantt_here");

在模型中添加任务顺序

然后,更新后端以反映当前任务顺序。在 Task 模型中添加新属性:

Models/Task.cs

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; }
public int SortOrder { get; set; } /*!*/
}
}

更新控制器

控制器也需要做一些更新。

  1. 客户端应按 SortOrder 排序获取任务。在 DataController 中添加标记行:

Controllers/DataController.cs

[HttpGet]
public object Get()
{
return new
{
data = _context.Tasks
.OrderBy(t => t.SortOrder) /*!*/
.ToList()
.Select(t => (WebApiTask)t),
links = _context.Links
.ToList()
.Select(l => (WebApiLink)l)
};
}
  1. 创建新任务时,确保它们获得默认的 SortOrder 值:

controllers/TaskController.cs

// POST api/task
[HttpPost]
public IActionResult Post(WebApiTask apiTask)
{
var newTask = (Models.Task)apiTask;

newTask.SortOrder = _context.Tasks.Max(t => t.SortOrder) + 1; /*!*/
_context.Tasks.Add(newTask);
_context.SaveChanges();

return Ok(new
{
tid = newTask.Id,
action = "inserted"
});
}
  1. 当任务在客户端重新排序时,需要更新 sortOrder。当任务被重新排列时,gantt 会通过 PUT 请求发送包含新位置信息的 'target' 属性,以及其他任务详情。

WebApiTask.cs 类中添加 target 属性:

Models/WebApiTask.cs

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 string? target { get; set; } /*!*/
public bool open
{
get { return true; }
set { }
}
}

现在,在 Task 控制器的 PUT (EditTask) 方法中实现重新排序逻辑:

Controllers/TaskController.cs

// PUT api/task/5
[HttpPut("{id}")]
public IActionResult? Put(int id, WebApiTask apiTask)
{
var updatedTask = (Models.Task)apiTask;
updatedTask.Id = id;

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;

if (!string.IsNullOrEmpty(apiTask.target)) /*!*/
{ /*!*/
// reordering happened /*!*/
this._UpdateOrders(dbTask, apiTask.target); /*!*/
} /*!*/

_context.SaveChanges();

return Ok(new
{
action = "updated"
});
}

添加用于调整任务顺序的辅助方法:

Controllers/TaskController.cs

private void _UpdateOrders(Models.Task updatedTask, string orderTarget)
{
int adjacentTaskId;
var nextSibling = false;

var targetId = orderTarget;

// adjacent task id may come as '{id}' or 'next:{id}' indicating
// whether it's the next or previous sibling
if (targetId.StartsWith("next:"))
{
targetId = targetId.Replace("next:", "");
nextSibling = true;
}

if (!int.TryParse(targetId, out adjacentTaskId))
{
return;
}

var adjacentTask = _context.Tasks.Find(adjacentTaskId);
var startOrder = adjacentTask!.SortOrder;

if (nextSibling)
startOrder++;

updatedTask.SortOrder = startOrder;

var updateOrders = _context.Tasks
.Where(t => t.Id != updatedTask.Id)
.Where(t => t.SortOrder >= startOrder)
.OrderBy(t => t.SortOrder);

var taskList = updateOrders.ToList();

taskList.ForEach(t => t.SortOrder++);
}

应用安全

Gantt 本身不负责防范如 SQL 注入、XSS 或 CSRF 等安全威胁。后端开发者需自行保证应用的安全。详情请参考 相关文档

XSS 防护

一种简单的方法是在将文本字段发送到客户端之前进行编码。

例如,下面的示例使用内置的 HtmlEncoder 对任务文本中的 HTML 进行转义。这样,数据库中保存的是原始数据,但客户端收到的是安全的 task.text

Models/WebApiTask.cs

using System.Text.Encodings.Web;

public static explicit operator WebApiTask(Task task)
{
return new WebApiTask
{
id = task.Id,
text = HtmlEncoder.Default.Encode(task.Text != null ? task.Text : ""), /*!*/
start_date = task.StartDate.ToString("yyyy-MM-dd HH:mm"),
duration = task.Duration,
parent = task.ParentId,
type = task.Type,
progress = task.Progress
};
}

另外,也可以使用如 HtmlAgilityPack 这样的专业库,在保存或加载任务时彻底移除所有 HTML。

故障排查

如果你已按照所有步骤将 Gantt 集成到 ASP.NET Core,但页面上未显示任务和链接,请参阅 백엔드 통합 문제 해결 文章。文中涵盖了常见问题及其排查方法。

后续步骤

此时,你已经拥有了一个可用的甘特图实现。完整源码可在 GitHub 上获取,可用于你的项目。

你还可以查阅 涵盖众多甘特功能的指南集成 Gantt 到其他后端框架的教程

Need help?
Got a question about the documentation? Reach out to our technical support team for help and guidance. For custom component solutions, visit the Services page.