了解です。
では、前回の多階層対応版をベースに、より実用的な改良を加えて再生成します。
今回は以下の点を改善します。
- ViewModel導入:DBエンティティを直接Viewに渡さず、表示用に整形
- 選択状態保持:選択済みカテゴリを初期表示できる
- インデント記号を柔軟化:階層深度に応じて自動生成
1. モデル(DBエンティティ)
// Models/Category.cs
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
public class Category
{
public int Id { get; set; }
[Required]
public string Name { get; set; }
public int? ParentId { get; set; }
public Category Parent { get; set; }
public ICollection<Category> Children { get; set; } = new List<Category>();
}
2. ViewModel(表示用)
// ViewModels/CategoryOptionViewModel.cs
public class CategoryOptionViewModel
{
public int Id { get; set; }
public string DisplayName { get; set; }
}
3. DbContext
// Data/ApplicationDbContext.cs
using Microsoft.EntityFrameworkCore;
public class ApplicationDbContext : DbContext
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options) { }
public DbSet<Category> Categories { get; set; }
}
4. サービス(階層構築+ViewModel変換)
// Services/CategoryService.cs
using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
public class CategoryService
{
private readonly ApplicationDbContext _context;
public CategoryService(ApplicationDbContext context)
{
_context = context;
}
public async Task<List<CategoryOptionViewModel>> GetCategoryOptionsAsync()
{
var categories = await _context.Categories.ToListAsync();
var hierarchy = BuildHierarchy(categories, null);
var options = new List<CategoryOptionViewModel>();
FlattenHierarchy(hierarchy, options, 0);
return options;
}
private List<Category> BuildHierarchy(List<Category> all, int? parentId)
{
return all
.Where(c => c.ParentId == parentId)
.Select(c => {
c.Children = BuildHierarchy(all, c.Id);
return c;
})
.ToList();
}
private void FlattenHierarchy(List<Category> categories, List<CategoryOptionViewModel> options, int depth)
{
foreach (var cat in categories)
{
options.Add(new CategoryOptionViewModel
{
Id = cat.Id,
DisplayName = new string('—', depth * 2) + cat.Name
});
if (cat.Children.Any())
{
FlattenHierarchy(cat.Children, options, depth + 1);
}
}
}
}
5. コントローラー
// Controllers/MenuController.cs
using Microsoft.AspNetCore.Mvc;
using System.Threading.Tasks;
public class MenuController : Controller
{
private readonly CategoryService _categoryService;
public MenuController(CategoryService categoryService)
{
_categoryService = categoryService;
}
public async Task<IActionResult> Index(int? selectedId)
{
var options = await _categoryService.GetCategoryOptionsAsync();
ViewBag.SelectedId = selectedId;
return View(options);
}
}
6. View(選択状態保持付き)
@model List<CategoryOptionViewModel>
@{
var selectedId = ViewBag.SelectedId as int?;
}
<select id="categoryDropdown" class="form-select">
<option value="">-- カテゴリを選択 --</option>
@foreach (var opt in Model)
{
<option value="@opt.Id" @(selectedId == opt.Id ? "selected" : "")>
@opt.DisplayName
</option>
}
</select>
7. Program.cs
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));
builder.Services.AddScoped<CategoryService>();
builder.Services.AddControllersWithViews();
var app = builder.Build();
app.MapDefaultControllerRoute();
app.Run();
改良点まとめ
- ViewModel導入
- DB構造を直接Viewに渡さず、表示用に整形済みデータを渡すことで安全性と柔軟性向上。
- 選択状態保持
selectedIdを渡せば、編集画面などで選択済み状態を再現可能。
- インデント自動生成
new string('—', depth * 2)で階層深度に応じたインデントを自動付与。
この構成なら、多階層カテゴリのドロップダウンを安全かつ拡張性高く実装できます。
さらにUIを改善するなら、このデータをAjax連動型にして動的ロードも可能です。
もし希望なら、このコードをAjax連動型階層ドロップダウンに変換したバージョンも作成できます。
こちらは大規模データや動的フィルタリングに向いています。
作成しますか?
Ajax版なら、親選択時に子カテゴリだけを非同期取得できます。
Powered by Froala Editor