dhtmlxGantt с ASP.NET Core

Вот руководство, которое поможет вам настроить диаграмму Ганта с использованием ASP.NET Core на стороне сервера.

Если вы хотите изучить другие серверные технологии, посмотрите эти учебные материалы:

Для взаимодействия с базой данных используется Entity Framework Core, а приложение создается с использованием Visual Studio 2022.

Полный исходный код можно найти на GitHub.

Шаг 1. Настройка проекта

Начните с запуска Visual Studio 2022 и создания нового проекта. Выберите Create a new project.

dhtmlxGantt with ASP.NET Core creating a project

Затем выберите "ASP.NET Core Web App" и назовите проект DHX.Gantt.

dhtmlxGantt with ASP.NET Core creating a project

dhtmlxGantt with ASP.NET Core configure a project

dhtmlxGantt with ASP.NET Core configure a project

Теперь проект готов, и вы можете перейти к добавлению разметки и скрипта диаграммы Ганта.

Шаг 2. Добавление разметки и JavaScript для Ганта

Перейдите в wwwroot и создайте новый файл под названием index.html.

dhtmlxGantt with ASP.NET Core 2 creating a project

dhtmlxGantt with ASP.NET Core 2 creating a project

В этом файле настройте базовую страницу для диаграммы Ганта.

Здесь файлы Ганта загружаются с CDN. Если вы используете профессиональную версию компонента, вам нужно будет добавить файлы вручную.

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) {
            // Установить формат даты
            gantt.config.date_format = "%Y-%m-%d %H:%i";
            // Инициализировать диаграмму Ганта
            gantt.init("gantt_here");
 
            // Загрузить данные
            gantt.load("/api/data");
 
            // Настроить dataProcessor
            var dp = new gantt.dataProcessor("/api/");
            dp.init(gantt);
            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(). Подробнее можно узнать здесь.

Program.cs

var builder = WebApplication.CreateBuilder(args);
 
// Добавить службы в контейнер.
builder.Services.AddRazorPages();
 
var app = builder.Build();
 
// Настроить конвейер обработки HTTP-запросов.
if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}
 
app.UseHttpsRedirection();
 
app.UseDefaultFiles();  
app.UseStaticFiles();
 
app.UseRouting();
 
app.UseAuthorization();
 
app.MapRazorPages();
 
app.Run();

Метод app.UseDefaultFiles() позволяет обслуживать файлы по умолчанию, такие как:

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

В этом руководстве используется "index.html". Обратите внимание, что UseDefaultFiles() только переписывает URL — он не обслуживает файлы. Чтобы обслуживать файлы, также необходимо UseStaticFiles().

На данном этапе запуск приложения должен отобразить пустую диаграмму Ганта. Метка "Invalid data" в правом верхнем углу появляется, потому что вызывается gantt.load(), но серверная часть еще не готова для предоставления данных. После реализации серверной части диаграмма покажет задачи и связи.

dhtmlxGantt with ASP.NET Core 2 adding Gantt

После завершения базовой настройки вы можете перейти к реализации серверной части. Начните с создания классов моделей, а затем перейдите к контроллеру WebAPI.

Шаг 3. Создание моделей и базы данных

Модель данных Ганта включает задачи и связи. dhtmlxGantt использует нестандартные имена свойств по сравнению с конвенциями .NET. Иногда дополнительные свойства используются для клиентской или серверной логики, но они не обязательно должны храниться в базе данных.

Для обработки этого используется паттерн Data Transfer Object (DTO). Будут созданы два типа моделей:

  • Классы доменной модели для использования с EF Core и внутри приложения
  • Классы DTO для общения с Web API

Также будет реализовано отображение между этими моделями.

Модели

Создайте папку Models в каталоге вашего проекта для хранения классов моделей и контекста EF.

Модель задачи

Начните с создания класса для задач. Добавьте файл с именем Task.cs в папку Models.

Вот структура модели задачи:

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

Полный список свойств задачи доступен для справки.

Модель связи

Далее создайте класс для связей, добавив файл с именем Link.cs в папку Models.

Вот структура модели связи:

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 будет управлять взаимодействием приложения с базой данных. Чтобы установить его:

  • В дереве проекта найдите Dependencies под DHTMLX.Gantt
  • Щелкните правой кнопкой мыши и выберите Manage NuGet Packages
  • Во вкладке Browse установите Microsoft.EntityFrameworkCore.SqlServer, Microsoft.EntityFrameworkCore, и Microsoft.EntityFrameworkCore.Design

dhtmlxGantt with ASP.NET Core EF core installation

Или используйте командную строку Package Manager:

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

Создание контекста Entity

Чтобы включить взаимодействие с базой данных, определите контекст:

  • Добавьте файл с именем GanttContext.cs в папку Models
  • Определите класс 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;   // База данных уже заполнена
            }
 
            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;"
  }
}

Контекст базы данных будет зарегистрирован с использованием dependency injection.

Добавьте эти пространства имен в 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);
 
// Добавить службы в контейнер.
builder.Services.AddRazorPages();
 
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<GanttContext>(
    options => options.UseSqlServer(connectionString));
 
builder.Services.AddControllers();
 
var app = builder.Build();
 
// Настроить конвейер обработки HTTP-запросов.
if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    // Значение HSTS по умолчанию - 30 дней.
    // Возможно, вы захотите изменить это для производственных сценариев, 
    // см. 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:

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 и seed.

С этой настройкой завершенной, вы можете перейти к Gantt.

Определение DTO и отображение

Далее вы определите классы DTO для Web API. Начните с класса 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
            };
        }
    }
}

Теперь создайте класс DTO для связей в файле с именем WebApiLink.cs, также внутри папки Models:

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

Теперь вы можете запустить приложение, чтобы убедиться, что все настроено правильно. Если ошибок времени выполнения не появляется, значит, все в порядке.

Шаг 4. Реализация Web API

Теперь пришло время реализовать REST API.

Добавление контроллеров

Создайте папку Controllers и добавьте три пустых API контроллера: один для задач, другой для связей и третий для всего набора данных:

Gantt ASP.NET Core 2 adding controllers

Контроллер задач

Необходим контроллер для управления задачами. Он будет обрабатывать основные CRUD операции для задач Ганта.

Вот как это работает:

  • Для GET-запросов задачи извлекаются из базы данных и возвращаются как объекты передачи данных.
  • Для запросов PUT/POST задачи отправляются от клиента как объекты WebAPITask, что является форматом, используемым dhtmlxGantt. Эти объекты необходимо преобразовать в формат класса Task, используемый EntityFramework. После преобразования изменения могут быть сохранены в DatabaseContext.

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

Контроллер связей

Теперь требуется контроллер для управления связями:

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

Контроллер данных

Наконец, требуется контроллер для обработки действий с данными:

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, который будет перехватывать исключения времени выполнения и записывать ответы. Этот класс будет добавлен в конвейер запросов приложения. Вот как это настроить:

1. Начните с создания класса middleware с использованием шаблона в папке проекта.

Gantt ASP.NET Core middleware class

2. Установите JSON-фреймворк для ASP.NET Core. Вы можете сделать это через менеджер пакетов NuGet:

Gantt ASP.NET Core Install NewtonSoft Json

Или используйте командную строку Package Manager:

PM> Install-Package NewtonSoft.JSON

3. В методе Invoke класса middleware оберните вызов _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);
}

4. Добавьте необходимое пространство имен в GanttErrorMiddleware.cs:

using Newtonsoft.Json;

5. Наконец, подключите middleware в Program.cs. Добавьте необходимое пространство имен:

Program.cs

using DHX.Gantt;

Затем включите middleware в конвейер, вызвав:

Program.cs

app.UseGanttErrorMiddleware();

Хранение порядка задач

Если пользователи изменяют порядок задач с помощью перетаскивания, новый порядок можно сохранить в базе данных. Подробнее об этом можно узнать в этой секции.

Изменение порядка на клиентской стороне

Чтобы включить изменение порядка на клиентской стороне, добавьте следующие строки в index.html:

wwwroot/index.html

gantt.config.order_branch = true;
gantt.config.order_branch_free = true;
 
// указание формата даты
gantt.config.date_format = "%Y-%m-%d %H:%i";
// инициализация 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, вместе с другими данными задачи.

Чтобы обработать это, добавьте свойство target в класс WebApiTask:

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

Далее, измените действие Put в TaskController для обработки изменения порядка:

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))                         {                                                            // произошло изменение порядка                          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;
 
    // идентификатор соседней задачи отправляется либо как '{id}', либо как 'next:{id}' в зависимости от того, является ли она следующим или предыдущим соседом
    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

Простой способ защититься от XSS — закодировать текстовые свойства ваших данных перед их отправкой клиенту. Вот пример использования встроенного HtmlEncoder для экранирования HTML в тексте задач. Это гарантирует, что в базе данных хранятся неизмененные данные, но клиент получает безопасные значения.

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 все равно не отображает задачи и связи, ознакомьтесь с руководством по устранению неполадок в этой статье. В нем содержатся советы по диагностике и устранению проблем.

Что дальше

После выполнения этих шагов у вас теперь есть полностью функциональная диаграмма Ганта. Полный код доступен на GitHub для клонирования или загрузки и использования в ваших проектах.

Для получения дополнительных функций изучите руководства по Gantt или ознакомьтесь с учебными материалами по интеграции Gantt с другими серверными фреймворками.

К началу