I try to develop a simple blog app for learning ASP.NET Core. Every Post should have a category and this category should be selectable on creating a post page. I use to DropDownListFor
HTML helper for rendering categories and all category is rendered on creating a post page. I shared below the CreateModel
class:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.EntityFrameworkCore;
using netblogapp.Data;
using netblogapp.Models;
namespace netblogapp.Pages.Admin.Posts
{
public class CreateModel : PageModel
{
private readonly netblogapp.Data.BlogAppDbContext _context;
public CreateModel(netblogapp.Data.BlogAppDbContext context)
{
_context = context;
}
[BindProperty]
public netblogapp.Models.Post Post { get; set; } = default!;
public IList<netblogapp.Models.Category> Categories { get; set; }
public async Task OnGetAsync()
{
if (_context.Category != null)
{
this.Categories = await _context.Category.ToListAsync();
}
}
// To protect from overposting attacks, see https://aka.ms/RazorPagesCRUD
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid || _context.Post == null || Post == null)
{
return Page();
}
_context.Post.Add(Post);
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
}
}
I shared below the Create.cshtml
file:
@page
@model netblogapp.Pages.Admin.Posts.CreateModel
@{
ViewData["Title"] = "Create";
}
<h1>Create</h1>
<h4>Post</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form method="post">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="Post.Title" class="control-label"></label>
<input asp-for="Post.Title" class="form-control" />
<span asp-validation-for="Post.Title" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Post.Description" class="control-label"></label>
<input asp-for="Post.Description" class="form-control" />
<span asp-validation-for="Post.Description" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Post.Author" class="control-label"></label>
<input asp-for="Post.Author" class="form-control" />
<span asp-validation-for="Post.Author" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Post.CreatedAt" class="control-label"></label>
<input asp-for="Post.CreatedAt" class="form-control" />
<span asp-validation-for="Post.CreatedAt" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Post.UpdatedAt" class="control-label"></label>
<input asp-for="Post.UpdatedAt" class="form-control" />
<span asp-validation-for="Post.UpdatedAt" class="text-danger"></span>
</div>
<div class="form-group form-check">
<label class="form-check-label">
<input class="form-check-input" asp-for="Post.isPublish" /> @Html.DisplayNameFor(model => model.Post.isPublish)
</label>
</div>
<div class="form-group">
<label asp-for="Post.Context" class="control-label"></label>
<input asp-for="Post.Context" class="form-control" />
<span asp-validation-for="Post.Context" class="text-danger"></span>
</div>
@Html.DropDownListFor( m => m.Post.Category.Id, new SelectList(Model.Categories, "Id", "Name"), "--- Select Category ---")
<div class="form-group">
<input type="submit" value="Create" class="btn btn-primary" />
</div>
</form>
</div>
</div>
<div>
<a asp-page="Index">Back to List</a>
</div>
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
When I save this post, this exception is thrown:
An unhandled exception occurred while processing the request.
SqlException: Cannot insert explicit value for identity column in table 'Category' when IDENTITY_INSERT is set to OFF.
Microsoft.Data.SqlClient.SqlCommand+<>c.<ExecuteDbDataReaderAsync>b__208_0(Task<SqlDataReader> result)
DbUpdateException: An error occurred while saving the entity changes. See the inner exception for details.
Microsoft.EntityFrameworkCore.Update.ReaderModificationCommandBatch.ExecuteAsync(IRelationalConnection connection, CancellationToken cancellationToken)
There can be two reasons for this exception:
I solved this issue in a few steps:
1- CategoryId variable is added to Post Class. Without any changing on post table, EF performed this. This is my updated Post Class:
public class Post
{
public int Id { get; set; }
// other variables
public int CategoryId { get; set; } // This variable was added later.
public Category? Category { get; set; }
}
And then, I changed m.Post.Category.Id
HTML helper LINQ statements to m.CategoryId
2- OnGetAsync
method change to OnGet()
. Although rendering all categories on the web page, Async method caused to null Category Model. this was determined with if the condition on create.cshtml
@if (Model.Categories != null)
{
@Html.DropDownListFor( m => m.Post.CategoryId, new SelectList(Model.Categories, "Id", "Name"), "--- Select Category ---")
}
3- Another issue is the invalid ModelState. As you see before, the category is not a nullable variable. When I try to insert post data, the category is null. I don't know why this happens but I added a query again using categoryId number. Those statements are comment lines because twice query makes unsense to me. So, I change to category variable to nullable.
public async Task<IActionResult> OnPostAsync()
{
//Category category = _context.Category.First(m => m.Id == Post.CategoryId);
//Post.Category = category;
if (!ModelState.IsValid || _context.Post == null || Post == null) {
return Page();
}
_context.Post.Add(Post);
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
4- Finally, this step not directly about topic but I want to share with you. To render all category names on the Post Index Page, I used eager load search. I shared Index Page's OnGetAysnc
method below:
public async Task OnGetAsync()
{
if (_context.Post != null)
{
Post = await _context.Post.Include(m => m.Category).ToListAsync();
}
}
And add some lines to index html table like below:
<td>
@Html.DisplayFor(modelItem => item.Category.Name)
</td>