Древовидный список на ASP.NET MVC 4

в 23:33, , рубрики: .net, mvc, Веб-разработка, дерево, список, метки: , ,

Добрый день! На хабре есть статья, в которой рассказывается как сделать древовидный список. Однако, в этой версии будет использован движок Razor, Entity Framework и др., а так же реализованы операции со списком. Данный вариант отличается простотой и быстротой реализации. Статья рассчитана на тех, кто уже знаком с ASP .NET MVC.

Возможности:

  • Отображение списка
  • Добавление элементов
  • Перемещение элементов
  • Удаление элементов

Используемые технологии:

  • Microsoft ASP. NET MVC 4
  • Entity Framework
  • Linq to Entity
  • Microsoft SQL Server (Local DB)

База данных

Так как все записи будут храниться в базе данных, то необходимо создать следующую таблицу:

public class News
{
   public int Id {get;set;} //Идентификатор новости
   public int? ParentId {get;set;} //Идентификатор родительской новости
   public string Title {get;set;} //Заголовок новости
   public bool IsDeleted {get;set;} //Флаг удаления
}

Модели

Далее создадим модели для работы:
1. Обычно в моделях не используют напрямую записи из базы данных, поэтому создадим модель похожую на таблицу.

public class NewsModel
{
   public int Id {get;set;} //Идентификатор новости
   public int? ParentId {get;set;} //Идентификатор родительской новости
   public string Title {get;set;} //Заголовок новости
}

2. Список новостей.

public NewsListModel
{
   public int? Seed {get;set;} //Корневой элемент
   public IEnumerable<NewsModel> News {get;set;} //Список новостей
}

Контроллер

Ниже представлен контроллер, который умеет выбирать, удалять, добавлять и перемещать новости.

public class NewsController : Controller
{
    public ActionResult Index()
    {
        using (NewsContext context = new NewsContext())
        {
            NewsListModel model = new NewsListModel()
            {
                News = context.News.Where(x => !x.IsDeleted).ToArray().Select(x => new NewsModel(x))
            };
            return View(model);
        }
    }

    [HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult Add(int? parentId, string title)
    {
        using (NewsContext context = new NewsContext())
        {
            var newNews = new News()
            {
                ParentId = parentId,
                Title = title
            };
            context.News.Add(newNews);
            context.SaveChanges();
        }
        return RedirectToAction("Index");
    }

    [ValidateAntiForgeryToken]
    [HttpPost]
    public ActionResult Move(int nodeId, int? newParentId)
    {
        if (nodeId == newParentId)
        {
            return RedirectToAction("Index");
        }
        using (NewsContext context = new NewsContext())
        {
            if (newParentId.HasValue && ContainsChilds(context, nodeId, newParentId.Value))
            {
                return RedirectToAction("Index");
            }
            var node = context.News.Where(x => x.Id == nodeId).Single();
            node.ParentId = newParentId;
            context.SaveChanges();
        }
        return RedirectToAction("Index");
    }

    private bool ContainsChilds(NewsContext context, int parentId, int id)
    {
        bool result = false;
        var inner = context.News.Where(x => x.ParentId == parentId && !x.IsDeleted).ToArray();
        foreach (var node in inner)
        {
            if (node.Id == id && node.ParentId == parentId)
            {
                return true;
            }
            result = ContainsChilds(context, node.Id, id);
        }
        return result;
    }

    [HttpPost]
    public ActionResult Delete(int id)
    {
        using (NewsContext context = new NewsContext())
        {
            DeleteNodes(context, id);
            context.SaveChanges();
        }

        return RedirectToAction("Index");
    }

    private void DeleteNodes(NewsContext context, int id)
    {
        var inner = context.News.Where(x => x.ParentId == id && !x.IsDeleted).ToArray();
        foreach (var node in inner)
        {
            node.IsDeleted = true;
            DeleteNodes(context, node.Id);
        }
        var deleted = context.News.Where(x => x.Id == id && !x.IsDeleted).Single();
        deleted.IsDeleted = true;
    }
}

Представление

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

_TreeList.cshtml

@model MySLOTree.Models.NewsListModel

@if (Model.News.Where(x => x.ParentId == Model.Seed).Any())
{
    <ul>
        @foreach (var node in Model.News)
        {
            if (node.ParentId == Model.Seed)
            {
                <a>@node.Title</a>
                MySLOTree.Models.NewsListModel inner = new MySLOTree.Models.NewsListModel 
                { 
                   Seed = node.Id, 
                   News = Model.News 
                };
                @Html.Partial("_TreeList", inner)
            }
         }
       </ul>
}

Результат

Пустой список

Древовидный список на ASP.NET MVC 4

Добавление элементов

Древовидный список на ASP.NET MVC 4

Отображение списка

Древовидный список на ASP.NET MVC 4

СворачиваниеРазворачивание списка

Древовидный список на ASP.NET MVC 4

Перемещение элементов

Древовидный список на ASP.NET MVC 4

Заключение

Для перемещения элементов в дереве используется drag and drop. Добавление и удаление элементов происходит путем нажатия соответствующих иконок. В статье опущены некоторые моменты. Все подробности можно увидеть в исходном коде.
Полный исходный код доступен на github.

P.S. С радостью отвечу на вопросы и обменяюсь опытом.

Автор: Revkov

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js