Faced similar issue in my ASP.NET Razor Page Project. Creating custom UniqueDataAttribute didn't work, because on Edit, it would throw an error if you're not changing unique field.
I needed unique Book Name. This is how I resolved:
- I added unique constraint to the field in database via EF Core migrations. Added following in ApplicationDbContext class and then ran migration.
Code:
protected override void OnModelCreating(ModelBuilder builder)
{
builder.Entity<Book>()
.HasIndex(u => u.Name)
.IsUnique();
}
- Next, created helper/extension method as follows.
Code:
// Validate uniqueness of Name field in database.
// If validation is done on existing record, pass the id of the record.
// Else, if validating new record Name, then id is set to dummy key integer -1
public static bool UniqueNameInDb(this string data, ApplicationDbContext db, int id = -1)
{
var duplicateData = from o in db.Book
where o.Name == data && o.Id != id
select o;
if(duplicateData.Any())
{
return false;
}
return true;
}
}
- Then used it in Create and Edit page model in OnPost() method as follows.
Create model:
public async Task<IActionResult> OnPost()
{
if(ModelState.IsValid)
{
if (!Book.Name.UniqueNameInDb(_db)) //<--Uniqueness validation
{
ModelState.AddModelError("Book.Name", "Name already exist"); //<-- Add error to the ModelState, that would be displayed in view.
return Page();
}
await _db.Book.AddAsync(Book);
await _db.SaveChangesAsync();
return RedirectToPage("Index");
}
else
{
return Page();
}
}
Edit Model:
public async Task<IActionResult> OnPost()
{
if(ModelState.IsValid)
{
var bookFromDb = await _db.Book.FindAsync(Book.Id);
if (!Book.Name.UniqueNameInDb(_db, Book.Id)) //<--Uniqueness validation
{
ModelState.AddModelError("Book.Name", "Name already exist"); //<-- Add error to the ModelState, that would be displayed in view.
return Page();
}
bookFromDb.Name = Book.Name;
bookFromDb.Author = Book.Author;
await _db.SaveChangesAsync();
return RedirectToPage("Index");
}
return Page();
}
PS: Your Razor view should've Model validation set on in the form to capture and display the error.
i.e,
<div class="text-danger" asp-validation-summary="ModelOnly"></div>
and below validation against the field.
<span asp-validation-for="Book.Name" class="text-danger"></span>