본문으로 건너뛰기

ASP.NET MVC와 dhtmlxGantt

이 튜토리얼은 서버 측에서 ASP.NET과 REST API를 사용하여 Gantt를 단계별로 만드는 방법을 안내합니다.

다음 튜토리얼 중 하나를 선택하면 Gantt의 서버 측 통합 가능성을 다른 관점에서 탐색할 수 있습니다.

우리는 ASP.NET MVC 5 웹 플랫폼과 REST API용 Web API 2 컨트롤러를 활용하여 Gantt 애플리케이션을 만들 예정입니다. 데이터베이스와의 의사소통을 조직하기 위해 Entity Framework을 사용할 것입니다. 그리고 Visual Studio IDE의 도움으로 애플리케이션을 구축할 것입니다.

노트

전체 소스 코드는 GitHub에서 확인할 수 있습니다.

1단계. 프로젝트 생성

새로운 Visual Studio 프로젝트 만들기

Visual Studio 2022를 시작하고 Create a new project(새 프로젝트 만들기)를 선택합니다.

how_to_start_net_create_project

다음으로 "ASP.NET Web Application"을 선택하고 이름을 DHX.Gantt.Web으로 지정합니다. 필요한 템플릿을 찾을 수 없다면 Troubleshooting 섹션을 확인하십시오.

how_to_start_net_project_template

how_to_start_net_configure_project

목록의 템플릿 가운데에서 Empty를 선택하고, 템플릿 옆의 MVC 및 Web API 체크박스를 선택합니다.

how_to_start_net_app

2단계. 페이지에 Gantt 추가하기

컨트롤러 생성

이제 빈 프로젝트가 준비되었으며, Gantt를 구현할 준비가 되었습니다.

먼저, Gantt 차트를 보여줄 페이지를 갖는 MVC 컨트롤러를 추가합니다.

컨트롤러 폴더에서 컨텍스트 메뉴를 열고 Add->Controller를 선택합니다. 열린 창에서 MVC 5 Controller -> Empty를 선택하고 새로 추가된 컨트롤러의 이름을 "HomeController"로 지정합니다.

how_to_start_net_controller

HomeController는 기본적으로 Index() 메서드를 가진다으며, 추가 로직이 필요하지 않습니다. 우리는 단지 이를 위한 뷰를 추가하기만 하면 됩니다.

Controllers/HomeController.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;

namespace DHX.Gantt.Web.Controllers
{
public class HomeController : Controller
{
// GET: Home
public ActionResult Index()
{
return View();
}
}
}

뷰 생성

이제 인덱스 페이지를 생성할 차례입니다. Views/Home으로 이동하여 Index라는 빈 뷰를 추가합니다.

how_to_start_net_view

방금 생성된 뷰를 열고 아래 코드를 입력합니다:

Views/Home/Index.cshtml
@{
Layout = null;
}

<!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/");
// 그리고 gantt에 연결
dp.init(gantt);
// dataProcessor의 REST 모드 설정
dp.setTransactionMode("REST");
});
</script>
</head>
<body>
<div id="gantt_here" style="width: 100%; height: 100vh;"></div>
</body>
</html>

여기서 한 일:

  • 간트 애플리케이션의 간단한 페이지 마크업 정의
  • CDN 링크를 사용하여 dhtmlx gantt js/css 소스 추가
  • 페이지에서 gantt를 생성

데이터 소스에서 오는 날짜 형식을 지정한 구성을 주의 깊게 확인하십시오.

Views/Home/Index.cshtml
gantt.config.date_format = "%Y-%m-%d %H:%i";

이는 서버에서 오는 날짜를 클라이언트가 파싱할 수 있도록 필요한 설정입니다.

그리고 또한 백엔드에서 RESTful API와 동작하도록 gantt에 RESTful API를 사용한다고 명시했습니다. 기본 경로로 "/api/"을 사용합니다:

Views/Home/Index.cshtml
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");

서버 측은 나중에 구현하겠습니다. 지금은 애플리케이션을 실행해 페이지에 간트가 렌더링되는지 확인하면 됩니다.

adding_gantt

3단계. 모델 및 데이터베이스 생성

모델 생성

이제 간트 차트를 위한 모델 클래스를 정의해야 합니다. 간트 데이터 모델은 링크와 작업으로 구성됩니다. 볼 수 있듯이, dhtmlxGantt는 C#에서 전통적으로 사용하는 것과 다른 데이터 모델의 명명 규칙을 사용합니다. 클라이언트 측 모델에는 데이터베이스에 저장할 필요가 없는 속성도 포함될 수 있지만, 이는 클라이언트에서 또는 백엔드 로직에서 사용될 수 있습니다.

이 때문에 여기에선 데이터 전송 객체 패턴을 사용합니다. 도메인 모델 클래스(EF와 함께 애플리케이션에서 사용할)와 Web API와 통신하는 데 사용할 DTO 클래스를 정의한 뒤, 두 모델 간 매핑을 구현합니다.

이제 시작합니다!

Task 모델

먼저 Task 클래스를 생성합니다. 내용은 아래와 유사합니다:

Models/Task.cs
using System;

namespace DHX.Gantt.Web.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 객체에 대해 사용할 수 있는 속성의 전체 목록은 문서의 해당 기사(task_properties)에서 확인할 수 있습니다.

다음으로 Link 클래스는 아래와 같이 보일 수 있습니다:

Models/Link.cs
namespace DHX.Gantt.Web.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 설치

데이터베이스와의 작업을 구성하기 위해 Entity Framework를 사용할 예정입니다.

먼저 EF를 설치해야 합니다. 이를 위해 Package Manager Console에서 다음 명령을 실행합니다:

Install-Package EntityFramework

데이터베이스 컨텍스트 생성

다음 단계는 Context를 만드는 일입니다. 컨텍스트는 데이터베이스와의 세션을 나타냅니다. 데이터를 가져오고 저장할 수 있게 해줍니다.

Models 폴더에 컨텍스트를 만들기 위한 컨텍스트 메뉴를 열고 Add->Class를 선택합니다. 새 클래스의 이름은 "GanttContext"이며 아래와 같은 내용이 포함됩니다:

Models/GanttContext.cs
using System.Data.Entity;

namespace DHX.Gantt.Web.Models
{
public class GanttContext : DbContext
{
public DbSet<Task> Tasks { get; set; }
public DbSet<Link> Links { get; set; }
}
}

데이터베이스에 초기 데이터 추가

이제 데이터베이스에 몇 가지 데이터를 추가할 수 있습니다.

Entity Framework는 애플리케이션이 실행될 때 데이터베이스를 자동으로 생성할 수 있습니다. 모델이 변경될 때마다 데이터베이스를 삭제하고 재생성하도록 설정해야 합니다.

먼저 데이터베이스 이니셜라이저를 생성해야 합니다. 이를 위해 App_Start 폴더에 DropCreateDatabaseIfModelChanges를 상속받는 새 클래스를 추가합니다. 이름은 "GanttInitializer"로 하겠습니다.

이 클래스에서 Seed() 메서드를 재정의하여 테스트 데이터를 채웁니다. 그런 다음 컨텍스트에 엔티티 컬렉션을 Add() 메서드로 추가합니다.

아래에 있는 것은 GanttInitializer 클래스의 전체 코드입니다:

App_Start/GanttInitializer.cs
using System;
using System.Collections.Generic;
using System.Data.Entity;


namespace DHX.Gantt.Web.Models
{
public class GanttInitializer : DropCreateDatabaseIfModelChanges<GanttContext>
{
protected override void Seed(GanttContext context)
{
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.SaveChanges();

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.SaveChanges();
}
}
}

Global.asax 파일을 엽니다. 애플리케이션 시작 시 실행되는 코드가 들어 있습니다. 컨텍스트에 Initializer를 설정하는 코드 한 줄과 필요한 네임스페이스를 추가합니다:

Global.asax.cs
using System;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
using System.Web.Http;

using System.Data.Entity;
using DHX.Gantt.Web.Models;

namespace DHX.Gantt.Web
{
public class Global : HttpApplication
{
void Application_Start(object sender, EventArgs e)
{
// Code that runs on application startup
AreaRegistration.RegisterAllAreas();
GlobalConfiguration.Configure(WebApiConfig.Register);
RouteConfig.RegisterRoutes(RouteTable.Routes);

Database.SetInitializer(new GanttInitializer());/*!*/
}
}
}

DTO와 매핑 정의

이제 Web API에서 사용할 DTO 클래스를 선언할 차례입니다. Model과 DTO 간의 매핑은 가장 단순한 방식으로 진행하고, 이 클래스들에 대한 명시적 변환 연산자를 정의합니다.

TaskDto 클래스는 아래와 같은 형태가 될 것입니다:

Models/TaskDto.cs
using System;

namespace DHX.Gantt.Web.Models
{
public class TaskDto
{
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 TaskDto(Task task)
{
return new TaskDto
{
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(TaskDto task)
{
return new Task
{
Id = task.id,
Text = task.text,
StartDate = DateTime.Parse(
task.start_date,
System.Globalization.CultureInfo.InvariantCulture),
Duration = task.duration,
ParentId = task.parent,
Type = task.type,
Progress = task.progress
};
}
}
}

다음은 LinkDto 클래스의 코드입니다:

Models/LinkDto.cs
namespace DHX.Gantt.Web.Models
{
public class LinkDto
{
public int id { get; set; }
public string type { get; set; }
public int source { get; set; }
public int target { get; set; }

public static explicit operator LinkDto(Link link)
{
return new LinkDto
{
id = link.Id,
type = link.Type,
source = link.SourceTaskId,
target = link.TargetTaskId
};
}

public static explicit operator Link(LinkDto link)
{
return new Link
{
Id = link.id,
Type = link.type,
SourceTaskId = link.source,
TargetTaskId = link.target
};
}
}
}

마지막으로 데이터 소스에 대한 모델도 추가합니다:

Models/GanttDto.cs
using System.Collections.Generic;

namespace DHX.Gantt.Web.Models
{
public class GanttDto
{
public IEnumerable<TaskDto> data { get; set; }
public IEnumerable<LinkDto> links { get; set; }
}
}

4단계. Web API 구현

REST API를 이용한 데이터 로딩의 일반 기술

마침내 API를 구현할 차례입니다.

API 상세 정보(requestresponsedetails)에서 보듯 두 개의 컨트롤러가 필요합니다: 하나는 작업(Task)용, 다른 하나는 링크(Link)용 입니다. 또한 간트가 거기에 혼합된 결과를 기대하기 때문에 데이터 로드 작업용 컨트롤러가 한 개 더 필요합니다.

Task 컨트롤러

새 컨트롤러를 만들려면:

  • 컨트롤러 폴더의 컨텍스트 메뉴를 활성화하고 Add -> Controller를 선택합니다.
  • Web API 2 Controller -> Empty를 선택합니다. 새 컨트롤러의 이름은 "TaskController"가 됩니다.

이제 작업 항목에 대한 기본 CRUD 동작을 구현해야 합니다:

Controllers/TaskController.cs
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Web.Http;
using System.Web.UI.WebControls;

using DHX.Gantt.Web.Models;

namespace DHX.Gantt.Web.Controllers
{
public class TaskController : ApiController
{
private GanttContext db = new GanttContext();

// GET api/Task
public IEnumerable<TaskDto> Get()
{
return db.Tasks
.ToList()
.Select(t => (TaskDto)t);
}

// GET api/Task/5
[System.Web.Http.HttpGet]
public TaskDto Get(int id)
{
return (TaskDto)db
.Tasks
.Find(id);
}

// PUT api/Task/5
[System.Web.Http.HttpPut]
public IHttpActionResult EditTask(int id, TaskDto taskDto)
{
var updatedTask = (Task)taskDto;
updatedTask.Id = id;
db.Entry(updatedTask).State = EntityState.Modified;
db.SaveChanges();

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

// POST api/Task
[System.Web.Http.HttpPost]
public IHttpActionResult CreateTask(TaskDto taskDto)
{
var newTask = (Task)taskDto;

db.Tasks.Add(newTask);
db.SaveChanges();

return Ok(new
{
tid = newTask.Id,
action = "inserted"
});
}

// DELETE api/Task/5
[System.Web.Http.HttpDelete]
public IHttpActionResult DeleteTask(int id)
{
var task = db.Tasks.Find(id);
if (task != null)
{
db.Tasks.Remove(task);
db.SaveChanges();
}

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

protected override void Dispose(bool disposing)
{
if (disposing)
{
db.Dispose();
}
base.Dispose(disposing);
}
}
}

여기서는 모든 것이 아주 단순합니다:

  • GET 작업에서 데이터베이스에서 로드한 작업을 데이터 전송 객체로 출력
  • PUT/POST 동작에서 DTO를 입력으로 받아 Task 모델로 변환한 뒤 DB 컨텍스트에 변경 사항을 저장

이제 링크에 대해서도 동일하게 구현합니다.

링크용 빈 Web API 컨트롤러를 다음과 같이 생성합니다:

Controllers/LinkController.cs
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Web.Http;
using DHX.Gantt.Web.Models;

namespace DHX.Gantt.Web.Controllers
{
public class LinkController : ApiController
{
private GanttContext db = new GanttContext();

// GET api/Link
[System.Web.Http.HttpGet]
public IEnumerable<LinkDto> Get()
{
return db
.Links
.ToList()
.Select(l => (LinkDto)l);
}

// GET api/Link/5
[System.Web.Http.HttpGet]
public LinkDto Get(int id)
{
return (LinkDto)db
.Links
.Find(id);
}

// POST api/Link
[System.Web.Http.HttpPost]
public IHttpActionResult CreateLink(LinkDto linkDto)
{
var newLink = (Link)linkDto;
db.Links.Add(newLink);
db.SaveChanges();

return Ok(new
{
tid = newLink.Id,
action = "inserted"
});
}

// PUT api/Link/5
[System.Web.Http.HttpPut]
public IHttpActionResult EditLink(int id, LinkDto linkDto)
{
var clientLink = (Link)linkDto;
clientLink.Id = id;

db.Entry(clientLink).State = EntityState.Modified;
db.SaveChanges();

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

// DELETE api/Link/5
[System.Web.Http.HttpDelete]
public IHttpActionResult DeleteLink(int id)
{
var link = db.Links.Find(id);
if (link != null)
{
db.Links.Remove(link);
db.SaveChanges();
}
return Ok(new
{
action = "deleted"
});
}

protected override void Dispose(bool disposing)
{
if (disposing)
{
db.Dispose();
}
base.Dispose(disposing);
}

}
}

데이터 컨트롤러

마지막으로 데이터 액션용 컨트롤러를 추가합니다:

Controllers/DataController.cs
using System.Web.Http;

using DHX.Gantt.Web.Models;

namespace DHX.Gantt.Web.Controllers
{
public class DataController : ApiController
{
// GET api/
[System.Web.Http.HttpGet]
public GanttDto Get()
{
return new GanttDto
{
data = new TaskController().Get(),
links = new LinkController().Get()
};
}
}
}

이제 모든 준비가 끝났습니다. 애플리케이션을 실행하면 페이지에 완전한 간트가 나타나야 합니다:

ready_gantt_dotnet

미리 준비된 데모를 GitHub에서 확인할 수 있습니다.

오류 처리

예외 필터"를 CRUD 핸들러에서 예외를 포착하고 클라이언트 측 간트가 인식할 수 있는 응답으로 반환하는 데 사용할 수 있습니다.

간트를 위한 오류 처리를 제공하려면 아래 단계를 따르십시오:

App_Start로 가서 GanttAPIExceptionFilterAttribute라는 새 클래스를 추가합니다:

App_Start/GanttAPIExceptionFilterAttribute.cs
using System.Net;
using System.Net.Http;
using System.Web.Http.Filters;

namespace DHX.Gantt.Web
{
public class GanttAPIExceptionFilterAttribute : ExceptionFilterAttribute
{
public override void OnException(HttpActionExecutedContext context)
{

context.Response = context.Request.CreateResponse(
HttpStatusCode.InternalServerError, new
{
action = "error",
message = context.Exception.Message
}
);
}
}
}

그런 다음 이 클래스를 Web API 컨트롤러에 추가합니다:

  • 데이터 컨트롤러(DataController.cs):
Controllers/DataController.cs
namespace DHX.Gantt.Web.Controllers
{
[GanttAPIExceptionFilter]/*!*/
public class DataController : ApiController
  • 링크 컨트롤러(LinkController.cs):
Controllers/LinkController.cs
namespace DHX.Gantt.Web.Controllers
{
[GanttAPIExceptionFilter]/*!*/
public class LinkController : ApiController
  • 작업 컨트롤러(TaskController.cs):
Controllers/TaskController.cs
namespace DHX.Gantt.Web.Controllers
{
[GanttAPIExceptionFilter]/*!*/
public class TaskController : ApiController

이제 Web API 컨트롤러가 요청 처리 중 예외를 발생시키면, 클라이언트 측은 에러 상태와 에러 메시지를 받게 되어 적절히 처리되거나 사용자에게 표시될 수 있습니다.

생산 환경에서는 예외 메시지를 클라이언트에 그대로 반환하는 것이 최선의 방법이 아닐 수도 있다는 점에 주의하십시오.

작업 순서 저장

클라이언트 측 간트는 드래그 앤 드롭을 사용하여 작업의 재정렬을 허용합니다. 이 기능을 사용한다면 데이터베이스에 이 순서를 저장해야 합니다. 여기서는 일반적인 설명을 서버 측 저장 방법에서 확인할 수 있습니다.

이 기능을 앱에 추가해 보겠습니다.

클라이언트에서 작업 재정렬 활성화

우선 UI에서 사용자가 작업 순서를 변경할 수 있도록 해야 합니다.

[Index] 뷰를 열고 간트 구성(configuration)을 업데이트합니다:

Views/Home/Index.cshtml
gantt.config.order_branch = true;/*!*/
gantt.config.order_branch_free = true;/*!*/

// 날짜 형식 지정
gantt.config.date_format = "%Y-%m-%d %H:%i";
// 간트 초기화
gantt.init("gantt_here");

모델에 작업 순서 추가

다음으로 백엔드에서 이 변경을 반영합니다.

SortOrder라는 속성의 값으로 순서를 저장할 예정이므로 Task 클래스도 이에 맞게 업데이트합니다:

Models/Task.cs
using System;
using System.ComponentModel.DataAnnotations;

namespace DHX.Gantt.Web.Models
{
public class Task
{
public int Id { get; set; }
[MaxLength(255)]
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; }/*!*/
}
}

이제 TaskController를 업데이트해야 합니다. 본문은 아래와 같이 변경됩니다:

  • 클라이언트는 SortOrder 값으로 정렬된 작업을 받아야 합니다:
Controllers/TaskController.cs
namespace DHX.Gantt.Web.Controllers
{
[GanttAPIExceptionFilter]
public class TaskController : ApiController
{
private GanttContext db = new GanttContext();

// GET api/Task
public IEnumerable<TaskDto> Get()
{
return db.Tasks
.OrderBy(t => t.SortOrder) /*!*/
.ToList()
.Select(t => (TaskDto)t);
}
  • 새로 추가되는 작업의 기본 SortOrder 값은 다음과 같이 설정됩니다:
Controllers/TaskController.cs
namespace DHX.Gantt.Web.Controllers
{
[System.Web.Http.HttpPost]
public IHttpActionResult CreateTask(TaskDto taskDto)
{
var newTask = (Task)taskDto;

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

db.Tasks.Add(newTask);
db.SaveChanges();

return Ok(new
{
tid = newTask.Id,
action = "inserted"
});
}
  • 클라이언트에서 작업 순서가 변경될 때 SortOrder가 업데이트되어야 합니다.

사용자가 작업 순서를 변경하면, gantt는 새 작업 위치에 대한 정보를 담은 요청의 'target' 속성과 함께 PUT 동작을 호출합니다. 따라서 Task DTO 클래스에 추가 속성을 정의해야 합니다:

Models/TaskDto.cs
namespace DHX.Gantt.Web.Models
{
public class TaskDto
{
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 string target { get; set; }/*!*/

...
}
}

이제 PUT(EditTask) 동작에서 재정렬 로직을 구현합니다:

Controllers/TaskController.cs
    // PUT api/Task/5
[System.Web.Http.HttpPut]
public IHttpActionResult EditTask(int id, TaskDto taskDto)
{
var updatedTask = (Task)taskDto;
updatedTask.Id = id;

if (!string.IsNullOrEmpty(taskDto.target))
{
// 재정렬이 발생했습니다
this._UpdateOrders(updatedTask, taskDto.target);/*!*/
}

db.Entry(updatedTask).State = EntityState.Modified;
db.SaveChanges();

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

private void _UpdateOrders(Task updatedTask, string orderTarget)/*!*/
{
int adjacentTaskId;
var nextSibling = false;

var targetId = orderTarget;

// 인접 작업 ID는 '{id}' 또는 'next:{id}' 형식으로 전송되며
// 이는 다음 형제인지 이전 형제인지를 나타냅니다
if (targetId.StartsWith("next:"))
{
targetId = targetId.Replace("next:", "");
nextSibling = true;
}

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

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

if (nextSibling)
startOrder++;

updatedTask.SortOrder = startOrder;

var updateOrders = db.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++);
}

알려진 문제점

HTTP PUT 및 DELETE 요청이 IIS에서 애플리케이션이 실행 중일 때 405 또는 401 오류를 반환하는 경우가 있습니다. 이 문제는 RESTful 핸들러와 충돌할 수 있는 WebDAV 모듈 때문에 발생할 수 있습니다.

일반적인 해결 방법으로는 web.config 파일에서 모듈을 비활성화하는 것이 있습니다. 자세한 내용은 여기를 참고하십시오.

애플리케이션 보안

Gantt는 SQL 인젝션, XSS 및 CSRF 공격과 같은 다양한 위협으로부터 애플리케이션을 방지하는 방법을 제공하지 않습니다. 안전한 애플리케이션 운영 책임은 백엔드를 구현하는 개발자에게 있습니다. 관련 세부 정보는 해당 기사(앱 보안_guides)를 읽어보십시오.

Trouble shooting

ASP.NET Web Application 템플릿이 없을 때

Visual Studio 2022에서 필요한 "ASP.NET Web Application" 템플릿을 찾을 수 없다면 아래 단계를 따라 주십시오:

  1. Visual Studio 2022를 종료합니다.

  2. 시작 메뉴 -> Visual Studio 설치관리자

  3. Visual Studio Community 2022를 찾아 Modify를 클릭합니다.

vsinstaller

  1. 열린 창에서 Individual components를 선택하고, 목록에서 .NET Framework Project and item templates 항목을 체크한 후 Modify를 클릭합니다.

components

그런 다음 Visual Studio 2022를 다시 시작하고 필요한 템플릿을 찾으실 수 있습니다.

데이터베이스 초기화 중 예외 발생

때때로 모델 변경 시 데이터베이스를 드롭하고 새로 생성하는 DropCreateDatabaseIfModelChanges 초기화에서 문제가 발생할 수 있습니다.

exception_error

이 경우 GanttInitializer.cs를 열고 DropCreateDatabaseIfModelChangesDropCreateDatabaseAlways로 교체합니다:

App_Start/GanttInitializer.cs
using System;
using System.Collections.Generic;
using System.Data.Entity;

namespace DHX.Gantt.Web.Models
{
public class GanttInitializer : DropCreateDatabaseAlways<GanttContext> /*!*/
{
...
}
}

그런 다음 애플리케이션을 다시 실행합니다.

작업 및 링크 렌더링 문제

위의 단계를 따라 ASP.NET MVC와의 Gantt 연동을 구현했으나 페이지에 작업과 링크가 렌더링되지 않는 경우, Troubleshooting Backend Integration Issues 문서를 확인해 보십시오. 문제의 원인을 식별하는 방법을 설명합니다.

다음 단계

지금은 완전히 작동하는 간트를 갖게 되었습니다. GitHub에서 전체 코드를 확인하거나 클론/다운로드하여 프로젝트에 활용하십시오.

또한 간트의 다양한 기능에 대한 가이드(guides.md)나 다른 백엔드 프레임워크와의 연동 튜토리얼(integrations/howtostart-guides.md)을 확인해 보실 수 있습니다.

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.