Explanation of the Problem
Consider this model where a field called Name
has been marked as required.
public class Product { // auto-increment Primary ID key [Key] [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int ID { get; set; } // this is a required field [Required] public String Name { get; set; } } // ProductContext not shown for simplicity // (see the previous tutorial for the code)
Consider this FORM based on the above model. Notice that we have added a validation summary so that we can show validation failures.
@page @model Modular.Pages.IndexModel @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers <form method="post"> // textbox Name: <input asp-for="Prod.Name" /> // validation summary <div asp-validation-summary="All"></div> // submit button <input type="submit" /> <p>(run with blank form)</p> </form>
Also consider the following handler on the backing class
public class IndexModel : PageModel { private readonly ProductContext _ctx; // dependency injection of the ProductContext public IndexModel(ProductContext ctx) { _ctx = ctx; } // 2-way binding [BindProperty] public Product Prod { get; set; } // OnPost handler public ActionResult OnPost() { // commented so that // the exception is thrown // on context SaveChanges // if (!ModelState.IsValid) // { // return Page() // } // attach the item to // the ProductContext _ctx.Products.Add(Prod); ----> FAILS _ctx.SaveChanges(); // redirect and show the // form again return RedirectToPage(); } }
When the above form is submitted with a blank textbox, an exception is thrown because the [Required]
constraint fails.
Video Explanation
Please watch the following youtube video:
Suggested Solution
Follow these steps-
- Wrap
SaveChanges
in a try-catch - Call
ModelState.AddModelError
to artificially add an error to the affected property in thecatch
block. You can show the actual inner exception or some message. The model error will appear in a validation summary control as shown below.
using DBaseCon; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.EntityFrameworkCore; namespace Modular.Pages { public class IndexModel : PageModel { private readonly ProductContext _ctx; // dependency injection of the ProductContext public IndexModel(ProductContext ctx) { _ctx = ctx; } // 2-way binding [BindProperty] public Product Prod { get; set; } public ActionResult OnPost() { // commented so that // the exception is thrown // on context SaveChanges // if (!ModelState.IsValid) // { // return Page() // } // attach the item to // the ProductContext _ctx.Products.Add(Prod); try { _ctx.SaveChanges(); } catch (DbUpdateException e) { ModelState.Clear(); ModelState.AddModelError("Prod.Name", e.InnerException.Message); return Page(); } // redirect and show the // form again return RedirectToPage(); } } }
This is a sample output:
This Blog Post/Article "(C# ASP.NET Core) Strategy to handle a DbUpdateException" by Parveen is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.