I have created tags for my website posts using Many-to-Many relationship data table. I want to edit/update the tag data in database table using Controller and ViewModel for posts
Models
:
public class PostModel
{
[Key]
public int Id { get; set; }
public string? Title { get; set; }
public List<TagModel> Tags { get; set; }
public List<PostTagModel> PostTags { get; set; }
}
public class TagModel
{
[Key]
public int Id { get; set; }
public string? Title { get; set; }
public List<PostModel> Posts { get; set; }
public List<PostTagModel> PostTags { get; set; }
}
public class PostTagModel
{
public int PostId { get; set; }
public int TagId { get; set; }
public PostModel Post { get; set; }
public TagModel Tag { get; set; }
}
ViewModel
:
public class EditPostViewModel
{
public string? Title { get; set; }
public string? Description { get; set; }
public List<int>? TagId { get; set; }
public List<TagModel>? TagList { get; set; }
}
Controller
:
[HttpGet]
public async Task<IActionResult> Edit(int id)
{
// Id Check
var post = await _postInterface.GetByIdAsync(id);
// Check
if (post == null)
{
return View("Error");
}
var postVM = new EditPostViewModel
{
Title = post.Title,
Description = post.Description,
TagId = post.Tags, // Gives error CS0029
// List
TagList = await _context.Tags.ToListAsync()
};
return View(postVM);
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, EditPostViewModel postVM)
{
// Check
if (!ModelState.IsValid)
{
ModelState.AddModelError("", "Failed to edit post");
return View("Edit", postVM);
}
// Id Select
var userPost = await _postInterface.GetByIdAsyncNoTracking(id);
// Selected Tag
var tags = await _tagInterface.GetAll();
List<TagModel> selectedTags = new List<TagModel>();
if (postVM.TagId != null && postVM.TagId.Any())
{
selectedTags = tags.Where(x => postVM.TagId.Contains(x.Id)).ToList();
}
if (userPost != null)
{
var post = new PostModel
{
Id = id,
Title = postVM.Title,
Description = postVM.Description,
Tags = selectedTags
};
_postInterface.Update(post);
return RedirectToAction("Index");
}
else
{
return View(postVM);
}
}
View
:
@using SimpleWebsite.ViewModels.PostViewModel
@model EditPostViewModel
<!-- Code above -->
<!-- Tag -->
@if (Model.TagList != null)
{
<div class="col-md-12 mb-3">
<label asp-for="TagId" class="control-label"></label>
<div class="form-check-inline">
@foreach (var item in Model.TagList)
{
<input name="TagId" type="checkbox" class="btn-check" id="@item.Id" value="@item.Id">
<label asp-for="TagId" class="btn btn-outline-primary" for="@item.Id">@item.Title</label>
}
</div>
<span asp-validation-for="TagId" class="text-danger"></span>
</div>
}
<!-- Code Below -->
I have resolved my issue. I have also updated the code on my GitHub Repository.
Models:
Added virtual
to many-to-many database table in all models. No need to migrate or update database
public class PostModel
{
[Key]
public int Id { get; set; }
public string? Title { get; set; }
public string Description { get; set; }
public List<TagModel> Tags { get; set; }
public virtual List<PostTagModel> PostTags { get; set; }
}
public class TagModel
{
[Key]
public int Id { get; set; }
public string? Title { get; set; }
public string Description { get; set; }
public List<PostModel> Posts { get; set; }
public virtual List<PostTagModel> PostTags { get; set; }
}
public class PostTagModel
{
public int PostId { get; set; }
public int TagId { get; set; }
public virtual PostModel Post { get; set; }
public virtual TagModel Tag { get; set; }
}
ViewModels
: Added SelectedItemViewModel
public class SelectedItemViewModel
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public bool Selected { get; set; }
}
public class EditPostViewModel
{
public string? Title { get; set; }
public string? Description { get; set; }
public List<SelectedItemViewModel> Tag { get; set; }
}
Controller
:
[HttpGet]
public async Task<IActionResult> Edit(int id)
{
// Id Check
var post = await _postInterface.GetByIdAsync(id);
// Check
if (post == null)
{
return View("Error");
}
// Initialize Selected Tags
var Results = from t in _context.Tags
select new
{
t.Id,
t.Title,
t.Description,
Checked = ((from pt in _context.PostTags
where (pt.PostId == id) &
(pt.TagId == t.Id)
select pt).Count() > 0),
};
// Tags List
var TagList = new List<SelectedItemViewModel>();
// Selected Tags
foreach (var item in Results)
{
TagList.Add(new SelectedItemViewModel
{
Id = item.Id,
Name = item.Title,
Description = item.Description,
Selected = item.Checked
});
}
var postVM = new EditPostViewModel
{
Title = post.Title,
Description = post.Description,
Tag = TagList
};
return View(postVM);
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, EditPostViewModel postVM)
{
// Check
if (ModelState.IsValid)
{
ModelState.AddModelError("", "Failed to edit post");
return View("Edit", postVM);
}
// Id Select
var userPost = await _postInterface.GetByIdAsyncNoTracking(id);
if (userPost != null)
{
var post = new PostModel
{
Id = id,
Title = postVM.Title,
Description = postVM.Description,
};
// Delete Selected Tags
foreach (var item in _context.PostTags)
{
if (item.PostId == id)
{
_context.Entry(item).State = EntityState.Deleted;
}
}
// Update Selected tags
foreach (var item in postVM.Tag)
{
if (item.Selected)
{
_context.PostTags.Add(new PostTagModel()
{
PostId = id,
TagId = item.Id
});
}
}
_postInterface.Update(post);
return RedirectToAction("Index");
}
else
{
return View(postVM);
}
}
View
:
@using SimpleWebsite.ViewModels.PostViewModel
@model EditPostViewModel
<!-- Code above -->
<!-- Tag -->
@if (Model.Tag != null)
{
<div class="col-md-12 mb-3">
<label asp-for="Tag" class="control-label"></label>
<br />
<div class="form-check-inline">
@for (int i = 0; i < Model.Tag.Count(); i++)
{
<input type="checkbox" asp-for="@Model.Tag[i].Selected" class="form-check-input" id="@Model.Tag[i].Id">
<label class="form-check-label me-3" title="@Model.Tag[i].Description" for="@Model.Tag[i].Id">@Model.Tag[i].Name</label>
<input type="hidden" asp-for="@Model.Tag[i].Id">
}
</div>
<span asp-validation-for="Tag" class="text-danger"></span>
</div>
}
<!-- Code Below -->